OSDN Git Service

Refactoring drawing iteration in ThreadView.
[fukui-no-namari/fukui-no-namari.git] / src / FukuiNoNamari / thread_view.py
1 # Copyright (C) 2007 by Aiwota Programmer
2 # aiwotaprog@tetteke.tk
3 #
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 2 of the License, or
7 # (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17
18 import pygtk
19 pygtk.require('2.0')
20 import gtk
21 import pango
22 import gobject
23 import itertools
24 from FukuiNoNamariExt import thread_view_extend
25
26
27 def get_approximate_char_height(pango_context):
28     desc = pango_context.get_font_description()
29     font = pango_context.load_font(desc)
30     ink, log = font.get_glyph_extents(0)
31     return log[3] / pango.SCALE + 2
32
33
34 class Rectangle:
35     def __init__(self, x, y, width, height):
36         self.x = x
37         self.y = y
38         self.width = width
39         self.height = height
40
41
42 class Line:
43
44     HEIGHT = 15
45
46     def __init__(self, start_index, end_index, rectangle):
47         self.start_index = start_index
48         self.end_index = end_index
49         self.rectangle = rectangle
50
51     def is_on_xy(self, x, y):
52         left = self.rectangle.x
53         right = left + self.rectangle.width
54         top = self.rectangle.y
55         bottom = top + self.rectangle.height
56         return x >= left and x < right and y >= top and y < bottom
57         
58
59 class ElementEmpty:
60
61     def __init__(self, pango_layout):
62         self.pango_layout = pango_layout
63         self.initialize()
64
65     def recalc_char_widths(self):
66         pass
67         
68     def initialize(self):
69         self.line_list = []
70
71     def is_on_xy(self, x, y):
72         for line in self.line_list:
73             if line.is_on_xy(x, y):
74                 return True
75         return False
76
77     def xy_to_index(self, x, y):
78         return 0
79
80     def build_line_list(self, x, y, width, left_margin):
81         self.initialize()
82
83         line = Line(0, 0, Rectangle(
84             x, y, width - x,
85             get_approximate_char_height(self.pango_layout.get_context())))
86         self.line_list.append(line)
87
88         return width, y
89
90     def get_text(self, selection=False, start_index=0, end_index=0xffffff):
91         return ""
92
93     def draw(self, drawingarea, y_offset, pango_layout,
94              selection=False, start_index=0, end_index=0xffffff):
95         pass
96
97     
98 class ElementText:
99
100     def __init__(self, text, pango_layout):
101         self.text = text
102         self.pango_layout = pango_layout
103
104         attrlist = self._get_attrs()
105         self.widths = thread_view_extend.get_char_width(
106             pango_layout.get_context(), text, attrlist)
107
108         self.line_list = []
109
110     def recalc_char_widths(self):
111         attrlist = self._get_attrs()
112         self.widths = thread_view_extend.get_char_width(
113             self.pango_layout.get_context(), self.text, attrlist)
114
115     def _get_attrs(self):
116         attrs = pango.AttrList()
117         return attrs
118
119     def is_on_xy(self, x, y):
120         for line in self.line_list:
121             if line.is_on_xy(x, y):
122                 return True
123         return False
124
125     def xy_to_index(self, x, y):
126         for line in self.line_list:
127             top = line.rectangle.y
128             bottom = top + line.rectangle.height
129             if y >= top and y < bottom:
130                 sum_of_widths = line.rectangle.x
131                 index = line.start_index
132                 for width in self.widths[line.start_index:line.end_index]:
133                     if sum_of_widths + width/2 > x:
134                         break
135                     sum_of_widths += width
136                     index += 1
137                 return index
138         
139     def build_line_list(self, x, y, width, left_margin):
140         self.line_list = []
141
142         current_line_start_index = 0
143         current_line_x = x
144         current_line_y = y
145         current_line_width = 0
146
147         ch_h = get_approximate_char_height(self.pango_layout.get_context())
148
149         for index, ch in enumerate(self.text):
150             ch_w = self.widths[index]
151             if current_line_x + current_line_width + ch_w > width:
152                 line = Line(
153                     current_line_start_index, index,
154                     Rectangle(
155                     current_line_x, current_line_y,
156                     current_line_width, ch_h))
157                 self.line_list.append(line)
158
159                 current_line_start_index = index
160                 current_line_x = left_margin
161                 current_line_y += ch_h
162                 current_line_width = ch_w
163             else:
164                 current_line_width += ch_w
165
166         if current_line_start_index < len(self.text):
167             line = Line(current_line_start_index, len(self.text),
168                         Rectangle(current_line_x,
169                                           current_line_y,
170                                           current_line_width,
171                                           ch_h))
172             self.line_list.append(line)
173
174             current_line_x += current_line_width
175
176         return current_line_x, current_line_y
177
178     def get_text(self, selection=False, start_index=0, end_index=0xffffff):
179
180         text = ""
181
182         for line in self.line_list:
183
184             t = self.text[line.start_index:line.end_index]
185             if selection:
186                 s = start_index - line.start_index
187                 s = max(s, 0)
188                 s = min(s, line.end_index - line.start_index)
189
190                 e = end_index - line.start_index
191                 e = min(e, line.end_index - line.start_index)
192                 e = max(e, 0)
193
194                 t = t[s:e]
195
196             text += t
197
198         return text
199
200     def draw(self, drawingarea, y_offset, pango_layout,
201              selection=False, start_index=0, end_index=0xffffff):
202
203         selection_fg = drawingarea.style.fg[3]
204         selection_bg = drawingarea.style.bg[3]
205
206         for line in self.line_list:
207
208             text = self.text[line.start_index:line.end_index]
209             u_text = text.encode("utf8")
210             gc = drawingarea.window.new_gc()
211             attrs = self._get_attrs()
212             if selection:
213
214                 s = start_index - line.start_index
215                 s = max(s, 0)
216                 s = min(s, line.end_index - line.start_index)
217                 s = len(text[:s].encode("utf8"))
218
219                 e = end_index - line.start_index
220                 e = min(e, line.end_index - line.start_index)
221                 e = max(e, 0)
222                 e = len(text[:e].encode("utf8"))
223                     
224                 selection_all_attr_fg = pango.AttrForeground(
225                     selection_fg.red, selection_fg.green, selection_fg.blue,
226                     s, e)
227                 selection_all_attr_bg= pango.AttrBackground(
228                     selection_bg.red, selection_bg.green, selection_bg.blue,
229                     s, e)
230                 attrs.insert(selection_all_attr_fg)
231                 attrs.insert(selection_all_attr_bg)
232
233             pango_layout.set_text(u_text)
234             pango_layout.set_attributes(attrs)
235             drawingarea.window.draw_layout(
236                 gc, int(line.rectangle.x), line.rectangle.y + y_offset,
237                 pango_layout)
238
239
240 class ElementBoldText(ElementText):
241
242     def _get_attrs(self):
243         attrlist = pango.AttrList()
244         attr = pango.AttrWeight(pango.WEIGHT_BOLD,
245                                    end_index=0xffffff)
246         attrlist.insert(attr)
247         return attrlist
248
249
250 class ElementLink(ElementText):
251     
252     def __init__(self, text, href, pango_layout):
253         self.href = href
254         ElementText.__init__(self, text, pango_layout)
255
256     def _get_attrs(self):
257         attrlist = pango.AttrList()
258         attr = pango.AttrUnderline(pango.UNDERLINE_SINGLE,
259                                    end_index=0xffffff)
260         attrlist.insert(attr)
261         return attrlist
262
263
264 class ResLayout:
265 # represent one line
266
267     def __init__(self, left_margin, resnum, pango_layout):
268         self.element_list = [ElementEmpty(pango_layout)]
269         self.width = 0
270         self.height = 0
271         self.pango_layout = pango_layout
272         self.left_margin = left_margin
273         self.resnum = resnum
274         self.posY = 0
275         self.list_index = 0
276
277     def add_text(self, text, bold, href):
278         if isinstance(self.element_list[0], ElementEmpty):
279             self.element_list = []
280
281         if href:
282             element = ElementLink(text, href, self.pango_layout)
283             self.element_list.append(element)
284         elif bold:
285             element = ElementBoldText(text, self.pango_layout)
286             self.element_list.append(element)
287         else:
288             element = ElementText(text, self.pango_layout)
289             self.element_list.append(element)
290
291     def get_element_from_xy(self, x, y):
292         for element in self.element_list:
293             if element.is_on_xy(x, y):
294                 return element
295         return None
296
297     def get_close_element_from_xy(self, x, y):
298         x= max(x, self.left_margin)
299         element = self.get_element_from_xy(x, y)
300         if element is None and len(self.element_list) != 0:
301             element = self.element_list[len(self.element_list) - 1]
302         return element
303
304     def recalc_char_widths(self):
305         for element in self.element_list:
306             element.recalc_char_widths()
307
308     def set_width(self, width):
309
310         self.width = width
311         
312         current_x = self.left_margin
313         current_y = 0
314
315         for element in self.element_list:
316             current_x, current_y = element.build_line_list(
317                 current_x, current_y, width, self.left_margin)
318
319         self.height = current_y + get_approximate_char_height(self.pango_layout.get_context())
320
321     def get_pixel_size(self):
322         return self.width, self.height
323
324     def get_text(self, selection_start, selection_end):
325         s_s = selection_start
326         e_s = selection_end
327         s_l, s_e, s_i = selection_start
328         e_l, e_e, e_i = selection_end
329
330         text = ""
331
332         if (s_l is None or s_e is None or s_i is None or
333             e_l is None or e_e is None or e_i is None or
334             self.posY < s_l.posY or self.posY > e_l.posY):
335
336             # nothing to do
337             pass
338
339         elif self.posY > s_s[0].posY and self.posY < e_s[0].posY:
340
341             for element in self.element_list:
342                 text += element.get_text(selection=True)
343
344         elif self == s_s[0] and self == e_s[0]:
345
346             selection = False
347
348             for element in self.element_list:
349                 if s_e == element:
350                     selection = True
351                     start = s_i
352                     end = 0xffffff
353                     if e_e == element:
354                         end = e_i
355                         selection = False
356                     text += element.get_text(selection=True, start_index=start,
357                                              end_index=end)
358                 elif e_e == element:
359                     end = e_i
360                     selection = False
361                     text += element.get_text(
362                         selection=True, end_index=end)
363                 elif selection:
364                     text += element.get_text(selection=True)
365
366         elif self == s_s[0]:
367
368             selection = False
369
370             for element in self.element_list:
371                 if s_e == element:
372                     selection = True
373                     start = s_i
374                     text += element.get_text(selection=True, start_index=start)
375                 elif selection:
376                     text += element.get_text(selection=True)
377
378         elif self == e_s[0]:
379
380             selection = True
381
382             for element in self.element_list:
383                 if e_e == element:
384                     end = e_i
385                     text += element.get_text(selection=True, end_index=e_i)
386                     selection = False
387                 elif selection:
388                     text += element.get_text(selection=True)
389
390         else:
391             # nothing to do
392             pass
393
394         return text
395
396
397     def draw(self, drawingarea, x_offset, y_offset,
398              start_selection, end_selection):
399
400         s_s = start_selection
401         e_s = end_selection
402
403         s_l, s_e, s_i = s_s
404         e_l, e_e, e_i = e_s
405
406         if (s_l is None or s_e is None or s_i is None or
407             e_l is None or e_e is None or e_i is None or
408             self.posY < s_l.posY or self.posY > e_l.posY):
409
410             for element in self.element_list:
411                 element.draw(drawingarea, y_offset, self.pango_layout)
412
413         elif self.posY > s_s[0].posY and self.posY < e_s[0].posY:
414
415             for element in self.element_list:
416                 element.draw(drawingarea, y_offset, self.pango_layout,
417                              selection=True)
418
419         elif self == s_s[0] and self == e_s[0]:
420
421             selection = False
422
423             for element in self.element_list:
424                 if s_e == element:
425                     selection = True
426                     start = s_i
427                     end = 0xffffff
428                     if e_e == element:
429                         end = e_i
430                         selection = False
431                     element.draw(drawingarea, y_offset, self.pango_layout,
432                                  selection=True,
433                                  start_index=start,
434                                  end_index=end)
435                 elif e_e == element:
436                     end = e_i
437                     selection = False
438                     element.draw(drawingarea, y_offset, self.pango_layout,
439                                  selection=True, end_index=end)
440                 else:
441                     element.draw(drawingarea, y_offset, self.pango_layout,
442                                  selection=selection)
443
444         elif self == s_s[0]:
445
446             selection = False
447
448             for element in self.element_list:
449                 if s_e == element:
450                     selection = True
451                     start = s_i
452                     element.draw(drawingarea, y_offset, self.pango_layout,
453                                  selection=selection, start_index = start)
454                 else:
455                     element.draw(drawingarea, y_offset, self.pango_layout,
456                                  selection=selection)
457
458         elif self == e_s[0]:
459
460             selection = True
461
462             for element in self.element_list:
463                 if e_e == element:
464                     end = e_i
465                     element.draw(drawingarea, y_offset, self.pango_layout,
466                                  selection=selection, end_index=e_i)
467                     selection = False
468                 else:
469                     element.draw(drawingarea, y_offset, self.pango_layout,
470                                  selection=selection)
471
472         else:
473             for element in self.element_list:
474                 element.draw(drawingarea, y_offset, self.pango_layout)
475
476     def clone(self):
477         import copy
478         layout = ResLayout(self.left_margin, self.resnum, self.pango_layout)
479         layout.element_list = []
480         for element in self.element_list:
481             layout.element_list.append(copy.copy(element))
482         return layout
483
484
485 class ThreadView(gtk.HBox):
486     __gsignals__ = {
487         "cursor-over-link-event":
488         (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (object, object, )),
489         "uri-clicked-event":
490         (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (object, ))
491         }
492
493     hand_cursor = gtk.gdk.Cursor(gtk.gdk.HAND2)
494     regular_cursor = gtk.gdk.Cursor(gtk.gdk.XTERM)
495     arrow_cursor = gtk.gdk.Cursor(gtk.gdk.LEFT_PTR)
496
497     def __init__(self):
498         gtk.HBox.__init__(self, False, 0)
499         self.drawingarea = gtk.DrawingArea()
500         self.drawingarea.set_property("can_focus", True)
501
502         self.vscrollbar = gtk.VScrollbar()
503         self.pack_start(self.drawingarea)
504         self.pack_start(self.vscrollbar, expand=False)
505         self.adjustment  = self.vscrollbar.get_adjustment()
506
507         self.drawingarea.add_events(
508             gtk.gdk.KEY_PRESS_MASK |
509             gtk.gdk.SCROLL_MASK |
510             gtk.gdk.POINTER_MOTION_MASK |
511             gtk.gdk.BUTTON_PRESS_MASK |
512             gtk.gdk.BUTTON_RELEASE_MASK)
513
514         self.drawingarea_prev_width = 0
515
516         self.drawingarea.connect(
517             "expose-event", self.on_drawingarea_expose_event)
518         self.drawingarea.connect(
519             "configure-event", self.on_drawingarea_configure_event)
520         self.drawingarea.connect(
521             "scroll-event", self.on_drawingarea_scroll_event)
522         self.drawingarea.connect(
523             "motion-notify-event", self.on_drawingrarea_motion_notify_event)
524         self.drawingarea.connect(
525             "button-press-event", self.on_drawingarea_button_press_event)
526         self.drawingarea.connect(
527             "button-release-event", self.on_drawingarea_button_release_event)
528         self.drawingarea.connect(
529             "style-set", self.on_drawingarea_style_set)
530         self.drawingarea.connect(
531             "key-press-event", self.on_drawingarea_key_press_event)
532         self.vscrollbar.connect(
533             "value-changed", self.on_vscrollbar_value_changed)
534
535         self.pango_layout = self.drawingarea.create_pango_layout("")
536
537         self.initialize_buffer()
538
539         self.button1_pressed = False
540         self.current_pressed_uri = None
541             
542         self.popupmenu = None
543         self.menu_openuri = None
544         self.menu_copylinkaddress = None
545         self.menu_separator_link = None
546         self.menu_copyselection = None
547         self.menu_openasuri = None
548         self.menu_separator_selection = None
549
550         self.menud_uri = None
551
552         # for selection
553         self.button_pressed_pt = (None, None, None)
554         self.button_moving_pt = (None, None, None)
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         top_layout = self.get_layout_on_y(view_y)
669         if top_layout is None:
670             return
671         #area_top = view_y + area.y
672         area_bottom = view_y + area.y + area.height
673
674         iter = range(top_layout.list_index, len(self.res_layout_list))
675         iter = itertools.imap(lambda index: self.res_layout_list[index], iter)
676         iter = itertools.takewhile(lambda lay: lay.posY <= area_bottom, iter)
677         for layout in iter:
678             layout.draw(self.drawingarea, 0, layout.posY - int(view_y),
679                 selection_start, selection_end)
680
681     def transform_coordinate_gdk_to_adj(self, y):
682         return y + self.vscrollbar.get_value()
683
684     def transform_coordinate_adj_to_layout(self, x, y, layout):
685         return x, y - layout.posY
686
687     def transform_coordinate_gdk_to_layout(self, x, y, layout):
688         return self.transform_coordinate_adj_to_layout(
689             x, self.transform_coordinate_gdk_to_adj(y), layout)
690
691     def get_layout_on_y(self, y):
692
693         def binary_search(lst, start, end, func):
694
695             if end - start <= 0:
696                 return None
697
698             m = (start + end) / 2
699             ret = func(lst[m])
700
701             if ret == 0:
702                 return m
703             if ret > 0:
704                 return binary_search(lst, start, m, func)
705             return binary_search(lst, m+1, end, func)
706
707         def on_y(layout, _y):
708             top = layout.posY
709             width, height = layout.get_pixel_size()
710             bottom = top + height
711             if _y >= top and _y < bottom:
712                 return 0
713             if _y < top:
714                 return 1
715             return -1
716
717         ret = binary_search(
718             self.res_layout_list, 0, len(self.res_layout_list),
719             lambda x: on_y(x, y))
720         if ret is not None:
721             return self.res_layout_list[ret]
722         return None
723
724     def ptrpos_to_layout(self, x, y):
725         # transform coordinate, GdkWindow -> adjustment
726         adj_y = self.transform_coordinate_gdk_to_adj(y)
727         return self.get_layout_on_y(adj_y)
728
729     def ptrpos_to_uri(self,  x, y):
730         # x, y is GdkWindow coordinate
731
732         layout = self.ptrpos_to_layout(x, y)
733
734         if layout is None:
735             return None, None, None
736
737         # transform coordinate, GdkWindow -> res_layout_list
738         lay_x, lay_y = self.transform_coordinate_gdk_to_layout(x, y, layout)
739
740         # xy -> element
741         element = layout.get_element_from_xy(lay_x, lay_y)
742         if isinstance(element, ElementLink):
743             return element.href, layout, element
744
745         return None, layout, None
746
747     def get_selected_text(self):
748         selection_start, selection_end = self._get_selection_start_end()
749         s_l, s_e, s_i = selection_start
750         e_l, e_e, e_i = selection_end
751
752         if (s_l is None or s_e is None or s_i is None or
753             e_l is None or e_e is None or e_i is None):
754             return ""
755
756         text = ""
757         index = s_l.list_index
758         end = e_l.list_index
759
760         while index <= end:
761             layout = self.res_layout_list[index]
762
763             text += layout.get_text(selection_start, selection_end)
764             if index != end:
765                 text += "\n"
766
767             index += 1
768
769         return text
770
771     def _set_button_pressed_pt(self, pt):
772         self.button_pressed_pt = (None, None, None)
773         if pt == None:
774             return
775
776         x, y = pt
777         layout = self.ptrpos_to_layout(x, y)
778         if layout is None:
779             return
780
781         x, y = self.transform_coordinate_gdk_to_layout(x, y, layout)
782         element = layout.get_element_from_xy(x, y)
783         if element is None:
784             element = layout.get_close_element_from_xy(x, y)
785
786         if element is None:
787             return
788
789         index = element.xy_to_index(x, y)
790         if index is None:
791             return
792
793         self.button_pressed_pt = (layout, element, index)
794
795     def _set_button_moving_pt(self, pt):
796         self.button_moving_pt = (None, None, None)
797         if pt == None:
798             return
799
800         x, y = pt
801         layout = self.ptrpos_to_layout(x, y)
802         if layout is None:
803             return
804
805         x, y = self.transform_coordinate_gdk_to_layout(x, y, layout)
806         element = layout.get_element_from_xy(x, y)
807         if element is None:
808             element = layout.get_close_element_from_xy(x, y)
809
810         if element is None:
811             return
812
813         index = element.xy_to_index(x, y)
814         if index is None:
815             return
816
817         self.button_moving_pt = (layout, element, index)
818
819     def on_drawingarea_expose_event(self, widget, event, data=None):
820         self.draw_viewport(event.area)
821
822     def on_drawingarea_configure_event(self, widget, event, data=None):
823         if event.width != self.drawingarea_prev_width:
824             self.wrap_relayout()
825
826         self.adjustment.page_size = self.drawingarea.allocation.height
827         self.vscrollbar.set_increments(20, self.drawingarea.allocation.height)
828
829         # re-set 'value' for prevent overflow
830         self.vscrollbar.set_value(self.vscrollbar.get_value())
831         self.change_vscrollbar_visible()
832
833     def on_vscrollbar_value_changed(self, widget, data=None):
834         self.drawingarea.queue_draw()
835
836     def on_drawingarea_scroll_event(self, widget, event, data=None):
837         self.vscrollbar.emit("scroll-event", event)
838
839     def on_drawingrarea_motion_notify_event(self, widget, event, data=None):
840         if event.state & gtk.gdk.BUTTON1_MASK != gtk.gdk.BUTTON1_MASK:
841             self.button1_pressed = False
842
843         if self.button1_pressed and self.current_pressed_uri is None:
844             old_lay, old_elem, old_idx = self.button_moving_pt
845             self._set_button_moving_pt((event.x, event.y))
846             new_lay, new_elem, new_idx = self.button_moving_pt
847             if (old_lay != new_lay
848                 or old_elem != new_elem
849                 or old_idx != new_idx):
850                 view_y = self.vscrollbar.get_value()
851                 o_y = old_lay.posY
852                 n_y = new_lay.posY
853                 o_width, o_height = old_lay.get_pixel_size()
854                 n_width, n_height = new_lay.get_pixel_size()
855
856                 y = min(o_y, n_y)
857                 height = max(o_y, n_y) - y
858                 if o_y > n_y: height += o_height
859                 else: height += n_height
860
861                 y -= view_y
862
863                 self.drawingarea.queue_draw_area(0, y, n_width, height+1)
864                 #self.drawingarea.window.process_updates(False)
865
866         cursor = ThreadView.regular_cursor
867
868         uri, layout, element = self.ptrpos_to_uri(event.x, event.y)
869         if layout is None:
870             cursor = ThreadView.arrow_cursor
871         else:
872             if uri is not None and uri != "":
873                 cursor = ThreadView.hand_cursor
874                 self.emit("cursor-over-link-event", event, uri)
875
876         self.drawingarea.window.set_cursor(cursor)
877
878     def on_drawingarea_button_press_event(self, widget, event, data=None):
879         if event.button == 1:
880             self.current_pressed_uri = None
881             self.button1_pressed = True
882             uri, layout, element = self.ptrpos_to_uri(event.x, event.y)
883             if uri is not None and layout is not None and element is not None:
884                 self.current_pressed_uri = (uri, layout, element)
885             else:
886                 self._set_button_moving_pt((event.x, event.y))
887                 self._set_button_pressed_pt((event.x, event.y))
888                 self.drawingarea.queue_draw()
889                 
890         elif event.button == 3:
891             time = event.time
892             uri, layout, element = self.ptrpos_to_uri(event.x, event.y)
893             if uri is not None and layout is not None and element is not None:
894                 self.menu_openuri.show()
895                 self.menu_copylinkaddress.show()
896                 self.menu_separator_link.show()
897                 self.menu_openuri.uri = uri
898                 self.menu_copylinkaddress.uri = uri
899             else:
900                 self.menu_openuri.hide()
901                 self.menu_copylinkaddress.hide()
902                 self.menu_separator_link.hide()
903                 self.menu_openuri.uri = None
904                 self.menu_copylinkaddress.uri = None
905
906             text = self.get_selected_text()
907             if text and len(text) > 0:
908                 self.menu_copyselection.show()
909                 self.menu_openasuri.show()
910                 self.menu_separator_selection.show()
911             else:
912                 self.menu_copyselection.hide()
913                 self.menu_openasuri.hide()
914                 self.menu_separator_selection.hide()
915
916             self.popupmenu.popup(None, None, None, event.button, time)
917             return True
918             
919
920     def on_drawingarea_button_release_event(self, widget, event, data=None):
921         if event.button == 1:
922             button1_pressed = self.button1_pressed
923             self.button1_pressed = False
924
925             if button1_pressed and self.current_pressed_uri is not None:
926                 uri, layout, element = self.ptrpos_to_uri(event.x, event.y)
927                 p_uri, p_layout, p_element = self.current_pressed_uri
928                 self.current_pressed_uri = None
929                 if (uri == p_uri and layout == p_layout and
930                     element == p_element):
931                     self.emit("uri-clicked-event", uri)
932
933     def on_drawingarea_style_set(self, widget, previous_style, data=None):
934         if previous_style is None:
935             return False
936
937         new = widget.style.font_desc.hash()
938         old = previous_style.font_desc.hash()
939         if new != old:
940             for layout in self.res_layout_list:
941                 layout.recalc_char_widths()
942             self.wrap_relayout()
943
944     def on_drawingarea_key_press_event(self, widget, event, data=None):
945         if event.type is not gtk.gdk.KEY_PRESS:
946             return
947
948         if event.keyval in (gtk.keysyms.Up, gtk.keysyms.Down,
949                             gtk.keysyms.Page_Up, gtk.keysyms.Page_Down,
950                             gtk.keysyms.Home):
951             value = self.vscrollbar.get_value()
952             if event.keyval == gtk.keysyms.Up:
953                 step_increment = self.adjustment.get_property("step-increment")
954                 value = value - step_increment
955             elif event.keyval == gtk.keysyms.Down:
956                 step_increment = self.adjustment.get_property("step-increment")
957                 value = value + step_increment
958             elif event.keyval == gtk.keysyms.Page_Up:
959                 step_increment = self.adjustment.get_property("page-increment")
960                 value = value - step_increment
961             elif event.keyval == gtk.keysyms.Page_Down:
962                 step_increment = self.adjustment.get_property("page-increment")
963                 value = value + step_increment
964             elif event.keyval == gtk.keysyms.Home:
965                 value = 0
966             self.jump(value)
967         elif event.keyval == gtk.keysyms.End:
968             self.jump_to_the_end()