OSDN Git Service

Improvement of thread view popup functionality. Appropriate size and position.
[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         "uri-clicked-event":
481         (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (object, ))
482         }
483
484     hand_cursor = gtk.gdk.Cursor(gtk.gdk.HAND2)
485     regular_cursor = gtk.gdk.Cursor(gtk.gdk.XTERM)
486     arrow_cursor = gtk.gdk.Cursor(gtk.gdk.LEFT_PTR)
487
488     def __init__(self):
489         gtk.HBox.__init__(self, False, 0)
490         self.drawingarea = gtk.DrawingArea()
491         self.drawingarea.set_property("can_focus", True)
492
493         self.vscrollbar = gtk.VScrollbar()
494         self.pack_start(self.drawingarea)
495         self.pack_start(self.vscrollbar, expand=False)
496         self.adjustment  = self.vscrollbar.get_adjustment()
497
498         self.drawingarea.add_events(
499             gtk.gdk.KEY_PRESS_MASK |
500             gtk.gdk.SCROLL_MASK |
501             gtk.gdk.POINTER_MOTION_MASK |
502             gtk.gdk.BUTTON_PRESS_MASK |
503             gtk.gdk.BUTTON_RELEASE_MASK)
504
505         self.drawingarea_prev_width = 0
506
507         self.drawingarea.connect(
508             "expose-event", self.on_drawingarea_expose_event)
509         self.drawingarea.connect(
510             "configure-event", self.on_drawingarea_configure_event)
511         self.drawingarea.connect(
512             "scroll-event", self.on_drawingarea_scroll_event)
513         self.drawingarea.connect(
514             "motion-notify-event", self.on_drawingrarea_motion_notify_event)
515         self.drawingarea.connect(
516             "button-press-event", self.on_drawingarea_button_press_event)
517         self.drawingarea.connect(
518             "button-release-event", self.on_drawingarea_button_release_event)
519         self.drawingarea.connect(
520             "style-set", self.on_drawingarea_style_set)
521         self.drawingarea.connect(
522             "key-press-event", self.on_drawingarea_key_press_event)
523         self.vscrollbar.connect(
524             "value-changed", self.on_vscrollbar_value_changed)
525
526         self.pango_layout = self.drawingarea.create_pango_layout("")
527
528         self.initialize_buffer()
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 initialize_buffer(self):
548         self.res_layout_list = []
549
550     def add_layout(self, res_layout):
551         if (len(self.res_layout_list) != 0):
552             last = self.res_layout_list[len(self.res_layout_list)-1]
553             x, y = last.get_pixel_size()
554             res_layout.posY = last.posY + y
555         self.set_layout_width(res_layout)
556         res_layout.list_index = len(self.res_layout_list)
557         self.res_layout_list.append(res_layout)
558
559         x, y = res_layout.get_pixel_size()
560         self.adjustment.upper = res_layout.posY + y
561         # do not use this method in a loop because expensive.
562         # self.redraw()
563         self.change_vscrollbar_visible()
564
565     def create_res_layout(self, left_margin, resnum):
566         return ResLayout(left_margin, resnum, self.pango_layout)
567
568     def set_layout_width(self, layout):
569         width = self.drawingarea.allocation.width
570         layout.set_width(width)
571         
572     def redraw(self):
573         self.drawingarea.queue_draw()
574
575     def wrap_relayout(self):
576         # before relayout, find top layout on gdkwindow
577         top_layout = self.get_layout_on_y(self.adjustment.value)
578         delta = 0
579
580         if top_layout is not None:
581             delta = top_layout.posY - self.vscrollbar.get_value()
582
583         self.relayout()
584         self.drawingarea_prev_width = self.drawingarea.allocation.width
585
586         # after relayout, set vscrollbar.value to top layout's posY
587         if top_layout is not None:
588             self.vscrollbar.set_value(top_layout.posY - delta)
589
590     def relayout(self):
591         width = self.drawingarea.allocation.width
592         sum_height = 0
593         for layout in self.res_layout_list:
594             layout.set_width(width)
595             layout.posY = sum_height
596             x, y = layout.get_pixel_size()
597             sum_height += y
598
599         self.vscrollbar.set_range(0, sum_height)
600         self.change_vscrollbar_visible()
601
602     def change_vscrollbar_visible(self):
603         if self.adjustment.upper <= self.adjustment.page_size:
604             self.vscrollbar.hide()
605         else:
606             self.vscrollbar.show()
607
608     def jump(self, value):
609         self.vscrollbar.set_value(value)
610
611     def jump_to_layout(self, layout):
612         self.jump(layout.posY)
613         
614     def jump_to_the_end(self):
615         value = self.adjustment.upper - self.adjustment.page_size
616         self.vscrollbar.set_value(value)
617
618     def jump_to_res(self, resnum):
619         for layout in self.res_layout_list:
620             if layout.resnum == resnum:
621                 self.jump_to_layout(layout)
622                 return True
623         return False
624
625
626     def _get_selection_start_end(self):
627         pressed_layout, pressed_element, pressed_index = self.button_pressed_pt
628         moving_layout, moving_element, moving_index = self.button_moving_pt
629
630         if (pressed_layout is None or pressed_element is None or
631             pressed_index is None or moving_layout is None or
632             moving_element is None or moving_index is None):
633             return (None, None, None), (None, None, None)
634         
635         if pressed_layout == moving_layout:
636             if pressed_element == moving_element:
637                 if moving_index < pressed_index:
638                     return self.button_moving_pt, self.button_pressed_pt
639             else:
640                 pressed_element_index = pressed_layout.element_list.index(
641                     pressed_element)
642                 moving_element_index = moving_layout.element_list.index(
643                     moving_element)
644                 if moving_element_index < pressed_element_index:
645                     return self.button_moving_pt, self.button_pressed_pt
646         elif moving_layout.posY < pressed_layout.posY:
647             return self.button_moving_pt, self.button_pressed_pt
648
649         return self.button_pressed_pt, self.button_moving_pt
650
651     def draw_viewport(self, area):
652         view_y = self.vscrollbar.get_value()
653         self.drawingarea.window.draw_rectangle(
654             self.drawingarea.style.base_gc[0],
655             True, area.x, area.y, area.width, area.height)
656
657         selection_start, selection_end = self._get_selection_start_end()
658
659
660         top_layout = self.get_layout_on_y(view_y)
661         index = 0
662         if top_layout:
663             index = top_layout.list_index
664         while index < len(self.res_layout_list):
665             layout = self.res_layout_list[index]
666             w, h = layout.get_pixel_size()
667             layout_top = layout.posY
668             layout_bottom = layout.posY + h
669             area_top = view_y + area.y
670             area_bottom = view_y + area.y + area.height
671             if layout_top <= area_bottom and layout_bottom >= area_top:
672                 layout.draw(self.drawingarea,
673                             0, layout.posY - int(view_y),
674                             selection_start, selection_end)
675             if layout_top > area_bottom:
676                 break
677
678             index += 1
679
680     def transform_coordinate_gdk_to_adj(self, y):
681         return y + self.vscrollbar.get_value()
682
683     def transform_coordinate_adj_to_layout(self, x, y, layout):
684         return x, y - layout.posY
685
686     def transform_coordinate_gdk_to_layout(self, x, y, layout):
687         return self.transform_coordinate_adj_to_layout(
688             x, self.transform_coordinate_gdk_to_adj(y), layout)
689
690     def get_layout_on_y(self, y):
691
692         def binary_search(lst, start, end, func):
693
694             if end - start <= 0:
695                 return None
696
697             m = (start + end) / 2
698             ret = func(lst[m])
699
700             if ret == 0:
701                 return m
702             if ret > 0:
703                 return binary_search(lst, start, m, func)
704             return binary_search(lst, m+1, end, func)
705
706         def on_y(layout, _y):
707             top = layout.posY
708             width, height = layout.get_pixel_size()
709             bottom = top + height
710             if _y >= top and _y < bottom:
711                 return 0
712             if _y < top:
713                 return 1
714             return -1
715
716         ret = binary_search(
717             self.res_layout_list, 0, len(self.res_layout_list),
718             lambda x: on_y(x, y))
719         if ret is not None:
720             return self.res_layout_list[ret]
721         return None
722
723     def ptrpos_to_layout(self, x, y):
724         # transform coordinate, GdkWindow -> adjustment
725         adj_y = self.transform_coordinate_gdk_to_adj(y)
726         return self.get_layout_on_y(adj_y)
727
728     def ptrpos_to_uri(self,  x, y):
729         # x, y is GdkWindow coordinate
730
731         layout = self.ptrpos_to_layout(x, y)
732
733         if layout is None:
734             return None, None, None
735
736         # transform coordinate, GdkWindow -> res_layout_list
737         lay_x, lay_y = self.transform_coordinate_gdk_to_layout(x, y, layout)
738
739         # xy -> element
740         element = layout.get_element_from_xy(lay_x, lay_y)
741         if isinstance(element, ElementLink):
742             return element.href, layout, element
743
744         return None, layout, None
745
746     def get_selected_text(self):
747         selection_start, selection_end = self._get_selection_start_end()
748         s_l, s_e, s_i = selection_start
749         e_l, e_e, e_i = selection_end
750
751         if (s_l is None or s_e is None or s_i is None or
752             e_l is None or e_e is None or e_i is None):
753             return ""
754
755         text = ""
756         index = s_l.list_index
757         end = e_l.list_index
758
759         while index <= end:
760             layout = self.res_layout_list[index]
761
762             text += layout.get_text(selection_start, selection_end)
763             if index != end:
764                 text += "\n"
765
766             index += 1
767
768         return text
769
770     def _set_button_pressed_pt(self, pt):
771         self.button_pressed_pt = (None, None, None)
772         if pt == None:
773             return
774
775         x, y = pt
776         layout = self.ptrpos_to_layout(x, y)
777         if layout is None:
778             return
779
780         x, y = self.transform_coordinate_gdk_to_layout(x, y, layout)
781         element = layout.get_element_from_xy(x, y)
782         if element is None:
783             element = layout.get_close_element_from_xy(x, y)
784
785         if element is None:
786             return
787
788         index = element.xy_to_index(x, y)
789         if index is None:
790             return
791
792         self.button_pressed_pt = (layout, element, index)
793
794     def _set_button_moving_pt(self, pt):
795         self.button_moving_pt = (None, None, None)
796         if pt == None:
797             return
798
799         x, y = pt
800         layout = self.ptrpos_to_layout(x, y)
801         if layout is None:
802             return
803
804         x, y = self.transform_coordinate_gdk_to_layout(x, y, layout)
805         element = layout.get_element_from_xy(x, y)
806         if element is None:
807             element = layout.get_close_element_from_xy(x, y)
808
809         if element is None:
810             return
811
812         index = element.xy_to_index(x, y)
813         if index is None:
814             return
815
816         self.button_moving_pt = (layout, element, index)
817
818     def on_drawingarea_expose_event(self, widget, event, data=None):
819         self.draw_viewport(event.area)
820
821     def on_drawingarea_configure_event(self, widget, event, data=None):
822         if event.width != self.drawingarea_prev_width:
823             self.wrap_relayout()
824
825         self.adjustment.page_size = self.drawingarea.allocation.height
826         self.vscrollbar.set_increments(20, self.drawingarea.allocation.height)
827
828         # re-set 'value' for prevent overflow
829         self.vscrollbar.set_value(self.vscrollbar.get_value())
830         self.change_vscrollbar_visible()
831
832     def on_vscrollbar_value_changed(self, widget, data=None):
833         self.drawingarea.queue_draw()
834
835     def on_drawingarea_scroll_event(self, widget, event, data=None):
836         self.vscrollbar.emit("scroll-event", event)
837
838     def on_drawingrarea_motion_notify_event(self, widget, event, data=None):
839         if event.state & gtk.gdk.BUTTON1_MASK != gtk.gdk.BUTTON1_MASK:
840             self.button1_pressed = False
841
842         if self.button1_pressed and self.current_pressed_uri is None:
843             old_lay, old_elem, old_idx = self.button_moving_pt
844             self._set_button_moving_pt((event.x, event.y))
845             new_lay, new_elem, new_idx = self.button_moving_pt
846             if (old_lay != new_lay
847                 or old_elem != new_elem
848                 or old_idx != new_idx):
849                 view_y = self.vscrollbar.get_value()
850                 o_y = old_lay.posY
851                 n_y = new_lay.posY
852                 o_width, o_height = old_lay.get_pixel_size()
853                 n_width, n_height = new_lay.get_pixel_size()
854
855                 y = min(o_y, n_y)
856                 height = max(o_y, n_y) - y
857                 if o_y > n_y: height += o_height
858                 else: height += n_height
859
860                 y -= view_y
861
862                 self.drawingarea.queue_draw_area(0, y, n_width, height+1)
863                 #self.drawingarea.window.process_updates(False)
864
865         cursor = ThreadView.regular_cursor
866
867         uri, layout, element = self.ptrpos_to_uri(event.x, event.y)
868         if layout is None:
869             cursor = ThreadView.arrow_cursor
870         else:
871             if uri is not None and uri != "":
872                 cursor = ThreadView.hand_cursor
873                 self.emit("cursor-over-link-event", event, uri)
874
875         self.drawingarea.window.set_cursor(cursor)
876
877     def on_drawingarea_button_press_event(self, widget, event, data=None):
878         if event.button == 1:
879             self.current_pressed_uri = None
880             self.button1_pressed = True
881             uri, layout, element = self.ptrpos_to_uri(event.x, event.y)
882             if uri is not None and layout is not None and element is not None:
883                 self.current_pressed_uri = (uri, layout, element)
884             else:
885                 self._set_button_moving_pt((event.x, event.y))
886                 self._set_button_pressed_pt((event.x, event.y))
887                 self.drawingarea.queue_draw()
888                 
889         elif event.button == 3:
890             time = event.time
891             uri, layout, element = self.ptrpos_to_uri(event.x, event.y)
892             if uri is not None and layout is not None and element is not None:
893                 self.menu_openuri.show()
894                 self.menu_copylinkaddress.show()
895                 self.menu_separator_link.show()
896                 self.menu_openuri.uri = uri
897                 self.menu_copylinkaddress.uri = uri
898             else:
899                 self.menu_openuri.hide()
900                 self.menu_copylinkaddress.hide()
901                 self.menu_separator_link.hide()
902                 self.menu_openuri.uri = None
903                 self.menu_copylinkaddress.uri = None
904
905             text = self.get_selected_text()
906             if text and len(text) > 0:
907                 self.menu_copyselection.show()
908                 self.menu_openasuri.show()
909                 self.menu_separator_selection.show()
910             else:
911                 self.menu_copyselection.hide()
912                 self.menu_openasuri.hide()
913                 self.menu_separator_selection.hide()
914
915             self.popupmenu.popup(None, None, None, event.button, time)
916             return True
917             
918
919     def on_drawingarea_button_release_event(self, widget, event, data=None):
920         if event.button == 1:
921             button1_pressed = self.button1_pressed
922             self.button1_pressed = False
923
924             if button1_pressed and self.current_pressed_uri is not None:
925                 uri, layout, element = self.ptrpos_to_uri(event.x, event.y)
926                 p_uri, p_layout, p_element = self.current_pressed_uri
927                 self.current_pressed_uri = None
928                 if (uri == p_uri and layout == p_layout and
929                     element == p_element):
930                     self.emit("uri-clicked-event", uri)
931
932     def on_drawingarea_style_set(self, widget, previous_style, data=None):
933         if previous_style is None:
934             return False
935
936         new = widget.style.font_desc.hash()
937         old = previous_style.font_desc.hash()
938         if new != old:
939             for layout in self.res_layout_list:
940                 layout.recalc_char_widths()
941             self.wrap_relayout()
942
943     def on_drawingarea_key_press_event(self, widget, event, data=None):
944         if event.type is not gtk.gdk.KEY_PRESS:
945             return
946
947         if event.keyval in (gtk.keysyms.Up, gtk.keysyms.Down,
948                             gtk.keysyms.Page_Up, gtk.keysyms.Page_Down,
949                             gtk.keysyms.Home):
950             value = self.vscrollbar.get_value()
951             if event.keyval == gtk.keysyms.Up:
952                 step_increment = self.adjustment.get_property("step-increment")
953                 value = value - step_increment
954             elif event.keyval == gtk.keysyms.Down:
955                 step_increment = self.adjustment.get_property("step-increment")
956                 value = value + step_increment
957             elif event.keyval == gtk.keysyms.Page_Up:
958                 step_increment = self.adjustment.get_property("page-increment")
959                 value = value - step_increment
960             elif event.keyval == gtk.keysyms.Page_Down:
961                 step_increment = self.adjustment.get_property("page-increment")
962                 value = value + step_increment
963             elif event.keyval == gtk.keysyms.Home:
964                 value = 0
965             self.jump(value)
966         elif event.keyval == gtk.keysyms.End:
967             self.jump_to_the_end()