OSDN Git Service

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