OSDN Git Service

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