OSDN Git Service

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