OSDN Git Service

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