OSDN Git Service

Glyph widths are treated as double.
[fukui-no-namari/fukui-no-namari.git] / src / FukuiNoNamari / thread_view.py
1 # Copyright (C) 2007 by Aiwota Programmer
2 # aiwotaprog@tetteke.tk
3 #
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 2 of the License, or
7 # (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17
18 import pygtk
19 pygtk.require('2.0')
20 import gtk
21 import pango
22 import gobject
23 from FukuiNoNamariExt import thread_view_extend
24
25
26 def get_approximate_char_height(pango_context):
27     desc = pango_context.get_font_description()
28     font = pango_context.load_font(desc)
29     ink, log = font.get_glyph_extents(0)
30     return log[3] / pango.SCALE + 2
31
32
33 class Rectangle:
34     def __init__(self, x, y, width, height):
35         self.x = x
36         self.y = y
37         self.width = width
38         self.height = height
39
40
41 class Line:
42
43     HEIGHT = 15
44
45     def __init__(self, start_index, end_index, rectangle):
46         self.start_index = start_index
47         self.end_index = end_index
48         self.rectangle = rectangle
49
50     def is_on_xy(self, x, y):
51         left = self.rectangle.x
52         right = left + self.rectangle.width
53         top = self.rectangle.y
54         bottom = top + self.rectangle.height
55         return x >= left and x < right and y >= top and y < bottom
56         
57
58 class ElementEmpty:
59
60     def __init__(self, pango_layout):
61         self.pango_layout = pango_layout
62         self.initialize()
63
64     def recalc_char_widths(self):
65         pass
66         
67     def initialize(self):
68         self.line_list = []
69
70     def is_on_xy(self, x, y):
71         for line in self.line_list:
72             if line.is_on_xy(x, y):
73                 return True
74         return False
75
76     def xy_to_index(self, x, y):
77         return 0
78
79     def build_line_list(self, x, y, width, left_margin):
80         self.initialize()
81
82         line = Line(0, 0, Rectangle(
83             x, y, width - x,
84             get_approximate_char_height(self.pango_layout.get_context())))
85         self.line_list.append(line)
86
87         return width, y
88
89     def get_text(self, selection=False, start_index=0, end_index=0xffffff):
90         return ""
91
92     def draw(self, drawingarea, y_offset, pango_layout,
93              selection=False, start_index=0, end_index=0xffffff):
94         pass
95
96     
97 class ElementText:
98
99     def __init__(self, text, pango_layout):
100         self.text = text
101         self.pango_layout = pango_layout
102
103         attrlist = self._get_attrs()
104         self.widths = thread_view_extend.get_char_width(
105             pango_layout.get_context(), text, attrlist)
106
107         self.line_list = []
108
109     def recalc_char_widths(self):
110         attrlist = self._get_attrs()
111         self.widths = thread_view_extend.get_char_width(
112             self.pango_layout.get_context(), self.text, attrlist)
113
114     def _get_attrs(self):
115         attrs = pango.AttrList()
116         return attrs
117
118     def is_on_xy(self, x, y):
119         for line in self.line_list:
120             if line.is_on_xy(x, y):
121                 return True
122         return False
123
124     def xy_to_index(self, x, y):
125         for line in self.line_list:
126             top = line.rectangle.y
127             bottom = top + line.rectangle.height
128             if y >= top and y < bottom:
129                 sum_of_widths = line.rectangle.x
130                 index = line.start_index
131                 for width in self.widths[line.start_index:line.end_index]:
132                     if sum_of_widths + width/2 > x:
133                         break
134                     sum_of_widths += width
135                     index += 1
136                 return index
137         
138     def build_line_list(self, x, y, width, left_margin):
139         self.line_list = []
140
141         current_line_start_index = 0
142         current_line_x = x
143         current_line_y = y
144         current_line_width = 0
145
146         ch_h = get_approximate_char_height(self.pango_layout.get_context())
147
148         for index, ch in enumerate(self.text):
149             ch_w = self.widths[index]
150             if current_line_x + current_line_width + ch_w > width:
151                 line = Line(
152                     current_line_start_index, index,
153                     Rectangle(
154                     current_line_x, current_line_y,
155                     current_line_width, ch_h))
156                 self.line_list.append(line)
157
158                 current_line_start_index = index
159                 current_line_x = left_margin
160                 current_line_y += ch_h
161                 current_line_width = ch_w
162             else:
163                 current_line_width += ch_w
164
165         if current_line_start_index < len(self.text):
166             line = Line(current_line_start_index, len(self.text),
167                         Rectangle(current_line_x,
168                                           current_line_y,
169                                           current_line_width,
170                                           ch_h))
171             self.line_list.append(line)
172
173             current_line_x += current_line_width
174
175         return current_line_x, current_line_y
176
177     def get_text(self, selection=False, start_index=0, end_index=0xffffff):
178
179         text = ""
180
181         for line in self.line_list:
182
183             t = self.text[line.start_index:line.end_index]
184             if selection:
185                 s = start_index - line.start_index
186                 s = max(s, 0)
187                 s = min(s, line.end_index - line.start_index)
188
189                 e = end_index - line.start_index
190                 e = min(e, line.end_index - line.start_index)
191                 e = max(e, 0)
192
193                 t = t[s:e]
194
195             text += t
196
197         return text
198
199     def draw(self, drawingarea, y_offset, pango_layout,
200              selection=False, start_index=0, end_index=0xffffff):
201
202         selection_fg = drawingarea.style.fg[3]
203         selection_bg = drawingarea.style.bg[3]
204
205         for line in self.line_list:
206
207             text = self.text[line.start_index:line.end_index]
208             u_text = text.encode("utf8")
209             gc = drawingarea.window.new_gc()
210             attrs = self._get_attrs()
211             if selection:
212
213                 s = start_index - line.start_index
214                 s = max(s, 0)
215                 s = min(s, line.end_index - line.start_index)
216                 s = len(text[:s].encode("utf8"))
217
218                 e = end_index - line.start_index
219                 e = min(e, line.end_index - line.start_index)
220                 e = max(e, 0)
221                 e = len(text[:e].encode("utf8"))
222                     
223                 selection_all_attr_fg = pango.AttrForeground(
224                     selection_fg.red, selection_fg.green, selection_fg.blue,
225                     s, e)
226                 selection_all_attr_bg= pango.AttrBackground(
227                     selection_bg.red, selection_bg.green, selection_bg.blue,
228                     s, e)
229                 attrs.insert(selection_all_attr_fg)
230                 attrs.insert(selection_all_attr_bg)
231
232             pango_layout.set_text(u_text)
233             pango_layout.set_attributes(attrs)
234             drawingarea.window.draw_layout(
235                 gc, int(line.rectangle.x), line.rectangle.y + y_offset,
236                 pango_layout)
237
238
239 class ElementBoldText(ElementText):
240
241     def _get_attrs(self):
242         attrlist = pango.AttrList()
243         attr = pango.AttrWeight(pango.WEIGHT_BOLD,
244                                    end_index=0xffffff)
245         attrlist.insert(attr)
246         return attrlist
247
248
249 class ElementLink(ElementText):
250     
251     def __init__(self, text, href, pango_layout):
252         self.href = href
253         ElementText.__init__(self, text, pango_layout)
254
255     def _get_attrs(self):
256         attrlist = pango.AttrList()
257         attr = pango.AttrUnderline(pango.UNDERLINE_SINGLE,
258                                    end_index=0xffffff)
259         attrlist.insert(attr)
260         return attrlist
261
262
263 class ResLayout:
264 # represent one line
265
266     def __init__(self, left_margin, resnum, pango_layout):
267         self.element_list = [ElementEmpty(pango_layout)]
268         self.width = 0
269         self.height = 0
270         self.pango_layout = pango_layout
271         self.left_margin = left_margin
272         self.resnum = resnum
273         self.posY = 0
274         self.list_index = 0
275
276     def add_text(self, text, bold, href):
277         if isinstance(self.element_list[0], ElementEmpty):
278             self.element_list = []
279
280         if href:
281             element = ElementLink(text, href, self.pango_layout)
282             self.element_list.append(element)
283         elif bold:
284             element = ElementBoldText(text, self.pango_layout)
285             self.element_list.append(element)
286         else:
287             element = ElementText(text, self.pango_layout)
288             self.element_list.append(element)
289
290     def get_element_from_xy(self, x, y):
291         for element in self.element_list:
292             if element.is_on_xy(x, y):
293                 return element
294         return None
295
296     def get_close_element_from_xy(self, x, y):
297         x= max(x, self.left_margin)
298         element = self.get_element_from_xy(x, y)
299         if element is None and len(self.element_list) != 0:
300             element = self.element_list[len(self.element_list) - 1]
301         return element
302
303     def recalc_char_widths(self):
304         for element in self.element_list:
305             element.recalc_char_widths()
306
307     def set_width(self, width):
308
309         self.width = width
310         
311         current_x = self.left_margin
312         current_y = 0
313
314         for element in self.element_list:
315             current_x, current_y = element.build_line_list(
316                 current_x, current_y, width, self.left_margin)
317
318         self.height = current_y + get_approximate_char_height(self.pango_layout.get_context())
319
320     def get_pixel_size(self):
321         return self.width, self.height
322
323     def get_text(self, selection_start, selection_end):
324         s_s = selection_start
325         e_s = selection_end
326         s_l, s_e, s_i = selection_start
327         e_l, e_e, e_i = selection_end
328
329         text = ""
330
331         if (s_l is None or s_e is None or s_i is None or
332             e_l is None or e_e is None or e_i is None or
333             self.posY < s_l.posY or self.posY > e_l.posY):
334
335             # nothing to do
336             pass
337
338         elif self.posY > s_s[0].posY and self.posY < e_s[0].posY:
339
340             for element in self.element_list:
341                 text += element.get_text(selection=True)
342
343         elif self == s_s[0] and self == e_s[0]:
344
345             selection = False
346
347             for element in self.element_list:
348                 if s_e == element:
349                     selection = True
350                     start = s_i
351                     end = 0xffffff
352                     if e_e == element:
353                         end = e_i
354                         selection = False
355                     text += element.get_text(selection=True, start_index=start,
356                                              end_index=end)
357                 elif e_e == element:
358                     end = e_i
359                     selection = False
360                     text += element.get_text(
361                         selection=True, end_index=end)
362                 elif selection:
363                     text += element.get_text(selection=True)
364
365         elif self == s_s[0]:
366
367             selection = False
368
369             for element in self.element_list:
370                 if s_e == element:
371                     selection = True
372                     start = s_i
373                     text += element.get_text(selection=True, start_index=start)
374                 elif selection:
375                     text += element.get_text(selection=True)
376
377         elif self == e_s[0]:
378
379             selection = True
380
381             for element in self.element_list:
382                 if e_e == element:
383                     end = e_i
384                     text += element.get_text(selection=True, end_index=e_i)
385                     selection = False
386                 elif selection:
387                     text += element.get_text(selection=True)
388
389         else:
390             # nothing to do
391             pass
392
393         return text
394
395
396     def draw(self, drawingarea, x_offset, y_offset,
397              start_selection, end_selection):
398
399         s_s = start_selection
400         e_s = end_selection
401
402         s_l, s_e, s_i = s_s
403         e_l, e_e, e_i = e_s
404
405         if (s_l is None or s_e is None or s_i is None or
406             e_l is None or e_e is None or e_i is None or
407             self.posY < s_l.posY or self.posY > e_l.posY):
408
409             for element in self.element_list:
410                 element.draw(drawingarea, y_offset, self.pango_layout)
411
412         elif self.posY > s_s[0].posY and self.posY < e_s[0].posY:
413
414             for element in self.element_list:
415                 element.draw(drawingarea, y_offset, self.pango_layout,
416                              selection=True)
417
418         elif self == s_s[0] and self == e_s[0]:
419
420             selection = False
421
422             for element in self.element_list:
423                 if s_e == element:
424                     selection = True
425                     start = s_i
426                     end = 0xffffff
427                     if e_e == element:
428                         end = e_i
429                         selection = False
430                     element.draw(drawingarea, y_offset, self.pango_layout,
431                                  selection=True,
432                                  start_index=start,
433                                  end_index=end)
434                 elif e_e == element:
435                     end = e_i
436                     selection = False
437                     element.draw(drawingarea, y_offset, self.pango_layout,
438                                  selection=True, end_index=end)
439                 else:
440                     element.draw(drawingarea, y_offset, self.pango_layout,
441                                  selection=selection)
442
443         elif self == s_s[0]:
444
445             selection = False
446
447             for element in self.element_list:
448                 if s_e == element:
449                     selection = True
450                     start = s_i
451                     element.draw(drawingarea, y_offset, self.pango_layout,
452                                  selection=selection, start_index = start)
453                 else:
454                     element.draw(drawingarea, y_offset, self.pango_layout,
455                                  selection=selection)
456
457         elif self == e_s[0]:
458
459             selection = True
460
461             for element in self.element_list:
462                 if e_e == element:
463                     end = e_i
464                     element.draw(drawingarea, y_offset, self.pango_layout,
465                                  selection=selection, end_index=e_i)
466                     selection = False
467                 else:
468                     element.draw(drawingarea, y_offset, self.pango_layout,
469                                  selection=selection)
470
471         else:
472             for element in self.element_list:
473                 element.draw(drawingarea, y_offset, self.pango_layout)
474
475     def clone(self):
476         import copy
477         layout = ResLayout(self.left_margin, self.resnum, self.pango_layout)
478         layout.element_list = []
479         for element in self.element_list:
480             layout.element_list.append(copy.copy(element))
481         return layout
482
483
484 class ThreadView(gtk.HBox):
485     __gsignals__ = {
486         "cursor-over-link-event":
487         (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (object, object, )),
488         "uri-clicked-event":
489         (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (object, ))
490         }
491
492     hand_cursor = gtk.gdk.Cursor(gtk.gdk.HAND2)
493     regular_cursor = gtk.gdk.Cursor(gtk.gdk.XTERM)
494     arrow_cursor = gtk.gdk.Cursor(gtk.gdk.LEFT_PTR)
495
496     def __init__(self):
497         gtk.HBox.__init__(self, False, 0)
498         self.drawingarea = gtk.DrawingArea()
499         self.drawingarea.set_property("can_focus", True)
500
501         self.vscrollbar = gtk.VScrollbar()
502         self.pack_start(self.drawingarea)
503         self.pack_start(self.vscrollbar, expand=False)
504         self.adjustment  = self.vscrollbar.get_adjustment()
505
506         self.drawingarea.add_events(
507             gtk.gdk.KEY_PRESS_MASK |
508             gtk.gdk.SCROLL_MASK |
509             gtk.gdk.POINTER_MOTION_MASK |
510             gtk.gdk.BUTTON_PRESS_MASK |
511             gtk.gdk.BUTTON_RELEASE_MASK)
512
513         self.drawingarea_prev_width = 0
514
515         self.drawingarea.connect(
516             "expose-event", self.on_drawingarea_expose_event)
517         self.drawingarea.connect(
518             "configure-event", self.on_drawingarea_configure_event)
519         self.drawingarea.connect(
520             "scroll-event", self.on_drawingarea_scroll_event)
521         self.drawingarea.connect(
522             "motion-notify-event", self.on_drawingrarea_motion_notify_event)
523         self.drawingarea.connect(
524             "button-press-event", self.on_drawingarea_button_press_event)
525         self.drawingarea.connect(
526             "button-release-event", self.on_drawingarea_button_release_event)
527         self.drawingarea.connect(
528             "style-set", self.on_drawingarea_style_set)
529         self.drawingarea.connect(
530             "key-press-event", self.on_drawingarea_key_press_event)
531         self.vscrollbar.connect(
532             "value-changed", self.on_vscrollbar_value_changed)
533
534         self.pango_layout = self.drawingarea.create_pango_layout("")
535
536         self.initialize_buffer()
537
538         self.button1_pressed = False
539         self.current_pressed_uri = None
540             
541         self.popupmenu = None
542         self.menu_openuri = None
543         self.menu_copylinkaddress = None
544         self.menu_separator_link = None
545         self.menu_copyselection = None
546         self.menu_openasuri = None
547         self.menu_separator_selection = None
548
549         self.menud_uri = None
550
551         # for selection
552         self.button_pressed_pt = (None, None, None)
553         self.button_moving_pt = (None, None, None)
554
555     def initialize_buffer(self):
556         self.res_layout_list = []
557
558     def add_layout(self, res_layout):
559         if (len(self.res_layout_list) != 0):
560             last = self.res_layout_list[len(self.res_layout_list)-1]
561             x, y = last.get_pixel_size()
562             res_layout.posY = last.posY + y
563         self.set_layout_width(res_layout)
564         res_layout.list_index = len(self.res_layout_list)
565         self.res_layout_list.append(res_layout)
566
567         x, y = res_layout.get_pixel_size()
568         self.adjustment.upper = res_layout.posY + y
569         # do not use this method in a loop because expensive.
570         # self.redraw()
571         self.change_vscrollbar_visible()
572
573     def create_res_layout(self, left_margin, resnum):
574         return ResLayout(left_margin, resnum, self.pango_layout)
575
576     def set_layout_width(self, layout):
577         width = self.drawingarea.allocation.width
578         layout.set_width(width)
579         
580     def redraw(self):
581         self.drawingarea.queue_draw()
582
583     def wrap_relayout(self):
584         # before relayout, find top layout on gdkwindow
585         top_layout = self.get_layout_on_y(self.adjustment.value)
586         delta = 0
587
588         if top_layout is not None:
589             delta = top_layout.posY - self.vscrollbar.get_value()
590
591         self.relayout()
592         self.drawingarea_prev_width = self.drawingarea.allocation.width
593
594         # after relayout, set vscrollbar.value to top layout's posY
595         if top_layout is not None:
596             self.vscrollbar.set_value(top_layout.posY - delta)
597
598     def relayout(self):
599         width = self.drawingarea.allocation.width
600         sum_height = 0
601         for layout in self.res_layout_list:
602             layout.set_width(width)
603             layout.posY = sum_height
604             x, y = layout.get_pixel_size()
605             sum_height += y
606
607         self.vscrollbar.set_range(0, sum_height)
608         self.change_vscrollbar_visible()
609
610     def change_vscrollbar_visible(self):
611         if self.adjustment.upper <= self.adjustment.page_size:
612             self.vscrollbar.hide()
613         else:
614             self.vscrollbar.show()
615
616     def jump(self, value):
617         self.vscrollbar.set_value(value)
618
619     def jump_to_layout(self, layout):
620         self.jump(layout.posY)
621         
622     def jump_to_the_end(self):
623         value = self.adjustment.upper - self.adjustment.page_size
624         self.vscrollbar.set_value(value)
625
626     def jump_to_res(self, resnum):
627         for layout in self.res_layout_list:
628             if layout.resnum == resnum:
629                 self.jump_to_layout(layout)
630                 return True
631         return False
632
633
634     def _get_selection_start_end(self):
635         pressed_layout, pressed_element, pressed_index = self.button_pressed_pt
636         moving_layout, moving_element, moving_index = self.button_moving_pt
637
638         if (pressed_layout is None or pressed_element is None or
639             pressed_index is None or moving_layout is None or
640             moving_element is None or moving_index is None):
641             return (None, None, None), (None, None, None)
642         
643         if pressed_layout == moving_layout:
644             if pressed_element == moving_element:
645                 if moving_index < pressed_index:
646                     return self.button_moving_pt, self.button_pressed_pt
647             else:
648                 pressed_element_index = pressed_layout.element_list.index(
649                     pressed_element)
650                 moving_element_index = moving_layout.element_list.index(
651                     moving_element)
652                 if moving_element_index < pressed_element_index:
653                     return self.button_moving_pt, self.button_pressed_pt
654         elif moving_layout.posY < pressed_layout.posY:
655             return self.button_moving_pt, self.button_pressed_pt
656
657         return self.button_pressed_pt, self.button_moving_pt
658
659     def draw_viewport(self, area):
660         view_y = self.vscrollbar.get_value()
661         self.drawingarea.window.draw_rectangle(
662             self.drawingarea.style.base_gc[0],
663             True, area.x, area.y, area.width, area.height)
664
665         selection_start, selection_end = self._get_selection_start_end()
666
667
668         top_layout = self.get_layout_on_y(view_y)
669         index = 0
670         if top_layout:
671             index = top_layout.list_index
672         while index < len(self.res_layout_list):
673             layout = self.res_layout_list[index]
674             w, h = layout.get_pixel_size()
675             layout_top = layout.posY
676             layout_bottom = layout.posY + h
677             area_top = view_y + area.y
678             area_bottom = view_y + area.y + area.height
679             if layout_top <= area_bottom and layout_bottom >= area_top:
680                 layout.draw(self.drawingarea,
681                             0, layout.posY - int(view_y),
682                             selection_start, selection_end)
683             if layout_top > area_bottom:
684                 break
685
686             index += 1
687
688     def transform_coordinate_gdk_to_adj(self, y):
689         return y + self.vscrollbar.get_value()
690
691     def transform_coordinate_adj_to_layout(self, x, y, layout):
692         return x, y - layout.posY
693
694     def transform_coordinate_gdk_to_layout(self, x, y, layout):
695         return self.transform_coordinate_adj_to_layout(
696             x, self.transform_coordinate_gdk_to_adj(y), layout)
697
698     def get_layout_on_y(self, y):
699
700         def binary_search(lst, start, end, func):
701
702             if end - start <= 0:
703                 return None
704
705             m = (start + end) / 2
706             ret = func(lst[m])
707
708             if ret == 0:
709                 return m
710             if ret > 0:
711                 return binary_search(lst, start, m, func)
712             return binary_search(lst, m+1, end, func)
713
714         def on_y(layout, _y):
715             top = layout.posY
716             width, height = layout.get_pixel_size()
717             bottom = top + height
718             if _y >= top and _y < bottom:
719                 return 0
720             if _y < top:
721                 return 1
722             return -1
723
724         ret = binary_search(
725             self.res_layout_list, 0, len(self.res_layout_list),
726             lambda x: on_y(x, y))
727         if ret is not None:
728             return self.res_layout_list[ret]
729         return None
730
731     def ptrpos_to_layout(self, x, y):
732         # transform coordinate, GdkWindow -> adjustment
733         adj_y = self.transform_coordinate_gdk_to_adj(y)
734         return self.get_layout_on_y(adj_y)
735
736     def ptrpos_to_uri(self,  x, y):
737         # x, y is GdkWindow coordinate
738
739         layout = self.ptrpos_to_layout(x, y)
740
741         if layout is None:
742             return None, None, None
743
744         # transform coordinate, GdkWindow -> res_layout_list
745         lay_x, lay_y = self.transform_coordinate_gdk_to_layout(x, y, layout)
746
747         # xy -> element
748         element = layout.get_element_from_xy(lay_x, lay_y)
749         if isinstance(element, ElementLink):
750             return element.href, layout, element
751
752         return None, layout, None
753
754     def get_selected_text(self):
755         selection_start, selection_end = self._get_selection_start_end()
756         s_l, s_e, s_i = selection_start
757         e_l, e_e, e_i = selection_end
758
759         if (s_l is None or s_e is None or s_i is None or
760             e_l is None or e_e is None or e_i is None):
761             return ""
762
763         text = ""
764         index = s_l.list_index
765         end = e_l.list_index
766
767         while index <= end:
768             layout = self.res_layout_list[index]
769
770             text += layout.get_text(selection_start, selection_end)
771             if index != end:
772                 text += "\n"
773
774             index += 1
775
776         return text
777
778     def _set_button_pressed_pt(self, pt):
779         self.button_pressed_pt = (None, None, None)
780         if pt == None:
781             return
782
783         x, y = pt
784         layout = self.ptrpos_to_layout(x, y)
785         if layout is None:
786             return
787
788         x, y = self.transform_coordinate_gdk_to_layout(x, y, layout)
789         element = layout.get_element_from_xy(x, y)
790         if element is None:
791             element = layout.get_close_element_from_xy(x, y)
792
793         if element is None:
794             return
795
796         index = element.xy_to_index(x, y)
797         if index is None:
798             return
799
800         self.button_pressed_pt = (layout, element, index)
801
802     def _set_button_moving_pt(self, pt):
803         self.button_moving_pt = (None, None, None)
804         if pt == None:
805             return
806
807         x, y = pt
808         layout = self.ptrpos_to_layout(x, y)
809         if layout is None:
810             return
811
812         x, y = self.transform_coordinate_gdk_to_layout(x, y, layout)
813         element = layout.get_element_from_xy(x, y)
814         if element is None:
815             element = layout.get_close_element_from_xy(x, y)
816
817         if element is None:
818             return
819
820         index = element.xy_to_index(x, y)
821         if index is None:
822             return
823
824         self.button_moving_pt = (layout, element, index)
825
826     def on_drawingarea_expose_event(self, widget, event, data=None):
827         self.draw_viewport(event.area)
828
829     def on_drawingarea_configure_event(self, widget, event, data=None):
830         if event.width != self.drawingarea_prev_width:
831             self.wrap_relayout()
832
833         self.adjustment.page_size = self.drawingarea.allocation.height
834         self.vscrollbar.set_increments(20, self.drawingarea.allocation.height)
835
836         # re-set 'value' for prevent overflow
837         self.vscrollbar.set_value(self.vscrollbar.get_value())
838         self.change_vscrollbar_visible()
839
840     def on_vscrollbar_value_changed(self, widget, data=None):
841         self.drawingarea.queue_draw()
842
843     def on_drawingarea_scroll_event(self, widget, event, data=None):
844         self.vscrollbar.emit("scroll-event", event)
845
846     def on_drawingrarea_motion_notify_event(self, widget, event, data=None):
847         if event.state & gtk.gdk.BUTTON1_MASK != gtk.gdk.BUTTON1_MASK:
848             self.button1_pressed = False
849
850         if self.button1_pressed and self.current_pressed_uri is None:
851             old_lay, old_elem, old_idx = self.button_moving_pt
852             self._set_button_moving_pt((event.x, event.y))
853             new_lay, new_elem, new_idx = self.button_moving_pt
854             if (old_lay != new_lay
855                 or old_elem != new_elem
856                 or old_idx != new_idx):
857                 view_y = self.vscrollbar.get_value()
858                 o_y = old_lay.posY
859                 n_y = new_lay.posY
860                 o_width, o_height = old_lay.get_pixel_size()
861                 n_width, n_height = new_lay.get_pixel_size()
862
863                 y = min(o_y, n_y)
864                 height = max(o_y, n_y) - y
865                 if o_y > n_y: height += o_height
866                 else: height += n_height
867
868                 y -= view_y
869
870                 self.drawingarea.queue_draw_area(0, y, n_width, height+1)
871                 #self.drawingarea.window.process_updates(False)
872
873         cursor = ThreadView.regular_cursor
874
875         uri, layout, element = self.ptrpos_to_uri(event.x, event.y)
876         if layout is None:
877             cursor = ThreadView.arrow_cursor
878         else:
879             if uri is not None and uri != "":
880                 cursor = ThreadView.hand_cursor
881                 self.emit("cursor-over-link-event", event, uri)
882
883         self.drawingarea.window.set_cursor(cursor)
884
885     def on_drawingarea_button_press_event(self, widget, event, data=None):
886         if event.button == 1:
887             self.current_pressed_uri = None
888             self.button1_pressed = True
889             uri, layout, element = self.ptrpos_to_uri(event.x, event.y)
890             if uri is not None and layout is not None and element is not None:
891                 self.current_pressed_uri = (uri, layout, element)
892             else:
893                 self._set_button_moving_pt((event.x, event.y))
894                 self._set_button_pressed_pt((event.x, event.y))
895                 self.drawingarea.queue_draw()
896                 
897         elif event.button == 3:
898             time = event.time
899             uri, layout, element = self.ptrpos_to_uri(event.x, event.y)
900             if uri is not None and layout is not None and element is not None:
901                 self.menu_openuri.show()
902                 self.menu_copylinkaddress.show()
903                 self.menu_separator_link.show()
904                 self.menu_openuri.uri = uri
905                 self.menu_copylinkaddress.uri = uri
906             else:
907                 self.menu_openuri.hide()
908                 self.menu_copylinkaddress.hide()
909                 self.menu_separator_link.hide()
910                 self.menu_openuri.uri = None
911                 self.menu_copylinkaddress.uri = None
912
913             text = self.get_selected_text()
914             if text and len(text) > 0:
915                 self.menu_copyselection.show()
916                 self.menu_openasuri.show()
917                 self.menu_separator_selection.show()
918             else:
919                 self.menu_copyselection.hide()
920                 self.menu_openasuri.hide()
921                 self.menu_separator_selection.hide()
922
923             self.popupmenu.popup(None, None, None, event.button, time)
924             return True
925             
926
927     def on_drawingarea_button_release_event(self, widget, event, data=None):
928         if event.button == 1:
929             button1_pressed = self.button1_pressed
930             self.button1_pressed = False
931
932             if button1_pressed and self.current_pressed_uri is not None:
933                 uri, layout, element = self.ptrpos_to_uri(event.x, event.y)
934                 p_uri, p_layout, p_element = self.current_pressed_uri
935                 self.current_pressed_uri = None
936                 if (uri == p_uri and layout == p_layout and
937                     element == p_element):
938                     self.emit("uri-clicked-event", uri)
939
940     def on_drawingarea_style_set(self, widget, previous_style, data=None):
941         if previous_style is None:
942             return False
943
944         new = widget.style.font_desc.hash()
945         old = previous_style.font_desc.hash()
946         if new != old:
947             for layout in self.res_layout_list:
948                 layout.recalc_char_widths()
949             self.wrap_relayout()
950
951     def on_drawingarea_key_press_event(self, widget, event, data=None):
952         if event.type is not gtk.gdk.KEY_PRESS:
953             return
954
955         if event.keyval in (gtk.keysyms.Up, gtk.keysyms.Down,
956                             gtk.keysyms.Page_Up, gtk.keysyms.Page_Down,
957                             gtk.keysyms.Home):
958             value = self.vscrollbar.get_value()
959             if event.keyval == gtk.keysyms.Up:
960                 step_increment = self.adjustment.get_property("step-increment")
961                 value = value - step_increment
962             elif event.keyval == gtk.keysyms.Down:
963                 step_increment = self.adjustment.get_property("step-increment")
964                 value = value + step_increment
965             elif event.keyval == gtk.keysyms.Page_Up:
966                 step_increment = self.adjustment.get_property("page-increment")
967                 value = value - step_increment
968             elif event.keyval == gtk.keysyms.Page_Down:
969                 step_increment = self.adjustment.get_property("page-increment")
970                 value = value + step_increment
971             elif event.keyval == gtk.keysyms.Home:
972                 value = 0
973             self.jump(value)
974         elif event.keyval == gtk.keysyms.End:
975             self.jump_to_the_end()