1 /*
2 * Copyright (C) 2005-2006 Evgeniy <dushistov@mail.ru>
3 * Copyright 2011 kubtek <kubtek@mail.com>
4 *
5 * This file is part of StarDict.
6 *
7 * StarDict is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * StarDict is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with StarDict. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #ifdef HAVE_CONFIG_H
22 # include "config.h"
23 #endif
24
25 #include <cstring>
26 #include <iostream>
27 #include <gtk/gtk.h>
28 #include <glib/gi18n.h>
29
30 #include "conf.h"
31 #include "lib/utils.h"
32 #include "stardict.h"
33 #include "lib/xml_str.h"
34
35 #include "articleview.h"
36 #include "desktop.h"
37
38 //#define DEBUG
39 //#define DDEBUG
40
41 /* Helper class.
42 * Provides a means to generate a unique mark name and insert it at the
43 * specified point. All marks inserted by this class are deleted when this
44 * object is destroyed. */
45 class Marks {
46 public:
Marks(PangoWidgetBase * pango_view)47 Marks(PangoWidgetBase* pango_view)
48 :
49 mark_num_(-1),
50 pango_view_(pango_view),
51 insert_last_where_mark_(),
52 insert_last_offset_(-1),
53 insert_last_gravity_(false),
54 insert_last_mark_num_(-1),
55 clone_last_where_mark_(),
56 clone_last_gravity_(false),
57 clone_last_mark_num_(-1)
58 {
59 }
~Marks(void)60 ~Marks(void)
61 {
62 int size = static_cast<int>(marks_list_.size());
63 for(int i=0; i<size; ++i) {
64 if(marks_list_[i] > 0)
65 pango_view_->delete_mark(gen_mark_name(i).c_str());
66 }
67 }
68 // return value: name of the mark inserted
append_mark(bool left_gravity=true)69 std::string append_mark(bool left_gravity = true)
70 {
71 std::string mark(gen_mark_name(++mark_num_));
72 #ifdef DDEBUG
73 std::cout << "article::append_mark " << mark << std::endl;
74 #endif
75 pango_view_->append_mark(mark.c_str(), left_gravity);
76 marks_list_.push_back(1);
77 #ifdef DEBUG
78 if(mark_num_ + 1 != static_cast<int>(marks_list_.size()))
79 g_warning("article::Marks. incorrect list size.");
80 #endif
81 return mark;
82 }
83 // return value: name of the mark inserted
insert_mark(const std::string & where_mark_name,int char_offset=0,bool left_gravity=true)84 std::string insert_mark(const std::string& where_mark_name,
85 int char_offset = 0, bool left_gravity = true)
86 {
87 if(where_mark_name == insert_last_where_mark_
88 && char_offset == insert_last_offset_
89 && left_gravity == insert_last_gravity_
90 && marks_list_[insert_last_mark_num_] > 0) {
91 ++marks_list_[insert_last_mark_num_];
92 #ifdef DDEBUG
93 std::cout << "article::insert_mark hit" << std::endl;
94 #endif
95 return gen_mark_name(insert_last_mark_num_);
96 }
97 std::string mark(gen_mark_name(++mark_num_));
98 insert_last_where_mark_ = where_mark_name;
99 insert_last_offset_ = char_offset;
100 insert_last_gravity_ = left_gravity;
101 insert_last_mark_num_ = mark_num_;
102 #ifdef DDEBUG
103 std::cout << "article::insert_mark " << mark << std::endl;
104 #endif
105 pango_view_->insert_mark(mark.c_str(), where_mark_name.c_str(), char_offset,
106 left_gravity);
107 marks_list_.push_back(1);
108 #ifdef DEBUG
109 if(mark_num_ + 1 != static_cast<int>(marks_list_.size()))
110 g_warning("article::Marks. incorrect list size.");
111 #endif
112 return mark;
113 }
114 /* In is not the same as insert_mark(where_mark_name, 0, left_gravity)! */
clone_mark(const std::string & where_mark_name,bool left_gravity=true)115 std::string clone_mark(const std::string& where_mark_name,
116 bool left_gravity = true)
117 {
118 if(where_mark_name == clone_last_where_mark_
119 && left_gravity == clone_last_gravity_
120 && marks_list_[clone_last_mark_num_] > 0) {
121 ++marks_list_[clone_last_mark_num_];
122 #ifdef DDEBUG
123 std::cout << "article::clone_mark hit" << std::endl;
124 #endif
125 return gen_mark_name(clone_last_mark_num_);
126 }
127 std::string mark(gen_mark_name(++mark_num_));
128 clone_last_where_mark_ = where_mark_name;
129 clone_last_gravity_ = left_gravity;
130 clone_last_mark_num_ = mark_num_;
131 #ifdef DDEBUG
132 std::cout << "article::clone_mark " << mark << std::endl;
133 #endif
134 pango_view_->clone_mark(mark.c_str(), where_mark_name.c_str(), left_gravity);
135 marks_list_.push_back(1);
136 #ifdef DEBUG
137 if(mark_num_ + 1 != static_cast<int>(marks_list_.size()))
138 g_warning("article::Marks. incorrect list size.");
139 #endif
140 return mark;
141 }
142 private:
gen_mark_name(int num) const143 std::string gen_mark_name(int num) const
144 {
145 glib::CharStr gmark(g_strdup_printf("_ArticleView_mark_%d", num));
146 return get_impl(gmark);
147 }
148 private:
149 typedef std::vector<int> MarksList;
150 /* marks_list_[i] - number of references to mark i, use gen_mark_name
151 * function to get mark name: gen_mark_name(i) */
152 MarksList marks_list_;
153 int mark_num_; // last mark number used
154 PangoWidgetBase* pango_view_;
155
156 std::string insert_last_where_mark_;
157 int insert_last_offset_;
158 bool insert_last_gravity_;
159 int insert_last_mark_num_;
160
161 std::string clone_last_where_mark_;
162 bool clone_last_gravity_;
163 int clone_last_mark_num_;
164 };
165
166 /* resource data structure
167 * A resource file is loaded only if necessary.
168 * We postpone loading files till a user requests to use them.
169 * That speeds up article loading, saves memory. */
170 class ResData {
171 public:
ResData(size_t iLib_,const std::string & key_)172 ResData(size_t iLib_, const std::string& key_)
173 :
174 iLib(iLib_),
175 key(key_)
176 {
177
178 }
get_url(void)179 const char* get_url(void)
180 {
181 if(file.empty())
182 file = gpAppFrame->oLibs.GetStorageFilePath(iLib, key);
183 return file.get_url();
184 }
get_content(void)185 const char * get_content(void)
186 {
187 return gpAppFrame->oLibs.GetStorageFileContent(iLib, key);
188 }
get_key(void) const189 const std::string& get_key(void) const
190 {
191 return key;
192 }
193 private:
194 size_t iLib;
195 std::string key;
196 FileHolder file;
197 };
198
199 struct ArticleView::ParseResultItemWithMark {
200 ParseResultItem* item;
201 std::string mark;
202 int char_offset;
203 // true if a tmp char is added after the mark
204 bool tmp_char;
ParseResultItemWithMarkArticleView::ParseResultItemWithMark205 ParseResultItemWithMark(ParseResultItem* item, int char_offset,
206 bool tmp_char=false)
207 : item(item), char_offset(char_offset), tmp_char(tmp_char)
208 {
209 }
210 };
211
append_and_mark_orig_word(const std::string & mark,const gchar * origword,const LinksPosList & links)212 void ArticleView::append_and_mark_orig_word(const std::string& mark,
213 const gchar *origword,
214 const LinksPosList& links)
215 {
216 if(mark.empty())
217 return;
218 if (conf->get_bool_at("dictionary/markup_search_word") &&
219 origword && *origword) {
220 std::string res;
221 XMLCharData xmlcd;
222 xmlcd.assign_xml(mark.c_str());
223 const char* const cd_str = xmlcd.get_char_data_str();
224 if(cd_str) {
225 const size_t cd_str_len = xmlcd.get_char_data_str_length();
226 const size_t olen = strlen(origword);
227 const char* cd_b, *cd_p;
228 const char* const start_tag = "<span background=\"yellow\">";
229 const char* const end_tag = "</span>";
230 cd_b = cd_str;
231 while(cd_b < cd_str + cd_str_len && (cd_p = strstr(cd_b, origword))) {
232 xmlcd.copy_xml(res, cd_b - cd_str, cd_p - cd_str);
233 xmlcd.mark_substring(res, start_tag, end_tag, cd_p - cd_str, olen);
234 cd_b = cd_p + olen;
235 }
236 xmlcd.copy_xml(res, cd_b - cd_str, cd_str_len);
237 } else // mark does not contain char data, only markup
238 res = mark;
239 if (links.empty())
240 append_pango_text(res.c_str());
241 else
242 append_pango_text_with_links(res, links);
243 } else {
244 if (links.empty())
245 append_pango_text(mark.c_str());
246 else
247 append_pango_text_with_links(mark, links);
248 }
249 }
250
AppendData(gchar * data,const gchar * oword,const gchar * real_oword)251 void ArticleView::AppendData(gchar *data, const gchar *oword,
252 const gchar *real_oword)
253 {
254 std::string mark;
255
256 guint32 sec_size=0;
257 const guint32 data_size=get_uint32(data);
258 data+=sizeof(guint32);
259 const gchar *p=data;
260 bool first_time = true;
261 size_t iPlugin;
262 size_t nPlugins = gpAppFrame->oStarDictPlugins->ParseDataPlugins.nplugins();
263 unsigned int parsed_size;
264 ParseResult parse_result;
265 while (guint32(p - data)<data_size) {
266 if (first_time)
267 first_time=false;
268 else
269 mark+= "\n";
270 for (iPlugin = 0; iPlugin < nPlugins; iPlugin++) {
271 parse_result.clear();
272 if (gpAppFrame->oStarDictPlugins->ParseDataPlugins.parse(iPlugin, p, &parsed_size, parse_result, oword)) {
273 p += parsed_size;
274 break;
275 }
276 }
277 if (iPlugin != nPlugins) {
278 append_and_mark_orig_word(mark, real_oword, LinksPosList());
279 mark.clear();
280 append_data_parse_result(real_oword, parse_result);
281 parse_result.clear();
282 continue;
283 }
284 switch (*p) {
285 case 'm':
286 //case 'l': //TODO: convert from local encoding to utf-8
287 p++;
288 sec_size = strlen(p);
289 if (sec_size) {
290 gchar *m_str = g_markup_escape_text(p, sec_size);
291 mark+=m_str;
292 g_free(m_str);
293 }
294 sec_size++;
295 break;
296 case 'g':
297 p++;
298 sec_size=strlen(p);
299 if (sec_size) {
300 mark+=p;
301 }
302 sec_size++;
303 break;
304 case 'x':
305 p++;
306 sec_size = strlen(p) + 1;
307 mark+= _("XDXF data parsing plug-in is not found!");
308 break;
309 case 'k':
310 p++;
311 sec_size = strlen(p) + 1;
312 mark+= _("PowerWord data parsing plug-in is not found!");
313 break;
314 case 'w':
315 p++;
316 sec_size = strlen(p) + 1;
317 mark+= _("Wiki data parsing plug-in is not found!");
318 break;
319 case 'h':
320 p++;
321 sec_size = strlen(p) + 1;
322 mark+= _("HTML data parsing plug-in is not found!");
323 break;
324 case 'n':
325 p++;
326 sec_size = strlen(p) + 1;
327 mark+= _("WordNet data parsing plug-in is not found!");
328 break;
329 case 't':
330 p++;
331 sec_size = strlen(p);
332 if (sec_size) {
333 mark += "[<span foreground=\"blue\">";
334 gchar *m_str = g_markup_escape_text(p, sec_size);
335 mark += m_str;
336 g_free(m_str);
337 mark += "</span>]";
338 }
339 sec_size++;
340 break;
341 case 'y':
342 p++;
343 sec_size = strlen(p);
344 if (sec_size) {
345 mark += "[<span foreground=\"red\">";
346 gchar *m_str = g_markup_escape_text(p, sec_size);
347 mark += m_str;
348 g_free(m_str);
349 mark += "</span>]";
350 }
351 sec_size++;
352 break;
353 case 'r':
354 p++;
355 sec_size = strlen(p);
356 if(sec_size) {
357 append_and_mark_orig_word(mark, real_oword, LinksPosList());
358 mark.clear();
359 append_resource_file_list(p);
360 }
361 sec_size++;
362 break;
363 /*case 'W':
364 {
365 p++;
366 sec_size=g_ntohl(get_uint32(p));
367 //TODO: sound button.
368 sec_size += sizeof(guint32);
369 }
370 break;*/
371 case 'P':
372 {
373 p++;
374 sec_size=g_ntohl(get_uint32(p));
375 if (sec_size) {
376 if (for_float_win) {
377 append_and_mark_orig_word(mark, real_oword, LinksPosList());
378 mark.clear();
379 append_pixbuf(NULL);
380 } else {
381 GdkPixbufLoader* loader = gdk_pixbuf_loader_new();
382 gdk_pixbuf_loader_write(loader, (const guchar *)(p+sizeof(guint32)), sec_size, NULL);
383 gdk_pixbuf_loader_close(loader, NULL);
384 GdkPixbuf* pixbuf = gdk_pixbuf_loader_get_pixbuf(loader);
385 if (pixbuf) {
386 append_and_mark_orig_word(mark, real_oword, LinksPosList());
387 mark.clear();
388 append_pixbuf(pixbuf);
389 } else {
390 mark += _("<span foreground=\"red\">[Load image error!]</span>");
391 }
392 g_object_unref(loader);
393 }
394 } else {
395 mark += _("<span foreground=\"red\">[Missing Image]</span>");
396 }
397 sec_size += sizeof(guint32);
398 }
399 break;
400 default:
401 if (g_ascii_isupper(*p)) {
402 p++;
403 sec_size=g_ntohl(get_uint32(p));
404 sec_size += sizeof(guint32);
405 } else {
406 p++;
407 sec_size = strlen(p)+1;
408 }
409 mark += _("Unknown data type, please upgrade StarDict!");
410 break;
411 }
412 p += sec_size;
413 }
414
415 append_and_mark_orig_word(mark, real_oword, LinksPosList());
416 }
417
AppendNewline()418 void ArticleView::AppendNewline()
419 {
420 append_pango_text("\n");
421 }
422
AppendDataSeparate()423 void ArticleView::AppendDataSeparate()
424 {
425 append_pango_text("<span foreground=\"gray\"><s> </s></span>\n");
426 }
427
SetDictIndex(InstantDictIndex index)428 void ArticleView::SetDictIndex(InstantDictIndex index)
429 {
430 dict_index = index;
431 }
432
AppendHeaderMark()433 void ArticleView::AppendHeaderMark()
434 {
435 if (!for_float_win) {
436 gchar *mark = g_strdup_printf("%d", bookindex);
437 pango_view_->append_mark(mark);
438 g_free(mark);
439 bookindex++;
440 }
441 }
442
AppendHeader(const char * dict_name,const char * dict_link)443 void ArticleView::AppendHeader(const char *dict_name, const char *dict_link)
444 {
445 if (bookname_style == BookNameStyle_Default) {
446 } else if (bookname_style == BookNameStyle_OneBlankLine) {
447 if(++headerindex > 0)
448 append_pango_text("\n");
449 } else {
450 if(++headerindex > 0)
451 append_pango_text("\n\n");
452 }
453 AppendHeaderMark();
454 if (dict_link) {
455 std::string mark;
456 if ((bookname_style == BookNameStyle_Default) || (bookname_style == BookNameStyle_OneBlankLine)) {
457 mark= "<span foreground=\"blue\"><--- <u>";
458 } else {
459 mark= "<span foreground=\"blue\"><u>";
460 }
461 LinksPosList links_list;
462 std::string link(dict_link);
463 links_list.push_back(LinkDesc(5, g_utf8_strlen(dict_name, -1), link));
464 gchar *m_str = g_markup_escape_text(dict_name, -1);
465 mark += m_str;
466 g_free(m_str);
467 if ((bookname_style == BookNameStyle_Default) || (bookname_style == BookNameStyle_OneBlankLine)) {
468 mark += "</u> ---></span>\n";
469 } else {
470 mark += "</u></span>\n";
471 }
472 append_pango_text_with_links(mark, links_list);
473 } else {
474 std::string mark= "<span foreground=\"blue\">";
475 #ifdef CONFIG_GPE
476 if ((bookname_style == BookNameStyle_Default) || (bookname_style == BookNameStyle_OneBlankLine)) {
477 mark+= "<- ";
478 } else {
479 }
480 #else
481 if ((bookname_style == BookNameStyle_Default) || (bookname_style == BookNameStyle_OneBlankLine)) {
482 mark+= "<--- ";
483 } else {
484 }
485 #endif
486 gchar *m_str = g_markup_escape_text(dict_name, -1);
487 mark += m_str;
488 g_free(m_str);
489 #ifdef CONFIG_GPE
490 if ((bookname_style == BookNameStyle_Default) || (bookname_style == BookNameStyle_OneBlankLine)) {
491 mark += " -></span>\n";
492 } else {
493 mark += "</span>\n";
494 }
495 #else
496 if ((bookname_style == BookNameStyle_Default) || (bookname_style == BookNameStyle_OneBlankLine)) {
497 mark += " ---></span>\n";
498 } else {
499 mark += "</span>\n";
500 }
501 #endif
502 append_pango_text(mark.c_str());
503 }
504 }
505
AppendWord(const gchar * word)506 void ArticleView::AppendWord(const gchar *word)
507 {
508 std::string mark;
509 mark += for_float_win ? "<span foreground=\"purple\">" : "<b><span size=\"x-large\">";
510 gchar *m_str = g_markup_escape_text(word, -1);
511 mark += m_str;
512 g_free(m_str);
513 mark += for_float_win ? "</span>\n" : "</span></b>\n";
514 append_pango_text(mark.c_str());
515 }
516
connect_on_link(const sigc::slot<void,const std::string &> & s)517 void ArticleView::connect_on_link(const sigc::slot<void, const std::string &>& s)
518 {
519 pango_view_->on_link_click_.connect(s);
520 }
521
append_resource_file_list(const gchar * p)522 void ArticleView::append_resource_file_list(const gchar *p)
523 {
524 const gchar *b = p, *e;
525 std::string type, key;
526 bool new_line_before = true, new_line_after;
527 bool loaded;
528 while(*b) {
529 e = strchr(b, ':');
530 if(!e) {
531 pango_view_->append_pango_text(
532 _("<span foreground=\"red\">[Resource file list: incorrect format!]</span>"));
533 break;
534 }
535 type.assign(b, e-b);
536 b = e+1;
537 e = strchr(b, '\n');
538 if(!e)
539 e = strchr(b, '\0');
540 key.assign(b, e-b);
541 b = (*e) ? e + 1 : e;
542 loaded = false;
543 new_line_after = false;
544 if(type == "img") {
545 if(!new_line_before)
546 pango_view_->append_text("\n");
547 new_line_after = true;
548 append_data_res_image(key, "", loaded);
549 } else if(type == "snd") {
550 append_data_res_sound(key, "", loaded);
551 } else if(type == "vdo") {
552 append_data_res_video(key, "", loaded);
553 } else if(type == "att") {
554 append_data_res_attachment(key, "", loaded);
555 } else {
556 pango_view_->append_pango_text(
557 _("<span foreground=\"red\">[Resource file list: incorrect format!]</span>"));
558 break;
559 }
560 if(!loaded) {
561 glib::CharStr str(g_markup_escape_text(key.c_str(), -1));
562 std::string mark;
563 mark += "\n<span foreground=\"red\">";
564 mark += get_impl(str);
565 mark += "</span>";
566 pango_view_->append_pango_text(mark.c_str());
567 new_line_after = true;
568 }
569 if(new_line_after)
570 pango_view_->append_text("\n");
571 new_line_before = new_line_after;
572 }
573 }
574
append_data_parse_result(const gchar * real_oword,ParseResult & parse_result)575 void ArticleView::append_data_parse_result(const gchar *real_oword,
576 ParseResult& parse_result)
577 {
578 /* Why ParseResultItem's cannot be inserted into the pango_view_ in the
579 * order they appear in the parse_result list?
580 *
581 * They can, but that limits use of the pango markup language when markup
582 * intermixed with objects that are not expressed in pango markup.
583 * For instance we cannot handle the following piece of data:
584 * markup: "<span foreground=\"purple\">some text"
585 * res: some image
586 * markup: "text continues</span>"
587 * The first markup string cannot be committed because it is not a valid
588 * markup - span tag is not closed. But if we piece two markup strings
589 * together, commit markup and then insert the resource, everything will be
590 * fine.
591 *
592 * Here is an outline of the rules parse_result list must adhere to.
593 *
594 * - each list item with pango markup must contain only complete tags. Tag
595 * may not be opened in one list item and closed in another. For example
596 * this list is not allowed:
597 * markup: "<span foreground=\"purple\" "
598 * markup: "size=\"x-large\">"
599 *
600 * - after combining all list items with pango markup the resultant string
601 * must constitute a valid markup (correct order of tags, each tag must have
602 * a corresponding closing tag and so on). For example, the following text
603 * is not allowed: "<b> bla bla </b><i>text end".
604 * Note: list item may contain incomplete markup like "<b>text begins".
605 *
606 * - Delayed insert items must generate only valid markup. Items
607 * representing images, widgets generate a pango-formated error message
608 * if the primary object cannot be inserted.
609 *
610 *
611 * Position in the pango markup string cannot be exactly specified by
612 * character offset only. That is especially important for LabelPangoWidget
613 * where markup is stored in a string. In order to insert delayed insert
614 * items in the correct place it was decided to add a temporary character
615 * in the string in the place where the delayed item must be inserted.
616 * Temporary characters are deleted at the end.
617 *
618 * In addition to character offsets marks are used to specify position in
619 * the text. Marks must be used instead of char offsets when possible.
620 * Marks are preferred over offsets because they preserve position across
621 * text modifications, they have a notion of right and left gravity.
622 * In general we do not know how many character would be inserted in the
623 * text after the call the method insert_pixbuf. Normally it is only one,
624 * but it may be any number in case of error.
625 *
626 * */
627 Marks marks(pango_view_.get());
628 std::string start_mark = marks.append_mark(true);
629 std::list<ParseResultItemWithMark> delayed_insert_list;
630 // compose markup
631 {
632 std::string markup_str;
633 int char_offset = 0;
634 const char tmp_char = 'x'; // may be any unicode char excluding '<', '&', '>'
635
636 for (std::list<ParseResultItem>::iterator it = parse_result.item_list.begin();
637 it != parse_result.item_list.end(); ++it) {
638 switch (it->type) {
639 case ParseResultItemType_mark:
640 char_offset += xml_utf8_strlen(it->mark->pango.c_str());
641 markup_str += it->mark->pango;
642 break;
643 case ParseResultItemType_link:
644 {
645 /* links do not insert any text, so exact mark position is
646 * not important. */
647 ParseResultItemWithMark item(&*it, char_offset);
648 delayed_insert_list.push_back(item);
649 char_offset += xml_utf8_strlen(it->link->pango.c_str());
650 markup_str += it->link->pango;
651 break;
652 }
653 case ParseResultItemType_res:
654 case ParseResultItemType_widget:
655 {
656 ParseResultItemWithMark item1(NULL, char_offset, true);
657 delayed_insert_list.push_back(item1);
658 char_offset += 1;
659 markup_str += tmp_char;
660 ParseResultItemWithMark item2(&*it, char_offset, true);
661 delayed_insert_list.push_back(item2);
662 char_offset += 1;
663 markup_str += tmp_char;
664 break;
665 }
666 case ParseResultItemType_FormatBeg:
667 case ParseResultItemType_FormatEnd:
668 {
669 /* formats do not insert any text, so exact mark position is
670 * not important. */
671 ParseResultItemWithMark item2(&*it, char_offset);
672 delayed_insert_list.push_back(item2);
673 break;
674 }
675 default:
676 g_warning("Unsupported item type.");
677 break;
678 }
679 }
680 append_and_mark_orig_word(markup_str, real_oword, LinksPosList());
681 markup_str.clear();
682 pango_view_->flush();
683 }
684 // Marks that precede tmp chars. One mark - one char next to it that must be
685 // deleted. Different marks do not refer to the same char.
686 std::list<std::string> tmp_char_mark_list;
687 // insert marks
688 for(std::list<ParseResultItemWithMark>::iterator it = delayed_insert_list.begin();
689 it != delayed_insert_list.end(); ) {
690 it->mark = marks.insert_mark(start_mark, it->char_offset, false);
691 if(it->tmp_char)
692 tmp_char_mark_list.push_back(it->mark);
693 if(it->item)
694 ++it;
695 else
696 it = delayed_insert_list.erase(it);
697 }
698 #ifdef DDEBUG
699 std::cout << "ArticleView::append_data_parse_result. marks inserted."
700 << std::endl;
701 #endif
702 // insert delayed items
703 for(std::list<ParseResultItemWithMark>::iterator it = delayed_insert_list.begin();
704 it != delayed_insert_list.end(); ) {
705 bool EraseCurrent = true;
706 switch(it->item->type) {
707 case ParseResultItemType_link:
708 pango_view_->insert_pango_text_with_links("", it->item->link->links_list,
709 it->mark.c_str());
710 break;
711 case ParseResultItemType_res:
712 {
713 bool loaded = false;
714 if (it->item->res->type == "image") {
715 append_data_res_image(it->item->res->key, it->mark, loaded);
716 } else if (it->item->res->type == "sound") {
717 append_data_res_sound(it->item->res->key, it->mark, loaded);
718 } else if (it->item->res->type == "video") {
719 append_data_res_video(it->item->res->key, it->mark, loaded);
720 } else {
721 append_data_res_attachment(it->item->res->key, it->mark, loaded);
722 }
723 if (!loaded) {
724 std::string tmark;
725 tmark += "<span foreground=\"red\">";
726 glib::CharStr m_str(g_markup_escape_text(it->item->res->key.c_str(), -1));
727 tmark += get_impl(m_str);
728 tmark += "</span>";
729 pango_view_->insert_pango_text(tmark.c_str(), it->mark.c_str());
730 }
731 break;
732 }
733 case ParseResultItemType_widget:
734 pango_view_->insert_widget(it->item->widget->widget, it->mark.c_str());
735 break;
736 case ParseResultItemType_FormatBeg:
737 // change gravity of the mark
738 it->mark = marks.clone_mark(it->mark, true);
739 EraseCurrent = false;
740 break;
741 case ParseResultItemType_FormatEnd:
742 {
743 // find paired ParseResultItemType_FormatBeg item
744 std::list<ParseResultItemWithMark>::reverse_iterator it2(it);
745 for(; it2 != delayed_insert_list.rend(); ++it2)
746 if(it2->item->type == ParseResultItemType_FormatBeg)
747 break;
748 if(it2 != delayed_insert_list.rend() && it2->item->format_beg->type
749 == it->item->format_end->type) {
750 if(it->item->format_end->type == ParseResultItemFormatType_Indent)
751 pango_view_->indent_region(it2->mark.c_str(), 0, it->mark.c_str());
752 std::list<ParseResultItemWithMark>::iterator it3(it2.base());
753 delayed_insert_list.erase(--it3);
754 } else
755 g_warning("Not paired ParseResultItemType_FormatEnd item");
756 break;
757 }
758 default:
759 g_assert_not_reached();
760 break;
761 }
762 if(EraseCurrent)
763 it = delayed_insert_list.erase(it);
764 else
765 ++it;
766 }
767 // remove tmp chars
768 for(std::list<std::string>::iterator it = tmp_char_mark_list.begin();
769 it != tmp_char_mark_list.end(); ++it) {
770 #ifdef DDEBUG
771 std::cout << "tmp char mark " << *it << std::endl;
772 #endif
773 pango_view_->delete_text(it->c_str(), 1, 0);
774 }
775 pango_view_->reindent();
776 if(!delayed_insert_list.empty())
777 g_warning("delayed_insert_list is not empty. "
778 "parse_result contains not paired items.");
779 }
780
append_data_res_image(const std::string & key,const std::string & mark,bool & loaded)781 void ArticleView::append_data_res_image(
782 const std::string& key, const std::string& mark,
783 bool& loaded)
784 {
785 if (for_float_win) {
786 loaded = true;
787 if(mark.empty())
788 pango_view_->append_pixbuf(NULL, key.c_str());
789 else
790 pango_view_->insert_pixbuf(NULL, key.c_str(), mark.c_str());
791 } else {
792 GdkPixbuf* pixbuf = NULL;
793 if (dict_index.type == InstantDictType_LOCAL) {
794 StorageType type = gpAppFrame->oLibs.GetStorageType(dict_index.index);
795 if (type == StorageType_DATABASE || type == StorageType_FILE) {
796 if(const char* content = gpAppFrame->oLibs.GetStorageFileContent
797 (dict_index.index, key)) {
798 const guint32 size = get_uint32(content);
799 const guchar *data = (const guchar *)(content+sizeof(guint32));
800 GdkPixbufLoader* loader = gdk_pixbuf_loader_new();
801 gdk_pixbuf_loader_write(loader, data, size, NULL);
802 gdk_pixbuf_loader_close(loader, NULL);
803 pixbuf = gdk_pixbuf_loader_get_pixbuf(loader);
804 if(pixbuf)
805 g_object_ref(G_OBJECT(pixbuf));
806 g_object_unref(loader);
807 }
808 }
809 }
810 if (pixbuf) {
811 loaded = true;
812 if(mark.empty())
813 pango_view_->append_pixbuf(pixbuf, key.c_str());
814 else
815 pango_view_->insert_pixbuf(pixbuf, key.c_str(), mark.c_str());
816 g_object_unref(pixbuf);
817 }
818 }
819 }
820
append_data_res_sound(const std::string & key,const std::string & mark,bool & loaded)821 void ArticleView::append_data_res_sound(
822 const std::string& key, const std::string& mark,
823 bool& loaded)
824 {
825 if (for_float_win) {
826 loaded = true;
827 if(mark.empty())
828 pango_view_->append_widget(NULL);
829 else
830 pango_view_->insert_widget(NULL, mark.c_str());
831 } else {
832 GtkWidget *widget = NULL;
833 if (dict_index.type == InstantDictType_LOCAL) {
834 StorageType type = gpAppFrame->oLibs.GetStorageType(dict_index.index);
835 if (type == StorageType_DATABASE || type == StorageType_FILE) {
836 ResData *pResData
837 = new ResData(dict_index.index, key);
838 GtkWidget *button = NULL;
839 GtkWidget *image = NULL;
840 image = gtk_image_new_from_pixbuf(get_impl(
841 gpAppFrame->oAppSkin.pronounce));
842 button = gtk_button_new();
843 /* We need an event box to associate a custom cursor with the
844 * button. */
845 widget = gtk_event_box_new();
846 g_object_ref_sink(G_OBJECT(widget));
847 gtk_container_add(GTK_CONTAINER(button), image);
848 gtk_container_add(GTK_CONTAINER(widget), button);
849 #ifdef DEBUG
850 /* when the tooltip appears a number of gtk-warnings are generated:
851 * Gtk-WARNING **: IA__gtk_text_view_window_to_buffer_coords:
852 * can't get coords for private windows */
853 gtk_widget_set_tooltip_text(GTK_WIDGET(widget),
854 pResData->get_key().c_str());
855 #endif
856 gtk_event_box_set_above_child(GTK_EVENT_BOX(widget), FALSE);
857 gtk_widget_show_all(widget);
858 g_signal_connect(G_OBJECT(button), "destroy",
859 G_CALLBACK(on_resource_button_destroy), (gpointer)pResData);
860 g_signal_connect(G_OBJECT(button), "clicked",
861 G_CALLBACK(on_sound_button_clicked), (gpointer)pResData);
862 g_signal_connect(G_OBJECT(button), "realize",
863 G_CALLBACK(on_resource_button_realize), (gpointer)widget);
864 }
865 }
866 if(widget) {
867 loaded = true;
868 if(mark.empty())
869 pango_view_->append_widget(widget);
870 else
871 pango_view_->insert_widget(widget, mark.c_str());
872 g_object_unref(widget);
873 }
874 }
875 }
876
append_data_res_video(const std::string & key,const std::string & mark,bool & loaded)877 void ArticleView::append_data_res_video(
878 const std::string& key, const std::string& mark,
879 bool& loaded)
880 {
881 if (for_float_win) {
882 loaded = true;
883 if(mark.empty())
884 pango_view_->append_widget(NULL);
885 else
886 pango_view_->insert_widget(NULL, mark.c_str());
887 } else {
888 GtkWidget *widget = NULL;
889 if (dict_index.type == InstantDictType_LOCAL) {
890 StorageType type = gpAppFrame->oLibs.GetStorageType(dict_index.index);
891 if (type == StorageType_DATABASE || type == StorageType_FILE) {
892 ResData *pResData
893 = new ResData(dict_index.index, key);
894 GtkWidget *button = NULL;
895 GtkWidget *image = NULL;
896 image = gtk_image_new_from_pixbuf(get_impl(
897 gpAppFrame->oAppSkin.video));
898 button = gtk_button_new();
899 /* We need an event box to associate a custom cursor with the
900 * button. */
901 widget = gtk_event_box_new();
902 g_object_ref_sink(G_OBJECT(widget));
903 gtk_container_add(GTK_CONTAINER(button), image);
904 gtk_container_add(GTK_CONTAINER(widget), button);
905 #ifdef DEBUG
906 /* when the tooltip appears a number of gtk-warnings are generated:
907 * Gtk-WARNING **: IA__gtk_text_view_window_to_buffer_coords:
908 * can't get coords for private windows */
909 gtk_widget_set_tooltip_text(GTK_WIDGET(widget),
910 pResData->get_key().c_str());
911 #endif
912 gtk_event_box_set_above_child(GTK_EVENT_BOX(widget), FALSE);
913 gtk_widget_show_all(widget);
914 g_signal_connect(G_OBJECT(button), "destroy",
915 G_CALLBACK(on_resource_button_destroy), (gpointer)pResData);
916 g_signal_connect(G_OBJECT(button), "clicked",
917 G_CALLBACK(on_video_button_clicked), (gpointer)pResData);
918 g_signal_connect(G_OBJECT(button), "realize",
919 G_CALLBACK(on_resource_button_realize), (gpointer)widget);
920 }
921 }
922 if(widget) {
923 loaded = true;
924 if(mark.empty())
925 pango_view_->append_widget(widget);
926 else
927 pango_view_->insert_widget(widget, mark.c_str());
928 g_object_unref(widget);
929 }
930 }
931 }
932
append_data_res_attachment(const std::string & key,const std::string & mark,bool & loaded)933 void ArticleView::append_data_res_attachment(
934 const std::string& key, const std::string& mark,
935 bool& loaded)
936 {
937 if (for_float_win) {
938 loaded = true;
939 if(mark.empty())
940 pango_view_->append_widget(NULL);
941 else
942 pango_view_->insert_widget(NULL, mark.c_str());
943 } else {
944 GtkWidget *widget = NULL;
945 if (dict_index.type == InstantDictType_LOCAL) {
946 StorageType type = gpAppFrame->oLibs.GetStorageType(dict_index.index);
947 if (type == StorageType_DATABASE || type == StorageType_FILE) {
948 ResData *pResData
949 = new ResData(dict_index.index, key);
950 GtkWidget *button = NULL;
951 GtkWidget *image = NULL;
952 image = gtk_image_new_from_pixbuf(get_impl(
953 gpAppFrame->oAppSkin.attachment));
954 button = gtk_button_new();
955 /* We need an event box to associate a custom cursor with the
956 * button. */
957 widget = gtk_event_box_new();
958 g_object_ref_sink(G_OBJECT(widget));
959 gtk_container_add(GTK_CONTAINER(button), image);
960 gtk_container_add(GTK_CONTAINER(widget), button);
961 #ifdef DEBUG
962 /* when the tooltip appears a number of gtk-warnings are generated:
963 * Gtk-WARNING **: IA__gtk_text_view_window_to_buffer_coords:
964 * can't get coords for private windows */
965 gtk_widget_set_tooltip_text(GTK_WIDGET(widget),
966 pResData->get_key().c_str());
967 #endif
968 gtk_event_box_set_above_child(GTK_EVENT_BOX(widget), FALSE);
969 gtk_widget_show_all(widget);
970 g_signal_connect(G_OBJECT(button), "destroy",
971 G_CALLBACK(on_resource_button_destroy), (gpointer)pResData);
972 g_signal_connect(G_OBJECT(button), "clicked",
973 G_CALLBACK(on_attachment_button_clicked), (gpointer)pResData);
974 g_signal_connect(G_OBJECT(button), "realize",
975 G_CALLBACK(on_resource_button_realize), (gpointer)widget);
976 }
977 }
978 if(widget) {
979 loaded = true;
980 if(mark.empty())
981 pango_view_->append_widget(widget);
982 else
983 pango_view_->insert_widget(widget, mark.c_str());
984 g_object_unref(widget);
985 }
986 }
987 }
988
on_resource_button_destroy(GtkWidget * object,gpointer user_data)989 void ArticleView::on_resource_button_destroy(GtkWidget *object, gpointer user_data)
990 {
991 delete (ResData*)user_data;
992 }
993
on_sound_button_clicked(GtkWidget * object,gpointer user_data)994 void ArticleView::on_sound_button_clicked(GtkWidget *object, gpointer user_data)
995 {
996 ResData *pResData = (ResData*)user_data;
997 if(const char *filename = pResData->get_url())
998 play_sound_file(filename);
999 else
1000 g_warning("Unable to load resource: %s", pResData->get_key().c_str());
1001 }
1002
on_video_button_clicked(GtkWidget * object,gpointer user_data)1003 void ArticleView::on_video_button_clicked(GtkWidget *object, gpointer user_data)
1004 {
1005 ResData *pResData = (ResData*)user_data;
1006 if(const char *filename = pResData->get_url())
1007 play_video_file(filename);
1008 else
1009 g_warning("Unable to load resource: %s", pResData->get_key().c_str());
1010 }
1011
on_attachment_button_clicked(GtkWidget * object,gpointer user_data)1012 void ArticleView::on_attachment_button_clicked(GtkWidget *object, gpointer user_data)
1013 {
1014 ResData *pResData = (ResData*)user_data;
1015 const std::string& key = pResData->get_key();
1016 std::string::size_type pos = key.rfind(DB_DIR_SEPARATOR);
1017 // in utf-8, yet a file name
1018 const char* default_file_name = key.c_str() + (pos == std::string::npos ? 0 : pos+1);
1019 GtkWidget *dialog;
1020 dialog = gtk_file_chooser_dialog_new(_("Save File"),
1021 GTK_WINDOW(gpAppFrame->window),
1022 GTK_FILE_CHOOSER_ACTION_SAVE,
1023 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1024 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
1025 NULL);
1026 gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
1027 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog),
1028 gpAppFrame->last_selected_directory.c_str());
1029 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), default_file_name);
1030 if(gtk_dialog_run(GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) {
1031 glib::CharStr filename(gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)));
1032 glib::CharStr selected_dir(g_path_get_dirname(get_impl(filename)));
1033 gpAppFrame->last_selected_directory = get_impl(selected_dir);
1034 if(const char* content = pResData->get_content()) {
1035 const guint32 size = get_uint32(content);
1036 const gchar *data = (const gchar *)(content+sizeof(guint32));
1037 if(!g_file_set_contents(get_impl(filename), data, size, NULL))
1038 g_warning("Fail to save file %s", get_impl(filename));
1039 } else {
1040 g_warning("Unable to load resource: %s", pResData->get_key().c_str());
1041 }
1042 }
1043 gtk_widget_destroy(dialog);
1044 }
1045
on_resource_button_realize(GtkWidget * object,gpointer user_data)1046 void ArticleView::on_resource_button_realize(GtkWidget *object, gpointer user_data)
1047 {
1048 /* Event boxes are not automatically realized by GTK+, they must be realized
1049 * explicitly. You need to make sure that an event box is already added as
1050 * a child to a top-level widget before realizing it. A child realize event
1051 * handler is a good place for that (imho). */
1052 GtkWidget *eventbox = GTK_WIDGET(user_data);
1053 gtk_widget_realize(eventbox);
1054 GdkCursor *cursor = gdk_cursor_new(GDK_HAND2);
1055 gdk_window_set_cursor(gtk_widget_get_window(eventbox), cursor);
1056 #if GTK_MAJOR_VERSION >= 3
1057 g_object_unref(cursor);
1058 #else
1059 gdk_cursor_unref(cursor);
1060 #endif
1061 }
1062