OSDN Git Service

Replace PangoLayout with ResLayout.
[fukui-no-namari/fukui-no-namari.git] / src / FukuiNoNamari / thread_view.py
index 8a3ff1c..f3d2e4b 100644 (file)
@@ -19,6 +19,172 @@ import pygtk
 pygtk.require('2.0')
 import gtk
 import pango
+from FukuiNoNamariExt import thread_view_extend
+
+
+class Line:
+
+    HEIGHT = 15
+
+    def __init__(self, start_index, end_index, rectangle):
+        self.start_index = start_index
+        self.end_index = end_index
+        self.rectangle = rectangle
+
+    def is_on_xy(self, x, y):
+        left = self.rectangle.x
+        right = left + self.rectangle.width
+        top = self.rectangle.y
+        bottom = top + self.rectangle.height
+        return x >= left and x < right and y >= top and y < bottom
+        
+
+class ElementText:
+
+    def __init__(self, text, pango_layout):
+        self.text = text
+
+        attrlist = self._get_attrs()
+        self.widths = thread_view_extend.get_char_width(
+            pango_layout.get_context(), text, attrlist)
+
+        self.line_list = []
+
+    def _get_attrs(self):
+        attrs = pango.AttrList()
+        return attrs
+
+    def is_on_xy(self, x, y):
+        for line in self.line_list:
+            if line.is_on_xy(x, y):
+                return True
+        return False
+
+    def build_line_list(self, x, y, width, left_margin):
+        self.line_list = []
+
+        current_line_start_index = 0
+        current_line_x = x
+        current_line_y = y
+        current_line_width = 0
+
+        for index, ch in enumerate(self.text):
+            ch_w = self.widths[index]
+            ch_h = Line.HEIGHT
+            if current_line_x + current_line_width + ch_w > width:
+                line = Line(
+                    current_line_start_index, index,
+                    gtk.gdk.Rectangle(
+                    current_line_x, current_line_y,
+                    current_line_width, Line.HEIGHT))
+                self.line_list.append(line)
+
+                current_line_start_index = index
+                current_line_x = left_margin
+                current_line_y += Line.HEIGHT
+                current_line_width = ch_w
+            else:
+                current_line_width += ch_w
+
+        if current_line_start_index < len(self.text):
+            line = Line(current_line_start_index, len(self.text),
+                        gtk.gdk.Rectangle(current_line_x,
+                                          current_line_y,
+                                          current_line_width,
+                                          Line.HEIGHT))
+            self.line_list.append(line)
+
+            current_line_x += current_line_width
+
+        return current_line_x, current_line_y
+
+    def draw(self, drawable, y_offset, pango_layout):
+
+        for line in self.line_list:
+            text = self.text[line.start_index:line.end_index]
+            gc = drawable.new_gc()
+            pango_layout.set_text(text)
+            attrs = self._get_attrs()
+            if attrs:
+                pango_layout.set_attributes(attrs)
+            drawable.draw_layout(gc,
+                                 line.rectangle.x, line.rectangle.y + y_offset,
+                                 pango_layout)
+
+
+class ElementBoldText(ElementText):
+
+    def _get_attrs(self):
+        attrlist = pango.AttrList()
+        attr = pango.AttrWeight(pango.WEIGHT_BOLD,
+                                   end_index=0xffffff)
+        attrlist.insert(attr)
+        return attrlist
+
+
+class ElementLink(ElementText):
+    
+    def __init__(self, text, href, pango_layout):
+        self.href = href
+        ElementText.__init__(self, text, pango_layout)
+
+    def _get_attrs(self):
+        attrlist = pango.AttrList()
+        attr = pango.AttrUnderline(pango.UNDERLINE_SINGLE,
+                                   end_index=0xffffff)
+        attrlist.insert(attr)
+        return attrlist
+
+
+class ResLayout:
+# represent one line
+
+    def __init__(self, left_margin, resnum, pango_layout):
+        self.element_list = []
+        self.width = 0
+        self.height = 0
+        self.pango_layout = pango_layout
+        self.left_margin = left_margin
+        self.resnum = resnum
+        self.posY = 0
+
+    def add_text(self, text, bold, href):
+        if href:
+            element = ElementLink(text, href, self.pango_layout)
+            self.element_list.append(element)
+        elif bold:
+            element = ElementBoldText(text, self.pango_layout)
+            self.element_list.append(element)
+        else:
+            element = ElementText(text, self.pango_layout)
+            self.element_list.append(element)
+
+    def get_element_from_xy(self, x, y):
+        for element in self.element_list:
+            if element.is_on_xy(x, y):
+                return element
+        return None
+
+    def set_width(self, width):
+
+        self.width = width
+        
+        current_x = self.left_margin
+        current_y = 0
+
+        for element in self.element_list:
+            current_x, current_y = element.build_line_list(
+                current_x, current_y, width, self.left_margin)
+
+        self.height = current_y + Line.HEIGHT
+
+    def get_pixel_size(self):
+        return self.width, self.height
+
+    def draw(self, drawable, x_offset, y_offset):
+
+        for element in self.element_list:
+            element.draw(drawable, y_offset, self.pango_layout)
 
 
 class ThreadView(gtk.HBox):
@@ -45,7 +211,7 @@ class ThreadView(gtk.HBox):
         self.drawingarea.connect(
             "expose-event", self.on_drawingarea_expose_event)
         self.drawingarea.connect(
-            "size-allocate", self.on_drawingarea_size_allocate)
+            "configure-event", self.on_drawingarea_configure_event)
         self.drawingarea.connect(
             "scroll-event", self.on_drawingarea_scroll_event)
         self.drawingarea.connect(
@@ -57,6 +223,8 @@ class ThreadView(gtk.HBox):
         self.vscrollbar.connect(
             "value-changed", self.on_vscrollbar_value_changed)
 
+        self.pango_layout = self.drawingarea.create_pango_layout("")
+
         self.initialize_buffer()
 
         self.on_uri_clicked = self._on_uri_clicked
@@ -78,26 +246,26 @@ class ThreadView(gtk.HBox):
         print uri, "clicked!!!!"
 
     def initialize_buffer(self):
-        self.pangolayout = []
+        self.res_layout_list = []
 
-    def add_layout(self, pangolayout):
-        if (len(self.pangolayout) != 0):
-            last = self.pangolayout[len(self.pangolayout)-1]
+    def add_layout(self, res_layout):
+        if (len(self.res_layout_list) != 0):
+            last = self.res_layout_list[len(self.res_layout_list)-1]
             x, y = last.get_pixel_size()
-            pangolayout.posY = last.posY + y
-        self.set_layout_width(pangolayout)
-        self.pangolayout.append(pangolayout)
-        x, y = pangolayout.get_pixel_size()
-        self.vscrollbar.set_range(0, pangolayout.posY + y)
+            res_layout.posY = last.posY + y
+        self.set_layout_width(res_layout)
+        self.res_layout_list.append(res_layout)
+        x, y = res_layout.get_pixel_size()
+        self.adjustment.upper = res_layout.posY + y
         self.redraw()
         self.change_vscrollbar_visible()
 
-    def create_pango_layout(self, text):
-        return self.drawingarea.create_pango_layout(text)
+    def create_res_layout(self, left_margin, resnum):
+        return ResLayout(left_margin, resnum, self.pango_layout)
 
     def set_layout_width(self, layout):
         width = self.drawingarea.allocation.width
-        layout.set_width((width - layout.marginleft) * pango.SCALE)
+        layout.set_width(width)
         
     def redraw(self):
         self.drawingarea.queue_draw()
@@ -105,8 +273,8 @@ class ThreadView(gtk.HBox):
     def relayout(self):
         width = self.drawingarea.allocation.width
         sum_height = 0
-        for layout in self.pangolayout:
-            layout.set_width((width - layout.marginleft) * pango.SCALE)
+        for layout in self.res_layout_list:
+            layout.set_width(width)
             layout.posY = sum_height
             x, y = layout.get_pixel_size()
             sum_height += y
@@ -130,7 +298,7 @@ class ThreadView(gtk.HBox):
         self.vscrollbar.set_value(value)
 
     def jump_to_res(self, resnum):
-        for layout in self.pangolayout:
+        for layout in self.res_layout_list:
             if layout.resnum == resnum:
                 self.jump_to_layout(layout)
                 return True
@@ -145,21 +313,21 @@ class ThreadView(gtk.HBox):
             self.drawingarea.allocation.height)
 
         gc = self.drawingarea.window.new_gc()
-        for layout in self.pangolayout:
+        for layout in self.res_layout_list:
             w, h = layout.get_pixel_size()
             layout_top = layout.posY
             layout_bottom = layout.posY + h
             area_top = view_y
             area_bottom = view_y + self.drawingarea.allocation.height
             if layout_top <= area_bottom and layout_bottom >= area_top:
-                self.drawingarea.window.draw_layout(
-                    gc, layout.marginleft, layout.posY - int(view_y), layout)
+                layout.draw(self.drawingarea.window,
+                            0, layout.posY - int(view_y))
 
     def transform_coordinate_gdk_to_adj(self, y):
         return y + self.vscrollbar.get_value()
 
     def transform_coordinate_adj_to_layout(self, x, y, layout):
-        return x - layout.marginleft, y - layout.posY
+        return x, y - layout.posY
 
     def transform_coordinate_gdk_to_layout(self, x, y, layout):
         return self.transform_coordinate_adj_to_layout(
@@ -169,10 +337,10 @@ class ThreadView(gtk.HBox):
         # transform coordinate, GdkWindow -> adjustment
         adj_x = x
         adj_y = self.transform_coordinate_gdk_to_adj(y)
-        for lay in self.pangolayout:
+        for lay in self.res_layout_list:
             width, height = lay.get_pixel_size()
             if (adj_y >= lay.posY and adj_y < lay.posY + height and
-                adj_x >= lay.marginleft):
+                adj_x >= lay.left_margin):
                 return lay
         return None
 
@@ -184,34 +352,40 @@ class ThreadView(gtk.HBox):
         if layout is None:
             return None, None, None
 
-        # transform coordinate, GdkWindow -> pangolayout
+        # transform coordinate, GdkWindow -> res_layout_list
         lay_x, lay_y = self.transform_coordinate_gdk_to_layout(x, y, layout)
 
-        # xy -> index
-        idx, clk = layout.xy_to_index(
-            int(lay_x)*pango.SCALE, int(lay_y)*pango.SCALE)
-
-        x, y, width, height = layout.index_to_pos(idx)
-        x /= pango.SCALE
-        y /= pango.SCALE
-        width /= pango.SCALE
-        height /= pango.SCALE
-        if (lay_x >= x and lay_x < x + width and
-            lay_y >= y and lay_y < y + height):
-
-            for i, (start, end, href) in enumerate(layout.urilist):
-                if idx >= start and idx < end:
-                    return href, layout, i
+        # xy -> element
+        element = layout.get_element_from_xy(lay_x, lay_y)
+        if isinstance(element, ElementLink):
+            return element.href, layout, element
 
         return None, layout, None
 
     def on_drawingarea_expose_event(self, widget, event, data=None):
         self.draw_viewport()
 
-    def on_drawingarea_size_allocate(self, widget, allocation, data=None):
-        if allocation.width != self.drawingarea_prev_width:
+    def on_drawingarea_configure_event(self, widget, event, data=None):
+        if event.width != self.drawingarea_prev_width:
+
+            # before relayout, find top layout on gdkwindow
+            top_layout = None
+            delta = 0
+            for lay in self.res_layout_list:
+                if lay.posY > self.adjustment.value:
+                    break
+                top_layout = lay
+
+            if top_layout is not None:
+                delta = top_layout.posY - self.vscrollbar.get_value()
+
             self.relayout()
-            self.drawingarea_prev_width = allocation.width
+            self.drawingarea_prev_width = event.width
+
+            # after relayout, set vscrollbar.value to top layout's posY
+            if top_layout is not None:
+                self.vscrollbar.set_value(top_layout.posY - delta)
+
         self.adjustment.page_size = self.drawingarea.allocation.height
         self.vscrollbar.set_increments(20, self.drawingarea.allocation.height)
 
@@ -231,7 +405,7 @@ class ThreadView(gtk.HBox):
 
         cursor = ThreadView.regular_cursor
 
-        uri, layout, index = self.ptrpos_to_uri(event.x, event.y)
+        uri, layout, element = self.ptrpos_to_uri(event.x, event.y)
         if layout is None:
             cursor = ThreadView.arrow_cursor
         else:
@@ -244,13 +418,13 @@ class ThreadView(gtk.HBox):
         if event.button == 1:
             self.current_pressed_uri = None
             self.button1_pressed = True
-            uri, layout, index = self.ptrpos_to_uri(event.x, event.y)
-            if uri is not None and layout is not None and index is not None:
-                self.current_pressed_uri = (uri, layout, index)
+            uri, layout, element = self.ptrpos_to_uri(event.x, event.y)
+            if uri is not None and layout is not None and element is not None:
+                self.current_pressed_uri = (uri, layout, element)
         elif event.button == 3:
             time = event.time
-            uri, layout, index = self.ptrpos_to_uri(event.x, event.y)
-            if uri is not None and layout is not None and index is not None:
+            uri, layout, element = self.ptrpos_to_uri(event.x, event.y)
+            if uri is not None and layout is not None and element is not None:
                 self.menu_openuri.show()
                 self.menu_copylinkaddress.show()
                 self.menu_separator_link.show()
@@ -277,8 +451,9 @@ class ThreadView(gtk.HBox):
             self.button1_pressed = False
 
             if button1_pressed and self.current_pressed_uri is not None:
-                uri, layout, index = self.ptrpos_to_uri(event.x, event.y)
-                p_uri, p_layout, p_index = self.current_pressed_uri
+                uri, layout, element = self.ptrpos_to_uri(event.x, event.y)
+                p_uri, p_layout, p_element = self.current_pressed_uri
                 self.current_preesed_uri = None
-                if uri == p_uri and layout == p_layout and index == p_index:
+                if (uri == p_uri and layout == p_layout and
+                    element == p_element):
                     self.on_uri_clicked(uri)