1 # Copyright (C) 2007 by Aiwota Programmer
2 # aiwotaprog@tetteke.tk
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.
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.
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
22 from FukuiNoNamariExt import thread_view_extend
29 def __init__(self, start_index, end_index, rectangle):
30 self.start_index = start_index
31 self.end_index = end_index
32 self.rectangle = rectangle
34 def is_on_xy(self, x, y):
35 left = self.rectangle.x
36 right = left + self.rectangle.width
37 top = self.rectangle.y
38 bottom = top + self.rectangle.height
39 return x >= left and x < right and y >= top and y < bottom
50 def is_on_xy(self, x, y):
51 for line in self.line_list:
52 if line.is_on_xy(x, y):
56 def xy_to_index(self, x, y):
59 def build_line_list(self, x, y, width, left_margin):
62 line = Line(0, 0, gtk.gdk.Rectangle(x, y, width - x, Line.HEIGHT))
63 self.line_list.append(line)
67 def draw(self, drawingarea, y_offset, pango_layout,
68 selection=False, start_index=0, end_index=0xffffff):
74 def __init__(self, text, pango_layout):
77 attrlist = self._get_attrs()
78 self.widths = thread_view_extend.get_char_width(
79 pango_layout.get_context(), text, attrlist)
84 attrs = pango.AttrList()
87 def is_on_xy(self, x, y):
88 for line in self.line_list:
89 if line.is_on_xy(x, y):
93 def xy_to_index(self, x, y):
94 for line in self.line_list:
95 top = line.rectangle.y
96 bottom = top + line.rectangle.height
97 if y >= top and y < bottom:
98 sum_of_widths = line.rectangle.x
99 index = line.start_index
100 for width in self.widths[line.start_index:line.end_index]:
101 if sum_of_widths + width/2 > x:
103 sum_of_widths += width
107 def build_line_list(self, x, y, width, left_margin):
110 current_line_start_index = 0
113 current_line_width = 0
115 for index, ch in enumerate(self.text):
116 ch_w = self.widths[index]
118 if current_line_x + current_line_width + ch_w > width:
120 current_line_start_index, index,
122 current_line_x, current_line_y,
123 current_line_width, Line.HEIGHT))
124 self.line_list.append(line)
126 current_line_start_index = index
127 current_line_x = left_margin
128 current_line_y += Line.HEIGHT
129 current_line_width = ch_w
131 current_line_width += ch_w
133 if current_line_start_index < len(self.text):
134 line = Line(current_line_start_index, len(self.text),
135 gtk.gdk.Rectangle(current_line_x,
139 self.line_list.append(line)
141 current_line_x += current_line_width
143 return current_line_x, current_line_y
145 def draw(self, drawingarea, y_offset, pango_layout,
146 selection=False, start_index=0, end_index=0xffffff):
148 selection_fg = drawingarea.style.fg[3]
149 selection_bg = drawingarea.style.bg[3]
151 for line in self.line_list:
153 text = self.text[line.start_index:line.end_index]
154 u_text = text.encode("utf8")
155 gc = drawingarea.window.new_gc()
156 attrs = self._get_attrs()
159 s = start_index - line.start_index
161 s = min(s, line.end_index - line.start_index)
162 s = len(text[:s].encode("utf8"))
164 e = end_index - line.start_index
165 e = min(e, line.end_index - line.start_index)
167 e = len(text[:e].encode("utf8"))
169 selection_all_attr_fg = pango.AttrForeground(
170 selection_fg.red, selection_fg.green, selection_fg.blue,
172 selection_all_attr_bg= pango.AttrBackground(
173 selection_bg.red, selection_bg.green, selection_bg.blue,
175 attrs.insert(selection_all_attr_fg)
176 attrs.insert(selection_all_attr_bg)
178 pango_layout.set_text(u_text)
179 pango_layout.set_attributes(attrs)
180 drawingarea.window.draw_layout(
181 gc, line.rectangle.x, line.rectangle.y + y_offset,
185 class ElementBoldText(ElementText):
187 def _get_attrs(self):
188 attrlist = pango.AttrList()
189 attr = pango.AttrWeight(pango.WEIGHT_BOLD,
191 attrlist.insert(attr)
195 class ElementLink(ElementText):
197 def __init__(self, text, href, pango_layout):
199 ElementText.__init__(self, text, pango_layout)
201 def _get_attrs(self):
202 attrlist = pango.AttrList()
203 attr = pango.AttrUnderline(pango.UNDERLINE_SINGLE,
205 attrlist.insert(attr)
212 def __init__(self, left_margin, resnum, pango_layout):
213 self.element_list = [ElementEmpty()]
216 self.pango_layout = pango_layout
217 self.left_margin = left_margin
222 def add_text(self, text, bold, href):
223 if isinstance(self.element_list[0], ElementEmpty):
224 self.element_list = []
227 element = ElementLink(text, href, self.pango_layout)
228 self.element_list.append(element)
230 element = ElementBoldText(text, self.pango_layout)
231 self.element_list.append(element)
233 element = ElementText(text, self.pango_layout)
234 self.element_list.append(element)
236 def get_element_from_xy(self, x, y):
237 for element in self.element_list:
238 if element.is_on_xy(x, y):
242 def get_close_element_from_xy(self, x, y):
243 x= max(x, self.left_margin)
244 element = self.get_element_from_xy(x, y)
245 if element is None and len(self.element_list) != 0:
246 element = self.element_list[len(self.element_list) - 1]
249 def set_width(self, width):
253 current_x = self.left_margin
256 for element in self.element_list:
257 current_x, current_y = element.build_line_list(
258 current_x, current_y, width, self.left_margin)
260 self.height = current_y + Line.HEIGHT
262 def get_pixel_size(self):
263 return self.width, self.height
265 def draw(self, drawingarea, x_offset, y_offset,
266 start_selection, end_selection):
268 s_s = start_selection
274 if (s_l is None or s_e is None or s_i is None or
275 e_l is None or e_e is None or e_i is None or
276 self.posY < s_l.posY or self.posY > e_l.posY):
278 for element in self.element_list:
279 element.draw(drawingarea, y_offset, self.pango_layout)
281 elif self.posY > s_s[0].posY and self.posY < e_s[0].posY:
283 for element in self.element_list:
284 element.draw(drawingarea, y_offset, self.pango_layout,
287 elif self == s_s[0] and self == e_s[0]:
291 for element in self.element_list:
299 element.draw(drawingarea, y_offset, self.pango_layout,
306 element.draw(drawingarea, y_offset, self.pango_layout,
307 selection=True, end_index=end)
309 element.draw(drawingarea, y_offset, self.pango_layout,
316 for element in self.element_list:
320 element.draw(drawingarea, y_offset, self.pango_layout,
321 selection=selection, start_index = start)
323 element.draw(drawingarea, y_offset, self.pango_layout,
330 for element in self.element_list:
333 element.draw(drawingarea, y_offset, self.pango_layout,
334 selection=selection, end_index=e_i)
337 element.draw(drawingarea, y_offset, self.pango_layout,
341 for element in self.element_list:
342 element.draw(drawingarea, y_offset, self.pango_layout)
346 class ThreadView(gtk.HBox):
347 hand_cursor = gtk.gdk.Cursor(gtk.gdk.HAND2)
348 regular_cursor = gtk.gdk.Cursor(gtk.gdk.XTERM)
349 arrow_cursor = gtk.gdk.Cursor(gtk.gdk.LEFT_PTR)
352 gtk.HBox.__init__(self, False, 0)
353 self.drawingarea = gtk.DrawingArea()
354 self.vscrollbar = gtk.VScrollbar()
355 self.pack_start(self.drawingarea)
356 self.pack_start(self.vscrollbar, expand=False)
357 self.adjustment = self.vscrollbar.get_adjustment()
359 self.drawingarea.add_events(
360 gtk.gdk.SCROLL_MASK |
361 gtk.gdk.POINTER_MOTION_MASK |
362 gtk.gdk.BUTTON_PRESS_MASK |
363 gtk.gdk.BUTTON_RELEASE_MASK)
365 self.drawingarea_prev_width = 0
367 self.drawingarea.connect(
368 "expose-event", self.on_drawingarea_expose_event)
369 self.drawingarea.connect(
370 "configure-event", self.on_drawingarea_configure_event)
371 self.drawingarea.connect(
372 "scroll-event", self.on_drawingarea_scroll_event)
373 self.drawingarea.connect(
374 "motion-notify-event", self.on_drawingrarea_motion_notify_event)
375 self.drawingarea.connect(
376 "button-press-event", self.on_drawingarea_button_press_event)
377 self.drawingarea.connect(
378 "button-release-event", self.on_drawingarea_button_release_event)
379 self.vscrollbar.connect(
380 "value-changed", self.on_vscrollbar_value_changed)
382 self.pango_layout = self.drawingarea.create_pango_layout("")
384 self.initialize_buffer()
386 self.on_uri_clicked = self._on_uri_clicked
388 self.button1_pressed = False
389 self.current_pressed_uri = None
391 self.popupmenu = None
392 self.menu_openuri = None
393 self.menu_copylinkaddress = None
394 self.menu_separator_link = None
395 self.menu_copyselection = None
396 self.menu_openasuri = None
397 self.menu_separator_selection = None
399 self.menud_uri = None
402 self.button_pressed_pt = (None, None, None)
403 self.button_moving_pt = (None, None, None)
405 def _on_uri_clicked(self, uri):
406 print uri, "clicked!!!!"
408 def initialize_buffer(self):
409 self.res_layout_list = []
410 self.layout_posY_map = [(0, [])]
412 def add_layout(self, res_layout):
413 if (len(self.res_layout_list) != 0):
414 last = self.res_layout_list[len(self.res_layout_list)-1]
415 x, y = last.get_pixel_size()
416 res_layout.posY = last.posY + y
417 self.set_layout_width(res_layout)
418 res_layout.list_index = len(self.res_layout_list)
419 self.res_layout_list.append(res_layout)
421 if len(self.layout_posY_map[len(self.layout_posY_map)-1][1]) == 128:
422 self.layout_posY_map.append((res_layout.posY, []))
423 self.layout_posY_map[len(self.layout_posY_map)-1][1].append(res_layout)
425 x, y = res_layout.get_pixel_size()
426 self.adjustment.upper = res_layout.posY + y
428 self.change_vscrollbar_visible()
430 def create_res_layout(self, left_margin, resnum):
431 return ResLayout(left_margin, resnum, self.pango_layout)
433 def set_layout_width(self, layout):
434 width = self.drawingarea.allocation.width
435 layout.set_width(width)
438 self.drawingarea.queue_draw()
441 self.layout_posY_map = [(0, [])]
443 width = self.drawingarea.allocation.width
445 for layout in self.res_layout_list:
446 layout.set_width(width)
447 layout.posY = sum_height
448 x, y = layout.get_pixel_size()
451 if len(self.layout_posY_map[len(self.layout_posY_map)-1][1])==128:
452 self.layout_posY_map.append((layout.posY, []))
453 self.layout_posY_map[len(self.layout_posY_map)-1][1].append(layout)
455 self.vscrollbar.set_range(0, sum_height)
456 self.change_vscrollbar_visible()
458 def change_vscrollbar_visible(self):
459 if self.adjustment.upper < self.adjustment.page_size:
460 self.vscrollbar.hide()
462 self.vscrollbar.show()
464 def jump(self, value):
465 self.vscrollbar.set_value(value)
467 def jump_to_layout(self, layout):
468 self.jump(layout.posY)
470 def jump_to_the_end(self):
471 value = self.adjustment.upper - self.adjustment.page_size
472 self.vscrollbar.set_value(value)
474 def jump_to_res(self, resnum):
475 for layout in self.res_layout_list:
476 if layout.resnum == resnum:
477 self.jump_to_layout(layout)
482 def _get_selection_start_end(self):
483 pressed_layout, pressed_element, pressed_index = self.button_pressed_pt
484 moving_layout, moving_element, moving_index = self.button_moving_pt
486 if (pressed_layout is None or pressed_element is None or
487 pressed_index is None or moving_layout is None or
488 moving_element is None or moving_index is None):
489 return (None, None, None), (None, None, None)
491 if pressed_layout == moving_layout:
492 if pressed_element == moving_element:
493 if moving_index < pressed_index:
494 return self.button_moving_pt, self.button_pressed_pt
496 pressed_element_index = pressed_layout.element_list.index(
498 moving_element_index = moving_layout.element_list.index(
500 if moving_element_index < pressed_element_index:
501 return self.button_moving_pt, self.button_pressed_pt
502 elif moving_layout.posY < pressed_layout.posY:
503 return self.button_moving_pt, self.button_pressed_pt
505 return self.button_pressed_pt, self.button_moving_pt
507 def draw_viewport(self, area):
508 view_y = self.vscrollbar.get_value()
509 self.drawingarea.window.draw_rectangle(
510 self.drawingarea.style.base_gc[0],
511 True, area.x, area.y, area.width, area.height)
513 selection_start, selection_end = self._get_selection_start_end()
516 top_layout = self.get_layout_on_y(view_y)
519 index = top_layout.list_index
520 while index < len(self.res_layout_list):
521 layout = self.res_layout_list[index]
522 w, h = layout.get_pixel_size()
523 layout_top = layout.posY
524 layout_bottom = layout.posY + h
525 area_top = view_y + area.y
526 area_bottom = view_y + area.y + area.height
527 if layout_top <= area_bottom and layout_bottom >= area_top:
528 layout.draw(self.drawingarea,
529 0, layout.posY - int(view_y),
530 selection_start, selection_end)
531 if layout_top > area_bottom:
536 def transform_coordinate_gdk_to_adj(self, y):
537 return y + self.vscrollbar.get_value()
539 def transform_coordinate_adj_to_layout(self, x, y, layout):
540 return x, y - layout.posY
542 def transform_coordinate_gdk_to_layout(self, x, y, layout):
543 return self.transform_coordinate_adj_to_layout(
544 x, self.transform_coordinate_gdk_to_adj(y), layout)
546 def get_layout_on_y(self, y):
548 for pos, lay_lst in self.layout_posY_map:
551 layout_list = lay_lst
554 for lay in layout_list:
561 def ptrpos_to_layout(self, x, y):
562 # transform coordinate, GdkWindow -> adjustment
563 adj_y = self.transform_coordinate_gdk_to_adj(y)
564 return self.get_layout_on_y(adj_y)
566 def ptrpos_to_uri(self, x, y):
567 # x, y is GdkWindow coordinate
569 layout = self.ptrpos_to_layout(x, y)
572 return None, None, None
574 # transform coordinate, GdkWindow -> res_layout_list
575 lay_x, lay_y = self.transform_coordinate_gdk_to_layout(x, y, layout)
578 element = layout.get_element_from_xy(lay_x, lay_y)
579 if isinstance(element, ElementLink):
580 return element.href, layout, element
582 return None, layout, None
585 def _set_button_pressed_pt(self, pt):
586 self.button_pressed_pt = (None, None, None)
591 layout = self.ptrpos_to_layout(x, y)
595 x, y = self.transform_coordinate_gdk_to_layout(x, y, layout)
596 element = layout.get_element_from_xy(x, y)
598 element = layout.get_close_element_from_xy(x, y)
603 index = element.xy_to_index(x, y)
607 self.button_pressed_pt = (layout, element, index)
609 def _set_button_moving_pt(self, pt):
610 self.button_moving_pt = (None, None, None)
615 layout = self.ptrpos_to_layout(x, y)
619 x, y = self.transform_coordinate_gdk_to_layout(x, y, layout)
620 element = layout.get_element_from_xy(x, y)
622 element = layout.get_close_element_from_xy(x, y)
627 index = element.xy_to_index(x, y)
631 self.button_moving_pt = (layout, element, index)
633 def on_drawingarea_expose_event(self, widget, event, data=None):
634 self.draw_viewport(event.area)
636 def on_drawingarea_configure_event(self, widget, event, data=None):
637 if event.width != self.drawingarea_prev_width:
639 # before relayout, find top layout on gdkwindow
642 for lay in self.res_layout_list:
643 if lay.posY > self.adjustment.value:
647 if top_layout is not None:
648 delta = top_layout.posY - self.vscrollbar.get_value()
651 self.drawingarea_prev_width = event.width
653 # after relayout, set vscrollbar.value to top layout's posY
654 if top_layout is not None:
655 self.vscrollbar.set_value(top_layout.posY - delta)
657 self.adjustment.page_size = self.drawingarea.allocation.height
658 self.vscrollbar.set_increments(20, self.drawingarea.allocation.height)
660 # re-set 'value' for prevent overflow
661 self.vscrollbar.set_value(self.vscrollbar.get_value())
662 self.change_vscrollbar_visible()
664 def on_vscrollbar_value_changed(self, widget, data=None):
665 self.drawingarea.queue_draw()
667 def on_drawingarea_scroll_event(self, widget, event, data=None):
668 self.vscrollbar.emit("scroll-event", event)
670 def on_drawingrarea_motion_notify_event(self, widget, event, data=None):
671 if event.state & gtk.gdk.BUTTON1_MASK != gtk.gdk.BUTTON1_MASK:
672 self.button1_pressed = False
674 if self.button1_pressed:
675 old_lay, old_elem, old_idx = self.button_moving_pt
676 self._set_button_moving_pt((event.x, event.y))
677 new_lay, new_elem, new_idx = self.button_moving_pt
678 if (old_lay != new_lay
679 or old_elem != new_elem
680 or old_idx != new_idx):
681 view_y = self.vscrollbar.get_value()
684 o_width, o_height = old_lay.get_pixel_size()
685 n_width, n_height = new_lay.get_pixel_size()
688 height = max(o_y, n_y) - y
689 if o_y > n_y: height += o_height
690 else: height += n_height
694 self.drawingarea.queue_draw_area(0, y, n_width, height+1)
695 #self.drawingarea.window.process_updates(False)
697 cursor = ThreadView.regular_cursor
699 uri, layout, element = self.ptrpos_to_uri(event.x, event.y)
701 cursor = ThreadView.arrow_cursor
703 if uri is not None and uri != "":
704 cursor = ThreadView.hand_cursor
706 self.drawingarea.window.set_cursor(cursor)
708 def on_drawingarea_button_press_event(self, widget, event, data=None):
709 if event.button == 1:
710 self.current_pressed_uri = None
711 self.button1_pressed = True
712 uri, layout, element = self.ptrpos_to_uri(event.x, event.y)
713 if uri is not None and layout is not None and element is not None:
714 self.current_pressed_uri = (uri, layout, element)
716 self._set_button_moving_pt((event.x, event.y))
717 self._set_button_pressed_pt((event.x, event.y))
718 self.drawingarea.queue_draw()
720 elif event.button == 3:
722 uri, layout, element = self.ptrpos_to_uri(event.x, event.y)
723 if uri is not None and layout is not None and element is not None:
724 self.menu_openuri.show()
725 self.menu_copylinkaddress.show()
726 self.menu_separator_link.show()
727 self.menu_openuri.uri = uri
728 self.menu_copylinkaddress.uri = uri
730 self.menu_openuri.hide()
731 self.menu_copylinkaddress.hide()
732 self.menu_separator_link.hide()
733 self.menu_openuri.uri = None
734 self.menu_copylinkaddress.uri = None
736 self.menu_copyselection.hide()
737 self.menu_openasuri.hide()
738 self.menu_separator_selection.hide()
740 self.popupmenu.popup(None, None, None, event.button, time)
744 def on_drawingarea_button_release_event(self, widget, event, data=None):
745 if event.button == 1:
746 button1_pressed = self.button1_pressed
747 self.button1_pressed = False
749 if button1_pressed and self.current_pressed_uri is not None:
750 uri, layout, element = self.ptrpos_to_uri(event.x, event.y)
751 p_uri, p_layout, p_element = self.current_pressed_uri
752 self.current_preesed_uri = None
753 if (uri == p_uri and layout == p_layout and
754 element == p_element):
755 self.on_uri_clicked(uri)