OSDN Git Service

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