OSDN Git Service

The module "thread_view" is divided to the package "ThreadViewBase". (#16539)
authorAiwota Programmer <aiwotaprog@tetteke.tk>
Mon, 4 May 2009 16:04:11 +0000 (01:04 +0900)
committerAiwota Programmer <aiwotaprog@tetteke.tk>
Mon, 4 May 2009 16:04:11 +0000 (01:04 +0900)
src/FukuiNoNamari/ThreadViewBase/__init__.py [new file with mode: 0644]
src/FukuiNoNamari/ThreadViewBase/drawable.py [new file with mode: 0644]
src/FukuiNoNamari/ThreadViewBase/element.py [new file with mode: 0644]
src/FukuiNoNamari/ThreadViewBase/layoutable.py [new file with mode: 0644]
src/FukuiNoNamari/ThreadViewBase/pointer_trackable.py [new file with mode: 0644]
src/FukuiNoNamari/ThreadViewBase/res_layout.py [new file with mode: 0644]
src/FukuiNoNamari/ThreadViewBase/scrollable.py [new file with mode: 0644]
src/FukuiNoNamari/thread_popup.py
src/FukuiNoNamari/thread_view.py
src/FukuiNoNamari/thread_window.py

diff --git a/src/FukuiNoNamari/ThreadViewBase/__init__.py b/src/FukuiNoNamari/ThreadViewBase/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/FukuiNoNamari/ThreadViewBase/drawable.py b/src/FukuiNoNamari/ThreadViewBase/drawable.py
new file mode 100644 (file)
index 0000000..7aef589
--- /dev/null
@@ -0,0 +1,53 @@
+# Copyright (C) 2009 by Aiwota Programmer
+# aiwotaprog@tetteke.tk
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+
+import pygtk
+pygtk.require('2.0')
+import gtk
+import itertools
+from pointer_trackable import PointerTrackable
+
+
+class Drawable(PointerTrackable):
+
+    __gsignals__ = {
+        "expose-event": "override",
+        }
+
+    def draw_viewport(self, area):
+        view_y = self.adjustment.value
+        self.window.draw_rectangle(
+            self.style.base_gc[0],
+            True, area.x, area.y, area.width, area.height)
+
+        selection_start, selection_end = self._get_selection_start_end()
+
+        top_layout = self.get_layout_on_y(view_y)
+        if top_layout is None:
+            return
+        #area_top = view_y + area.y
+        area_bottom = view_y + area.y + area.height
+
+        iter = range(top_layout.list_index, len(self.res_layout_list))
+        iter = itertools.imap(lambda index: self.res_layout_list[index], iter)
+        iter = itertools.takewhile(lambda lay: lay.posY <= area_bottom, iter)
+        for layout in iter:
+            layout.draw(self, 0, layout.posY - int(view_y),
+                selection_start, selection_end)
+
+    def do_expose_event(self, event):
+        self.draw_viewport(event.area)
diff --git a/src/FukuiNoNamari/ThreadViewBase/element.py b/src/FukuiNoNamari/ThreadViewBase/element.py
new file mode 100644 (file)
index 0000000..dc5da2f
--- /dev/null
@@ -0,0 +1,288 @@
+# Copyright (C) 2009 by Aiwota Programmer
+# aiwotaprog@tetteke.tk
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+
+import pygtk
+pygtk.require('2.0')
+import gtk
+import pango
+import itertools
+from FukuiNoNamariExt import thread_view_extend
+
+def get_approximate_char_height(pango_context):
+    desc = pango_context.get_font_description()
+    font = pango_context.load_font(desc)
+    ink, log = font.get_glyph_extents(0)
+    return log[3] / pango.SCALE + 2
+
+
+class Rectangle:
+    def __init__(self, x, y, width, height):
+        self.x = x
+        self.y = y
+        self.width = width
+        self.height = height
+
+
+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 ElementEmpty:
+
+    def __init__(self, pango_layout):
+        self.pango_layout = pango_layout
+        self.initialize()
+
+    def recalc_char_widths(self):
+        pass
+        
+    def initialize(self):
+        self.line_list = []
+
+    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 xy_to_index(self, x, y):
+        return 0
+
+    def build_line_list(self, x, y, width, left_margin):
+        self.initialize()
+
+        line = Line(0, 0, Rectangle(
+            x, y, width - x,
+            get_approximate_char_height(self.pango_layout.get_context())))
+        self.line_list.append(line)
+
+        return width, y
+
+    def get_text(self, selection=False, start_index=0, end_index=0xffffff):
+        return ""
+
+    def draw(self, drawingarea, y_offset, pango_layout,
+             selection=False, start_index=0, end_index=0xffffff):
+        pass
+
+    
+class ElementText:
+
+    ch_width_dict = {}   # key: char, value: width
+
+    def __init__(self, text, pango_layout):
+        self.text = text
+        self.pango_layout = pango_layout
+
+        self.recalc_char_widths()
+
+        self.line_list = []
+
+    def recalc_char_widths(self):
+        self.widths = [i for i in itertools.repeat(0, len(self.text))]
+
+        dict = self._get_ch_width_dict()
+        need_to_get = False
+        for index, ch in enumerate(self.text):
+            if ch not in dict:
+                need_to_get = True
+                break
+            else:
+                width = dict[ch]
+                self.widths[index] = width
+
+        if need_to_get:
+            attrlist = self._get_attrs()
+            self.widths = thread_view_extend.get_char_width(
+                self.pango_layout.get_context(), self.text, attrlist)
+            for index, width in enumerate(self.widths):
+                dict[self.text[index]] = self.widths[index]
+
+    def _get_ch_width_dict(self):
+        return ElementText.ch_width_dict
+
+    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 xy_to_index(self, x, y):
+        for line in self.line_list:
+            top = line.rectangle.y
+            bottom = top + line.rectangle.height
+            if y >= top and y < bottom:
+                sum_of_widths = line.rectangle.x
+                index = line.start_index
+                for width in self.widths[line.start_index:line.end_index]:
+                    if sum_of_widths + width/2 > x:
+                        break
+                    sum_of_widths += width
+                    index += 1
+                return index
+        
+    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
+
+        ch_h = get_approximate_char_height(self.pango_layout.get_context())
+
+        for index, ch in enumerate(self.text):
+            ch_w = self.widths[index]
+            if current_line_x + current_line_width + ch_w > width:
+                line = Line(
+                    current_line_start_index, index,
+                    Rectangle(
+                    current_line_x, current_line_y,
+                    current_line_width, ch_h))
+                self.line_list.append(line)
+
+                current_line_start_index = index
+                current_line_x = left_margin
+                current_line_y += ch_h
+                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),
+                        Rectangle(current_line_x,
+                                          current_line_y,
+                                          current_line_width,
+                                          ch_h))
+            self.line_list.append(line)
+
+            current_line_x += current_line_width
+
+        return current_line_x, current_line_y
+
+    def get_text(self, selection=False, start_index=0, end_index=0xffffff):
+
+        text = ""
+
+        for line in self.line_list:
+
+            t = self.text[line.start_index:line.end_index]
+            if selection:
+                s = start_index - line.start_index
+                s = max(s, 0)
+                s = min(s, line.end_index - line.start_index)
+
+                e = end_index - line.start_index
+                e = min(e, line.end_index - line.start_index)
+                e = max(e, 0)
+
+                t = t[s:e]
+
+            text += t
+
+        return text
+
+    def draw(self, drawingarea, y_offset, pango_layout,
+             selection=False, start_index=0, end_index=0xffffff):
+
+        if drawingarea.get_property("has-focus"):
+            selection_fg = drawingarea.style.text[gtk.STATE_SELECTED]
+            selection_bg = drawingarea.style.base[gtk.STATE_SELECTED]
+        else:
+            selection_fg = drawingarea.style.text[gtk.STATE_ACTIVE]
+            selection_bg = drawingarea.style.base[gtk.STATE_ACTIVE]
+
+        for line in self.line_list:
+
+            text = self.text[line.start_index:line.end_index]
+            u_text = text.encode("utf8")
+            gc = drawingarea.window.new_gc()
+            gc.set_foreground(drawingarea.style.text[gtk.STATE_NORMAL])
+            gc.set_background(drawingarea.style.base[gtk.STATE_NORMAL])
+            attrs = self._get_attrs()
+            if selection:
+
+                s = start_index - line.start_index
+                s = max(s, 0)
+                s = min(s, line.end_index - line.start_index)
+                s = len(text[:s].encode("utf8"))
+
+                e = end_index - line.start_index
+                e = min(e, line.end_index - line.start_index)
+                e = max(e, 0)
+                e = len(text[:e].encode("utf8"))
+                    
+                selection_all_attr_fg = pango.AttrForeground(
+                    selection_fg.red, selection_fg.green, selection_fg.blue,
+                    s, e)
+                selection_all_attr_bg= pango.AttrBackground(
+                    selection_bg.red, selection_bg.green, selection_bg.blue,
+                    s, e)
+                attrs.insert(selection_all_attr_fg)
+                attrs.insert(selection_all_attr_bg)
+
+            pango_layout.set_text(u_text)
+            pango_layout.set_attributes(attrs)
+            drawingarea.window.draw_layout(
+                gc, int(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
+
+    def recalc_char_widths(self):
+        attrlist = self._get_attrs()
+        self.widths = thread_view_extend.get_char_width(
+            self.pango_layout.get_context(), self.text, 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
diff --git a/src/FukuiNoNamari/ThreadViewBase/layoutable.py b/src/FukuiNoNamari/ThreadViewBase/layoutable.py
new file mode 100644 (file)
index 0000000..c3723bf
--- /dev/null
@@ -0,0 +1,165 @@
+# Copyright (C) 2009 by Aiwota Programmer
+# aiwotaprog@tetteke.tk
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+
+import pygtk
+pygtk.require('2.0')
+import gtk
+from scrollable import Scrollable
+from res_layout import ResLayout
+from element import ElementLink
+
+
+class Layoutable(Scrollable):
+
+    __gsignals__ = {
+        "configure-event": "override",
+        "style-set": "override",
+    }
+
+    def __init__(self):
+        super(Layoutable, self).__init__()
+        self.initialize_buffer()
+        self.pango_layout = self.create_pango_layout("")
+        self.drawingarea_prev_width = 0 
+
+    def initialize_buffer(self):
+        self.res_layout_list = []
+
+    def transform_coordinate_adj_to_layout(self, x, y, layout):
+        return x, y - layout.posY
+
+    def transform_coordinate_gdk_to_layout(self, x, y, layout):
+        return self.transform_coordinate_adj_to_layout(
+            x, self.transform_coordinate_gdk_to_adj(y), layout)
+
+    def ptrpos_to_layout(self, x, y):
+        # transform coordinate, GdkWindow -> adjustment
+        adj_y = self.transform_coordinate_gdk_to_adj(y)
+        return self.get_layout_on_y(adj_y)
+
+    def ptrpos_to_uri(self,  x, y):
+        # x, y is GdkWindow coordinate
+
+        layout = self.ptrpos_to_layout(x, y)
+
+        if layout is None:
+            return None, None, None
+
+        # transform coordinate, GdkWindow -> res_layout_list
+        lay_x, lay_y = self.transform_coordinate_gdk_to_layout(x, y, layout)
+
+        # 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 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()
+            res_layout.posY = last.posY + y
+        self.set_layout_width(res_layout)
+        res_layout.list_index = len(self.res_layout_list)
+        self.res_layout_list.append(res_layout)
+
+        x, y = res_layout.get_pixel_size()
+        self.adjustment.upper = res_layout.posY + y
+        # do not use this method in a loop because expensive.
+        # self.redraw()
+
+    def create_res_layout(self, left_margin, resnum):
+        return ResLayout(left_margin, resnum, self.pango_layout)
+
+    def set_layout_width(self, layout):
+        layout.set_width(self.allocation.width)
+
+    def wrap_relayout(self):
+        # before relayout, find top layout on gdkwindow
+        top_layout = self.get_layout_on_y(self.adjustment.value)
+        delta = 0
+
+        if top_layout is not None:
+            delta = top_layout.posY - self.adjustment.value
+
+        self.relayout()
+        self.drawingarea_prev_width = self.allocation.width
+
+        # after relayout, set adjustment.value to top layout's posY
+        if top_layout is not None:
+            self.adjustment.value = top_layout.posY - delta
+
+    def relayout(self):
+        sum_height = 0
+        for layout in self.res_layout_list:
+            layout.set_width(self.allocation.width)
+            layout.posY = sum_height
+            x, y = layout.get_pixel_size()
+            sum_height += y
+
+        self.adjustment.upper = sum_height
+
+    def get_layout_on_y(self, y):
+
+        def binary_search(lst, start, end, func):
+
+            if end - start <= 0:
+                return None
+
+            m = (start + end) / 2
+            ret = func(lst[m])
+
+            if ret == 0:
+                return m
+            if ret > 0:
+                return binary_search(lst, start, m, func)
+            return binary_search(lst, m+1, end, func)
+
+        def on_y(layout, _y):
+            top = layout.posY
+            width, height = layout.get_pixel_size()
+            bottom = top + height
+            if _y >= top and _y < bottom:
+                return 0
+            if _y < top:
+                return 1
+            return -1
+
+        ret = binary_search(
+            self.res_layout_list, 0, len(self.res_layout_list),
+            lambda x: on_y(x, y))
+        if ret is not None:
+            return self.res_layout_list[ret]
+        return None
+
+    def do_configure_event(self, event):
+        if event.width != self.drawingarea_prev_width:
+            self.wrap_relayout()
+
+        Scrollable.do_configure_event(self, event)
+
+    def do_style_set(self, previous_style):
+        if previous_style is None:
+            return False
+
+        new = self.style.font_desc.hash()
+        old = previous_style.font_desc.hash()
+        if new != old:
+            for layout in self.res_layout_list:
+                layout.recalc_char_widths()
+            self.wrap_relayout()
diff --git a/src/FukuiNoNamari/ThreadViewBase/pointer_trackable.py b/src/FukuiNoNamari/ThreadViewBase/pointer_trackable.py
new file mode 100644 (file)
index 0000000..3033f6e
--- /dev/null
@@ -0,0 +1,185 @@
+# Copyright (C) 2009 by Aiwota Programmer
+# aiwotaprog@tetteke.tk
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+
+import pygtk
+pygtk.require('2.0')
+import gtk
+import gobject
+from layoutable import Layoutable
+
+
+class PointerTrackable(Layoutable):
+
+    __gsignals__ = {
+        "uri-clicked-event":
+        (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (object, )),
+        "motion-notify-event": "override",
+        "button-press-event": "override",
+        "button-release-event": "override",
+    }
+
+    hand_cursor = gtk.gdk.Cursor(gtk.gdk.HAND2)
+    regular_cursor = gtk.gdk.Cursor(gtk.gdk.XTERM)
+    arrow_cursor = gtk.gdk.Cursor(gtk.gdk.LEFT_PTR)
+
+    def __init__(self):
+        super(PointerTrackable, self).__init__()
+
+        # for url click event
+        self.button1_pressed = False
+        self.current_pressed_uri = None
+
+        # for selection
+        self.button_pressed_pt = (None, None, None)
+        self.button_moving_pt = (None, None, None)
+
+    def _get_selection_start_end(self):
+        pressed_layout, pressed_element, pressed_index = self.button_pressed_pt
+        moving_layout, moving_element, moving_index = self.button_moving_pt
+
+        if (pressed_layout is None or pressed_element is None or
+            pressed_index is None or moving_layout is None or
+            moving_element is None or moving_index is None):
+            return (None, None, None), (None, None, None)
+        
+        if pressed_layout == moving_layout:
+            if pressed_element == moving_element:
+                if moving_index < pressed_index:
+                    return self.button_moving_pt, self.button_pressed_pt
+            else:
+                pressed_element_index = pressed_layout.element_list.index(
+                    pressed_element)
+                moving_element_index = moving_layout.element_list.index(
+                    moving_element)
+                if moving_element_index < pressed_element_index:
+                    return self.button_moving_pt, self.button_pressed_pt
+        elif moving_layout.posY < pressed_layout.posY:
+            return self.button_moving_pt, self.button_pressed_pt
+
+        return self.button_pressed_pt, self.button_moving_pt
+
+    def _set_button_moving_pt(self, pt):
+        self.button_moving_pt = (None, None, None)
+        if pt == None:
+            return
+
+        x, y = pt
+        layout = self.ptrpos_to_layout(x, y)
+        if layout is None:
+            return
+
+        x, y = self.transform_coordinate_gdk_to_layout(x, y, layout)
+        element = layout.get_element_from_xy(x, y)
+        if element is None:
+            element = layout.get_close_element_from_xy(x, y)
+
+        if element is None:
+            return
+
+        index = element.xy_to_index(x, y)
+        if index is None:
+            return
+
+        self.button_moving_pt = (layout, element, index)
+
+    def _set_button_pressed_pt(self, pt):
+        self.button_pressed_pt = (None, None, None)
+        if pt == None:
+            return
+
+        x, y = pt
+        layout = self.ptrpos_to_layout(x, y)
+        if layout is None:
+            return
+
+        x, y = self.transform_coordinate_gdk_to_layout(x, y, layout)
+        element = layout.get_element_from_xy(x, y)
+        if element is None:
+            element = layout.get_close_element_from_xy(x, y)
+
+        if element is None:
+            return
+
+        index = element.xy_to_index(x, y)
+        if index is None:
+            return
+
+        self.button_pressed_pt = (layout, element, index)
+
+    def do_motion_notify_event(self, event):
+        if event.state & gtk.gdk.BUTTON1_MASK != gtk.gdk.BUTTON1_MASK:
+            self.button1_pressed = False
+
+        if self.button1_pressed and self.current_pressed_uri is None:
+            old_lay, old_elem, old_idx = self.button_moving_pt
+            self._set_button_moving_pt((event.x, event.y))
+            new_lay, new_elem, new_idx = self.button_moving_pt
+            if (old_lay != new_lay
+                or old_elem != new_elem
+                or old_idx != new_idx):
+                view_y = self.adjustment.value
+                o_y = old_lay.posY
+                n_y = new_lay.posY
+                o_width, o_height = old_lay.get_pixel_size()
+                n_width, n_height = new_lay.get_pixel_size()
+
+                y = min(o_y, n_y)
+                height = max(o_y, n_y) - y
+                if o_y > n_y: height += o_height
+                else: height += n_height
+
+                y -= view_y
+
+                self.queue_draw_area(0, y, n_width, height+1)
+                #self.window.process_updates(False)
+
+        cursor = PointerTrackable.regular_cursor
+
+        uri, layout, element = self.ptrpos_to_uri(event.x, event.y)
+        if layout is None:
+            cursor = PointerTrackable.arrow_cursor
+        else:
+            if uri is not None and uri != "":
+                cursor = PointerTrackable.hand_cursor
+                self.emit("cursor-over-link-event", event, uri)
+
+        self.window.set_cursor(cursor)
+
+    def do_button_press_event(self, event):
+        if event.button == 1:
+            self.current_pressed_uri = None
+            self.button1_pressed = True
+            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)
+            else:
+                self._set_button_moving_pt((event.x, event.y))
+                self._set_button_pressed_pt((event.x, event.y))
+                self.queue_draw()
+
+    def do_button_release_event(self, event):
+        if event.button == 1:
+            button1_pressed = self.button1_pressed
+            self.button1_pressed = False
+
+            if button1_pressed and self.current_pressed_uri is not None:
+                uri, layout, element = self.ptrpos_to_uri(event.x, event.y)
+                p_uri, p_layout, p_element = self.current_pressed_uri
+                self.current_pressed_uri = None
+                if (uri == p_uri and layout == p_layout and
+                    element == p_element):
+                    self.emit("uri-clicked-event", uri)
diff --git a/src/FukuiNoNamari/ThreadViewBase/res_layout.py b/src/FukuiNoNamari/ThreadViewBase/res_layout.py
new file mode 100644 (file)
index 0000000..724835c
--- /dev/null
@@ -0,0 +1,243 @@
+# Copyright (C) 2009 by Aiwota Programmer
+# aiwotaprog@tetteke.tk
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+
+import pygtk
+pygtk.require('2.0')
+import gtk
+from element import ElementEmpty, ElementText, ElementBoldText, ElementLink
+from element import get_approximate_char_height
+
+
+class ResLayout:
+# represent one line
+
+    def __init__(self, left_margin, resnum, pango_layout):
+        self.element_list = [ElementEmpty(pango_layout)]
+        self.width = 0
+        self.height = 0
+        self.pango_layout = pango_layout
+        self.left_margin = left_margin
+        self.resnum = resnum
+        self.posY = 0
+        self.list_index = 0
+
+    def add_text(self, text, bold, href):
+        if isinstance(self.element_list[0], ElementEmpty):
+            self.element_list = []
+
+        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 get_close_element_from_xy(self, x, y):
+        x= max(x, self.left_margin)
+        element = self.get_element_from_xy(x, y)
+        if element is None and len(self.element_list) != 0:
+            element = self.element_list[len(self.element_list) - 1]
+        return element
+
+    def recalc_char_widths(self):
+        for element in self.element_list:
+            element.recalc_char_widths()
+
+    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 + get_approximate_char_height(self.pango_layout.get_context())
+
+    def get_pixel_size(self):
+        return self.width, self.height
+
+    def get_text(self, selection_start, selection_end):
+        s_s = selection_start
+        e_s = selection_end
+        s_l, s_e, s_i = selection_start
+        e_l, e_e, e_i = selection_end
+
+        text = ""
+
+        if (s_l is None or s_e is None or s_i is None or
+            e_l is None or e_e is None or e_i is None or
+            self.posY < s_l.posY or self.posY > e_l.posY):
+
+            # nothing to do
+            pass
+
+        elif self.posY > s_s[0].posY and self.posY < e_s[0].posY:
+
+            for element in self.element_list:
+                text += element.get_text(selection=True)
+
+        elif self == s_s[0] and self == e_s[0]:
+
+            selection = False
+
+            for element in self.element_list:
+                if s_e == element:
+                    selection = True
+                    start = s_i
+                    end = 0xffffff
+                    if e_e == element:
+                        end = e_i
+                        selection = False
+                    text += element.get_text(selection=True, start_index=start,
+                                             end_index=end)
+                elif e_e == element:
+                    end = e_i
+                    selection = False
+                    text += element.get_text(
+                        selection=True, end_index=end)
+                elif selection:
+                    text += element.get_text(selection=True)
+
+        elif self == s_s[0]:
+
+            selection = False
+
+            for element in self.element_list:
+                if s_e == element:
+                    selection = True
+                    start = s_i
+                    text += element.get_text(selection=True, start_index=start)
+                elif selection:
+                    text += element.get_text(selection=True)
+
+        elif self == e_s[0]:
+
+            selection = True
+
+            for element in self.element_list:
+                if e_e == element:
+                    end = e_i
+                    text += element.get_text(selection=True, end_index=e_i)
+                    selection = False
+                elif selection:
+                    text += element.get_text(selection=True)
+
+        else:
+            # nothing to do
+            pass
+
+        return text
+
+
+    def draw(self, drawingarea, x_offset, y_offset,
+             start_selection, end_selection):
+
+        s_s = start_selection
+        e_s = end_selection
+
+        s_l, s_e, s_i = s_s
+        e_l, e_e, e_i = e_s
+
+        if (s_l is None or s_e is None or s_i is None or
+            e_l is None or e_e is None or e_i is None or
+            self.posY < s_l.posY or self.posY > e_l.posY):
+
+            for element in self.element_list:
+                element.draw(drawingarea, y_offset, self.pango_layout)
+
+        elif self.posY > s_s[0].posY and self.posY < e_s[0].posY:
+
+            for element in self.element_list:
+                element.draw(drawingarea, y_offset, self.pango_layout,
+                             selection=True)
+
+        elif self == s_s[0] and self == e_s[0]:
+
+            selection = False
+
+            for element in self.element_list:
+                if s_e == element:
+                    selection = True
+                    start = s_i
+                    end = 0xffffff
+                    if e_e == element:
+                        end = e_i
+                        selection = False
+                    element.draw(drawingarea, y_offset, self.pango_layout,
+                                 selection=True,
+                                 start_index=start,
+                                 end_index=end)
+                elif e_e == element:
+                    end = e_i
+                    selection = False
+                    element.draw(drawingarea, y_offset, self.pango_layout,
+                                 selection=True, end_index=end)
+                else:
+                    element.draw(drawingarea, y_offset, self.pango_layout,
+                                 selection=selection)
+
+        elif self == s_s[0]:
+
+            selection = False
+
+            for element in self.element_list:
+                if s_e == element:
+                    selection = True
+                    start = s_i
+                    element.draw(drawingarea, y_offset, self.pango_layout,
+                                 selection=selection, start_index = start)
+                else:
+                    element.draw(drawingarea, y_offset, self.pango_layout,
+                                 selection=selection)
+
+        elif self == e_s[0]:
+
+            selection = True
+
+            for element in self.element_list:
+                if e_e == element:
+                    end = e_i
+                    element.draw(drawingarea, y_offset, self.pango_layout,
+                                 selection=selection, end_index=e_i)
+                    selection = False
+                else:
+                    element.draw(drawingarea, y_offset, self.pango_layout,
+                                 selection=selection)
+
+        else:
+            for element in self.element_list:
+                element.draw(drawingarea, y_offset, self.pango_layout)
+
+    def clone(self):
+        import copy
+        layout = ResLayout(self.left_margin, self.resnum, self.pango_layout)
+        layout.element_list = []
+        for element in self.element_list:
+            layout.element_list.append(copy.copy(element))
+        return layout
diff --git a/src/FukuiNoNamari/ThreadViewBase/scrollable.py b/src/FukuiNoNamari/ThreadViewBase/scrollable.py
new file mode 100644 (file)
index 0000000..c88b051
--- /dev/null
@@ -0,0 +1,106 @@
+# Copyright (C) 2009 by Aiwota Programmer
+# aiwotaprog@tetteke.tk
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+
+import pygtk
+pygtk.require('2.0')
+import gtk
+import gobject
+
+
+class Scrollable(gtk.DrawingArea):
+
+    __gsignals__ = {
+        "configure-event": "override",
+        "set-scroll-adjustments" : (gobject.SIGNAL_RUN_LAST,
+                                    gobject.TYPE_NONE,
+                                    (gtk.Adjustment, gtk.Adjustment)),
+        "key-down": (gobject.SIGNAL_RUN_LAST | gobject.SIGNAL_ACTION,
+                     gobject.TYPE_NONE, ()),
+        "key-up": (gobject.SIGNAL_RUN_LAST | gobject.SIGNAL_ACTION,
+                   gobject.TYPE_NONE, ()),
+        "key-pagedown": (gobject.SIGNAL_RUN_LAST | gobject.SIGNAL_ACTION,
+                         gobject.TYPE_NONE, ()),
+        "key-pageup": (gobject.SIGNAL_RUN_LAST | gobject.SIGNAL_ACTION,
+                       gobject.TYPE_NONE, ()),
+        "key-home": (gobject.SIGNAL_RUN_LAST | gobject.SIGNAL_ACTION,
+                     gobject.TYPE_NONE, ()),
+        "key-end": (gobject.SIGNAL_RUN_LAST | gobject.SIGNAL_ACTION,
+                    gobject.TYPE_NONE, ()),
+        }
+
+    def __init__(self):
+        super(Scrollable, self).__init__()
+        self.set_set_scroll_adjustments_signal("set-scroll-adjustments")
+
+    def _set_adjustment_value(self, value):
+        value = min(self.adjustment.upper - self.adjustment.page_size, value)
+        value = max(self.adjustment.lower, value)
+        self.adjustment.value = value
+
+    def jump(self, value):
+        self._set_adjustment_value(value)
+
+    def jump_to_the_end(self):
+        value = self.adjustment.upper - self.adjustment.page_size
+        self._set_adjustment_value(value)
+
+    def transform_coordinate_gdk_to_adj(self, y):
+        return y + self.adjustment.value
+
+    def do_configure_event(self, event):
+        self.adjustment.page_size = self.allocation.height
+        self.adjustment.step_increment = 20
+        self.adjustment.page_increment = self.allocation.height
+
+        # re-set 'value' for prevent overflow
+        self._set_adjustment_value(self.adjustment.value)
+
+    def do_set_scroll_adjustments(self, hadjustment, vadjustment):
+        self.adjustment = vadjustment
+        if self.adjustment is not None:
+            self.adjustment.connect(
+                "value-changed", self.on_adjustment_value_changed)
+
+    def do_key_up(self):
+        self.jump(self.adjustment.value - self.adjustment.step_increment)
+
+    def do_key_down(self):
+        self.jump(self.adjustment.value + self.adjustment.step_increment)
+        
+    def do_key_pageup(self):
+        self.jump(self.adjustment.value - self.adjustment.page_increment)
+
+    def do_key_pagedown(self):
+        self.jump(self.adjustment.value + self.adjustment.page_increment)
+
+    def do_key_home(self):
+        self.jump(self.adjustment.lower)
+
+    def do_key_end(self):
+        self.jump_to_the_end()
+
+    def on_adjustment_value_changed(self, widget, data=None):
+        self.queue_draw()
+
+
+gtk.binding_entry_add_signal(Scrollable, gtk.keysyms.Up, 0, "key-up")
+gtk.binding_entry_add_signal(Scrollable, gtk.keysyms.Down, 0, "key-down")
+gtk.binding_entry_add_signal(Scrollable, gtk.keysyms.Page_Up, 0, "key-pageup")
+gtk.binding_entry_add_signal(Scrollable, gtk.keysyms.Page_Down, 0, "key-pagedown")
+gtk.binding_entry_add_signal(Scrollable, gtk.keysyms.Home, 0, "key-home")
+gtk.binding_entry_add_signal(Scrollable, gtk.keysyms.End, 0, "key-end")
+        
\ No newline at end of file
index 8459eca..0d3151a 100644 (file)
@@ -5,6 +5,7 @@ import re
 import urlparse
 import copy
 import thread_view
+from ThreadViewBase.element import ElementEmpty
 
 
 class ThreadViewForPopup(thread_view.ThreadView):
@@ -147,7 +148,7 @@ class ThreadPopup(gobject.GObject):
         for num in numlist:
             for layout in self._thread_view_list[0].res_layout_list:
                 # skip empty line
-                if isinstance(layout.element_list[0], thread_view.ElementEmpty):
+                if isinstance(layout.element_list[0], ElementEmpty):
                     continue
                 if layout.resnum == num:
                     clone = layout.clone()
index 5e5ec65..d47fe41 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (C) 2007 by Aiwota Programmer
+# Copyright (C) 2007, 2009 by Aiwota Programmer
 # aiwotaprog@tetteke.tk
 #
 # This program is free software; you can redistribute it and/or modify
 import pygtk
 pygtk.require('2.0')
 import gtk
-import pango
 import gobject
-import itertools
-from FukuiNoNamariExt import thread_view_extend
+from ThreadViewBase.drawable import Drawable
 
 
-def get_approximate_char_height(pango_context):
-    desc = pango_context.get_font_description()
-    font = pango_context.load_font(desc)
-    ink, log = font.get_glyph_extents(0)
-    return log[3] / pango.SCALE + 2
+class ThreadView(Drawable):
 
-
-class Rectangle:
-    def __init__(self, x, y, width, height):
-        self.x = x
-        self.y = y
-        self.width = width
-        self.height = height
-
-
-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 ElementEmpty:
-
-    def __init__(self, pango_layout):
-        self.pango_layout = pango_layout
-        self.initialize()
-
-    def recalc_char_widths(self):
-        pass
-        
-    def initialize(self):
-        self.line_list = []
-
-    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 xy_to_index(self, x, y):
-        return 0
-
-    def build_line_list(self, x, y, width, left_margin):
-        self.initialize()
-
-        line = Line(0, 0, Rectangle(
-            x, y, width - x,
-            get_approximate_char_height(self.pango_layout.get_context())))
-        self.line_list.append(line)
-
-        return width, y
-
-    def get_text(self, selection=False, start_index=0, end_index=0xffffff):
-        return ""
-
-    def draw(self, drawingarea, y_offset, pango_layout,
-             selection=False, start_index=0, end_index=0xffffff):
-        pass
-
-    
-class ElementText:
-
-    ch_width_dict = {}   # key: char, value: width
-
-    def __init__(self, text, pango_layout):
-        self.text = text
-        self.pango_layout = pango_layout
-
-        self.recalc_char_widths()
-
-        self.line_list = []
-
-    def recalc_char_widths(self):
-        self.widths = [i for i in itertools.repeat(0, len(self.text))]
-
-        dict = self._get_ch_width_dict()
-        need_to_get = False
-        for index, ch in enumerate(self.text):
-            if ch not in dict:
-                need_to_get = True
-                break
-            else:
-                width = dict[ch]
-                self.widths[index] = width
-
-        if need_to_get:
-            attrlist = self._get_attrs()
-            self.widths = thread_view_extend.get_char_width(
-                self.pango_layout.get_context(), self.text, attrlist)
-            for index, width in enumerate(self.widths):
-                dict[self.text[index]] = self.widths[index]
-
-    def _get_ch_width_dict(self):
-        return ElementText.ch_width_dict
-
-    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 xy_to_index(self, x, y):
-        for line in self.line_list:
-            top = line.rectangle.y
-            bottom = top + line.rectangle.height
-            if y >= top and y < bottom:
-                sum_of_widths = line.rectangle.x
-                index = line.start_index
-                for width in self.widths[line.start_index:line.end_index]:
-                    if sum_of_widths + width/2 > x:
-                        break
-                    sum_of_widths += width
-                    index += 1
-                return index
-        
-    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
-
-        ch_h = get_approximate_char_height(self.pango_layout.get_context())
-
-        for index, ch in enumerate(self.text):
-            ch_w = self.widths[index]
-            if current_line_x + current_line_width + ch_w > width:
-                line = Line(
-                    current_line_start_index, index,
-                    Rectangle(
-                    current_line_x, current_line_y,
-                    current_line_width, ch_h))
-                self.line_list.append(line)
-
-                current_line_start_index = index
-                current_line_x = left_margin
-                current_line_y += ch_h
-                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),
-                        Rectangle(current_line_x,
-                                          current_line_y,
-                                          current_line_width,
-                                          ch_h))
-            self.line_list.append(line)
-
-            current_line_x += current_line_width
-
-        return current_line_x, current_line_y
-
-    def get_text(self, selection=False, start_index=0, end_index=0xffffff):
-
-        text = ""
-
-        for line in self.line_list:
-
-            t = self.text[line.start_index:line.end_index]
-            if selection:
-                s = start_index - line.start_index
-                s = max(s, 0)
-                s = min(s, line.end_index - line.start_index)
-
-                e = end_index - line.start_index
-                e = min(e, line.end_index - line.start_index)
-                e = max(e, 0)
-
-                t = t[s:e]
-
-            text += t
-
-        return text
-
-    def draw(self, drawingarea, y_offset, pango_layout,
-             selection=False, start_index=0, end_index=0xffffff):
-
-        if drawingarea.get_property("has-focus"):
-            selection_fg = drawingarea.style.text[gtk.STATE_SELECTED]
-            selection_bg = drawingarea.style.base[gtk.STATE_SELECTED]
-        else:
-            selection_fg = drawingarea.style.text[gtk.STATE_ACTIVE]
-            selection_bg = drawingarea.style.base[gtk.STATE_ACTIVE]
-
-        for line in self.line_list:
-
-            text = self.text[line.start_index:line.end_index]
-            u_text = text.encode("utf8")
-            gc = drawingarea.window.new_gc()
-            gc.set_foreground(drawingarea.style.text[gtk.STATE_NORMAL])
-            gc.set_background(drawingarea.style.base[gtk.STATE_NORMAL])
-            attrs = self._get_attrs()
-            if selection:
-
-                s = start_index - line.start_index
-                s = max(s, 0)
-                s = min(s, line.end_index - line.start_index)
-                s = len(text[:s].encode("utf8"))
-
-                e = end_index - line.start_index
-                e = min(e, line.end_index - line.start_index)
-                e = max(e, 0)
-                e = len(text[:e].encode("utf8"))
-                    
-                selection_all_attr_fg = pango.AttrForeground(
-                    selection_fg.red, selection_fg.green, selection_fg.blue,
-                    s, e)
-                selection_all_attr_bg= pango.AttrBackground(
-                    selection_bg.red, selection_bg.green, selection_bg.blue,
-                    s, e)
-                attrs.insert(selection_all_attr_fg)
-                attrs.insert(selection_all_attr_bg)
-
-            pango_layout.set_text(u_text)
-            pango_layout.set_attributes(attrs)
-            drawingarea.window.draw_layout(
-                gc, int(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
-
-    def recalc_char_widths(self):
-        attrlist = self._get_attrs()
-        self.widths = thread_view_extend.get_char_width(
-            self.pango_layout.get_context(), self.text, 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 = [ElementEmpty(pango_layout)]
-        self.width = 0
-        self.height = 0
-        self.pango_layout = pango_layout
-        self.left_margin = left_margin
-        self.resnum = resnum
-        self.posY = 0
-        self.list_index = 0
-
-    def add_text(self, text, bold, href):
-        if isinstance(self.element_list[0], ElementEmpty):
-            self.element_list = []
-
-        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 get_close_element_from_xy(self, x, y):
-        x= max(x, self.left_margin)
-        element = self.get_element_from_xy(x, y)
-        if element is None and len(self.element_list) != 0:
-            element = self.element_list[len(self.element_list) - 1]
-        return element
-
-    def recalc_char_widths(self):
-        for element in self.element_list:
-            element.recalc_char_widths()
-
-    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 + get_approximate_char_height(self.pango_layout.get_context())
-
-    def get_pixel_size(self):
-        return self.width, self.height
-
-    def get_text(self, selection_start, selection_end):
-        s_s = selection_start
-        e_s = selection_end
-        s_l, s_e, s_i = selection_start
-        e_l, e_e, e_i = selection_end
-
-        text = ""
-
-        if (s_l is None or s_e is None or s_i is None or
-            e_l is None or e_e is None or e_i is None or
-            self.posY < s_l.posY or self.posY > e_l.posY):
-
-            # nothing to do
-            pass
-
-        elif self.posY > s_s[0].posY and self.posY < e_s[0].posY:
-
-            for element in self.element_list:
-                text += element.get_text(selection=True)
-
-        elif self == s_s[0] and self == e_s[0]:
-
-            selection = False
-
-            for element in self.element_list:
-                if s_e == element:
-                    selection = True
-                    start = s_i
-                    end = 0xffffff
-                    if e_e == element:
-                        end = e_i
-                        selection = False
-                    text += element.get_text(selection=True, start_index=start,
-                                             end_index=end)
-                elif e_e == element:
-                    end = e_i
-                    selection = False
-                    text += element.get_text(
-                        selection=True, end_index=end)
-                elif selection:
-                    text += element.get_text(selection=True)
-
-        elif self == s_s[0]:
-
-            selection = False
-
-            for element in self.element_list:
-                if s_e == element:
-                    selection = True
-                    start = s_i
-                    text += element.get_text(selection=True, start_index=start)
-                elif selection:
-                    text += element.get_text(selection=True)
-
-        elif self == e_s[0]:
-
-            selection = True
-
-            for element in self.element_list:
-                if e_e == element:
-                    end = e_i
-                    text += element.get_text(selection=True, end_index=e_i)
-                    selection = False
-                elif selection:
-                    text += element.get_text(selection=True)
-
-        else:
-            # nothing to do
-            pass
-
-        return text
-
-
-    def draw(self, drawingarea, x_offset, y_offset,
-             start_selection, end_selection):
-
-        s_s = start_selection
-        e_s = end_selection
-
-        s_l, s_e, s_i = s_s
-        e_l, e_e, e_i = e_s
-
-        if (s_l is None or s_e is None or s_i is None or
-            e_l is None or e_e is None or e_i is None or
-            self.posY < s_l.posY or self.posY > e_l.posY):
-
-            for element in self.element_list:
-                element.draw(drawingarea, y_offset, self.pango_layout)
-
-        elif self.posY > s_s[0].posY and self.posY < e_s[0].posY:
-
-            for element in self.element_list:
-                element.draw(drawingarea, y_offset, self.pango_layout,
-                             selection=True)
-
-        elif self == s_s[0] and self == e_s[0]:
-
-            selection = False
-
-            for element in self.element_list:
-                if s_e == element:
-                    selection = True
-                    start = s_i
-                    end = 0xffffff
-                    if e_e == element:
-                        end = e_i
-                        selection = False
-                    element.draw(drawingarea, y_offset, self.pango_layout,
-                                 selection=True,
-                                 start_index=start,
-                                 end_index=end)
-                elif e_e == element:
-                    end = e_i
-                    selection = False
-                    element.draw(drawingarea, y_offset, self.pango_layout,
-                                 selection=True, end_index=end)
-                else:
-                    element.draw(drawingarea, y_offset, self.pango_layout,
-                                 selection=selection)
-
-        elif self == s_s[0]:
-
-            selection = False
-
-            for element in self.element_list:
-                if s_e == element:
-                    selection = True
-                    start = s_i
-                    element.draw(drawingarea, y_offset, self.pango_layout,
-                                 selection=selection, start_index = start)
-                else:
-                    element.draw(drawingarea, y_offset, self.pango_layout,
-                                 selection=selection)
-
-        elif self == e_s[0]:
-
-            selection = True
-
-            for element in self.element_list:
-                if e_e == element:
-                    end = e_i
-                    element.draw(drawingarea, y_offset, self.pango_layout,
-                                 selection=selection, end_index=e_i)
-                    selection = False
-                else:
-                    element.draw(drawingarea, y_offset, self.pango_layout,
-                                 selection=selection)
-
-        else:
-            for element in self.element_list:
-                element.draw(drawingarea, y_offset, self.pango_layout)
-
-    def clone(self):
-        import copy
-        layout = ResLayout(self.left_margin, self.resnum, self.pango_layout)
-        layout.element_list = []
-        for element in self.element_list:
-            layout.element_list.append(copy.copy(element))
-        return layout
-
-
-class ThreadView(gtk.DrawingArea):
     __gsignals__ = {
         "cursor-over-link-event":
         (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (object, object, )),
-        "uri-clicked-event":
-        (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (object, )),
-        "set-scroll-adjustments" : (gobject.SIGNAL_RUN_LAST,
-                                   gobject.TYPE_NONE,
-                                   (gtk.Adjustment, gtk.Adjustment)),
         "populate-popup": (gobject.SIGNAL_RUN_LAST,
                            gobject.TYPE_NONE,
                            (gtk.Menu, )),
-        "move-position": (gobject.SIGNAL_RUN_LAST | gobject.SIGNAL_ACTION,
-                          gobject.TYPE_NONE, (gobject.TYPE_INT, )),
-        "configure-event": "override",
-        "expose-event": "override",
-        "motion-notify-event": "override",
         "button-press-event": "override",
-        "button-release-event": "override",
-        "style-set": "override",
-        }
-
-    hand_cursor = gtk.gdk.Cursor(gtk.gdk.HAND2)
-    regular_cursor = gtk.gdk.Cursor(gtk.gdk.XTERM)
-    arrow_cursor = gtk.gdk.Cursor(gtk.gdk.LEFT_PTR)
+          }
 
     def __init__(self):
         super(ThreadView, self).__init__()
-        self.set_set_scroll_adjustments_signal("set-scroll-adjustments")
         self.set_property("can_focus", True)
 
-        #self.adjustment = gtk.Adjustment()
-
         self.add_events(
             gtk.gdk.KEY_PRESS_MASK |
             gtk.gdk.SCROLL_MASK |
@@ -551,86 +44,8 @@ class ThreadView(gtk.DrawingArea):
             gtk.gdk.BUTTON_PRESS_MASK |
             gtk.gdk.BUTTON_RELEASE_MASK)
 
-        self.drawingarea_prev_width = 0
-
-        self.pango_layout = self.create_pango_layout("")
-
-        self.initialize_buffer()
-
-        self.button1_pressed = False
-        self.current_pressed_uri = None
-
-        # for selection
-        self.button_pressed_pt = (None, None, None)
-        self.button_moving_pt = (None, None, None)
-
-    def initialize_buffer(self):
-        self.res_layout_list = []
-
-    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()
-            res_layout.posY = last.posY + y
-        self.set_layout_width(res_layout)
-        res_layout.list_index = len(self.res_layout_list)
-        self.res_layout_list.append(res_layout)
-
-        x, y = res_layout.get_pixel_size()
-        self.adjustment.upper = res_layout.posY + y
-        # do not use this method in a loop because expensive.
-        # self.redraw()
-
-    def create_res_layout(self, left_margin, resnum):
-        return ResLayout(left_margin, resnum, self.pango_layout)
-
-    def set_layout_width(self, layout):
-        width = self.allocation.width
-        layout.set_width(width)
-        
-    def redraw(self):
-        self.queue_draw()
-
-    def wrap_relayout(self):
-        # before relayout, find top layout on gdkwindow
-        top_layout = self.get_layout_on_y(self.adjustment.value)
-        delta = 0
-
-        if top_layout is not None:
-            delta = top_layout.posY - self.adjustment.value
-
-        self.relayout()
-        self.drawingarea_prev_width = self.allocation.width
-
-        # after relayout, set adjustment.value to top layout's posY
-        if top_layout is not None:
-            self.adjustment.value = top_layout.posY - delta
-
-    def relayout(self):
-        width = self.allocation.width
-        sum_height = 0
-        for layout in self.res_layout_list:
-            layout.set_width(width)
-            layout.posY = sum_height
-            x, y = layout.get_pixel_size()
-            sum_height += y
-
-        self.adjustment.upper = sum_height
-
-    def _set_adjustment_value(self, value):
-        value = min(self.adjustment.upper - self.adjustment.page_size, value)
-        value = max(self.adjustment.lower, value)
-        self.adjustment.value = value
-
-    def jump(self, value):
-        self._set_adjustment_value(value)
-
     def jump_to_layout(self, layout):
         self.jump(layout.posY)
-        
-    def jump_to_the_end(self):
-        value = self.adjustment.upper - self.adjustment.page_size
-        self._set_adjustment_value(value)
 
     def jump_to_res(self, resnum):
         for layout in self.res_layout_list:
@@ -639,119 +54,6 @@ class ThreadView(gtk.DrawingArea):
                 return True
         return False
 
-
-    def _get_selection_start_end(self):
-        pressed_layout, pressed_element, pressed_index = self.button_pressed_pt
-        moving_layout, moving_element, moving_index = self.button_moving_pt
-
-        if (pressed_layout is None or pressed_element is None or
-            pressed_index is None or moving_layout is None or
-            moving_element is None or moving_index is None):
-            return (None, None, None), (None, None, None)
-        
-        if pressed_layout == moving_layout:
-            if pressed_element == moving_element:
-                if moving_index < pressed_index:
-                    return self.button_moving_pt, self.button_pressed_pt
-            else:
-                pressed_element_index = pressed_layout.element_list.index(
-                    pressed_element)
-                moving_element_index = moving_layout.element_list.index(
-                    moving_element)
-                if moving_element_index < pressed_element_index:
-                    return self.button_moving_pt, self.button_pressed_pt
-        elif moving_layout.posY < pressed_layout.posY:
-            return self.button_moving_pt, self.button_pressed_pt
-
-        return self.button_pressed_pt, self.button_moving_pt
-
-    def draw_viewport(self, area):
-        view_y = self.adjustment.value
-        self.window.draw_rectangle(
-            self.style.base_gc[0],
-            True, area.x, area.y, area.width, area.height)
-
-        selection_start, selection_end = self._get_selection_start_end()
-
-        top_layout = self.get_layout_on_y(view_y)
-        if top_layout is None:
-            return
-        #area_top = view_y + area.y
-        area_bottom = view_y + area.y + area.height
-
-        iter = range(top_layout.list_index, len(self.res_layout_list))
-        iter = itertools.imap(lambda index: self.res_layout_list[index], iter)
-        iter = itertools.takewhile(lambda lay: lay.posY <= area_bottom, iter)
-        for layout in iter:
-            layout.draw(self, 0, layout.posY - int(view_y),
-                selection_start, selection_end)
-
-    def transform_coordinate_gdk_to_adj(self, y):
-        return y + self.adjustment.value
-
-    def transform_coordinate_adj_to_layout(self, x, y, layout):
-        return x, y - layout.posY
-
-    def transform_coordinate_gdk_to_layout(self, x, y, layout):
-        return self.transform_coordinate_adj_to_layout(
-            x, self.transform_coordinate_gdk_to_adj(y), layout)
-
-    def get_layout_on_y(self, y):
-
-        def binary_search(lst, start, end, func):
-
-            if end - start <= 0:
-                return None
-
-            m = (start + end) / 2
-            ret = func(lst[m])
-
-            if ret == 0:
-                return m
-            if ret > 0:
-                return binary_search(lst, start, m, func)
-            return binary_search(lst, m+1, end, func)
-
-        def on_y(layout, _y):
-            top = layout.posY
-            width, height = layout.get_pixel_size()
-            bottom = top + height
-            if _y >= top and _y < bottom:
-                return 0
-            if _y < top:
-                return 1
-            return -1
-
-        ret = binary_search(
-            self.res_layout_list, 0, len(self.res_layout_list),
-            lambda x: on_y(x, y))
-        if ret is not None:
-            return self.res_layout_list[ret]
-        return None
-
-    def ptrpos_to_layout(self, x, y):
-        # transform coordinate, GdkWindow -> adjustment
-        adj_y = self.transform_coordinate_gdk_to_adj(y)
-        return self.get_layout_on_y(adj_y)
-
-    def ptrpos_to_uri(self,  x, y):
-        # x, y is GdkWindow coordinate
-
-        layout = self.ptrpos_to_layout(x, y)
-
-        if layout is None:
-            return None, None, None
-
-        # transform coordinate, GdkWindow -> res_layout_list
-        lay_x, lay_y = self.transform_coordinate_gdk_to_layout(x, y, layout)
-
-        # 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 get_selected_text(self):
         selection_start, selection_end = self._get_selection_start_end()
         s_l, s_e, s_i = selection_start
@@ -776,54 +78,6 @@ class ThreadView(gtk.DrawingArea):
 
         return text
 
-    def _set_button_pressed_pt(self, pt):
-        self.button_pressed_pt = (None, None, None)
-        if pt == None:
-            return
-
-        x, y = pt
-        layout = self.ptrpos_to_layout(x, y)
-        if layout is None:
-            return
-
-        x, y = self.transform_coordinate_gdk_to_layout(x, y, layout)
-        element = layout.get_element_from_xy(x, y)
-        if element is None:
-            element = layout.get_close_element_from_xy(x, y)
-
-        if element is None:
-            return
-
-        index = element.xy_to_index(x, y)
-        if index is None:
-            return
-
-        self.button_pressed_pt = (layout, element, index)
-
-    def _set_button_moving_pt(self, pt):
-        self.button_moving_pt = (None, None, None)
-        if pt == None:
-            return
-
-        x, y = pt
-        layout = self.ptrpos_to_layout(x, y)
-        if layout is None:
-            return
-
-        x, y = self.transform_coordinate_gdk_to_layout(x, y, layout)
-        element = layout.get_element_from_xy(x, y)
-        if element is None:
-            element = layout.get_close_element_from_xy(x, y)
-
-        if element is None:
-            return
-
-        index = element.xy_to_index(x, y)
-        if index is None:
-            return
-
-        self.button_moving_pt = (layout, element, index)
-
     def _copy_text_to_clipboard(self, text):
         if text and len(text) > 0:
             clip = gtk.Clipboard()
@@ -863,131 +117,12 @@ class ThreadView(gtk.DrawingArea):
         menu.show_all()
         menu.popup(None, None, None, button, time)
 
-    def do_expose_event(self, event):
-        self.draw_viewport(event.area)
-
-    def do_configure_event(self, event):
-        if event.width != self.drawingarea_prev_width:
-            self.wrap_relayout()
-
-        self.adjustment.page_size = self.allocation.height
-        self.adjustment.step_increment = 20
-        self.adjustment.page_increment = self.allocation.height
-
-        # re-set 'value' for prevent overflow
-        self._set_adjustment_value(self.adjustment.value)
-
-    def on_adjustment_value_changed(self, widget, data=None):
-        self.queue_draw()
-
-    def do_motion_notify_event(self, event):
-        if event.state & gtk.gdk.BUTTON1_MASK != gtk.gdk.BUTTON1_MASK:
-            self.button1_pressed = False
-
-        if self.button1_pressed and self.current_pressed_uri is None:
-            old_lay, old_elem, old_idx = self.button_moving_pt
-            self._set_button_moving_pt((event.x, event.y))
-            new_lay, new_elem, new_idx = self.button_moving_pt
-            if (old_lay != new_lay
-                or old_elem != new_elem
-                or old_idx != new_idx):
-                view_y = self.adjustment.value
-                o_y = old_lay.posY
-                n_y = new_lay.posY
-                o_width, o_height = old_lay.get_pixel_size()
-                n_width, n_height = new_lay.get_pixel_size()
-
-                y = min(o_y, n_y)
-                height = max(o_y, n_y) - y
-                if o_y > n_y: height += o_height
-                else: height += n_height
-
-                y -= view_y
-
-                self.queue_draw_area(0, y, n_width, height+1)
-                #self.window.process_updates(False)
-
-        cursor = ThreadView.regular_cursor
-
-        uri, layout, element = self.ptrpos_to_uri(event.x, event.y)
-        if layout is None:
-            cursor = ThreadView.arrow_cursor
-        else:
-            if uri is not None and uri != "":
-                cursor = ThreadView.hand_cursor
-                self.emit("cursor-over-link-event", event, uri)
-
-        self.window.set_cursor(cursor)
-
     def do_button_press_event(self, event):
-        if event.button == 1:
-            self.current_pressed_uri = None
-            self.button1_pressed = True
-            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)
-            else:
-                self._set_button_moving_pt((event.x, event.y))
-                self._set_button_pressed_pt((event.x, event.y))
-                self.queue_draw()
-                
-        elif event.button == 3:
+        Drawable.do_button_press_event(self, event)
+        if event.button == 3:
             self._do_popup(event.x, event.y, event.button, event.time)
             return True
 
-    def do_button_release_event(self, event):
-        if event.button == 1:
-            button1_pressed = self.button1_pressed
-            self.button1_pressed = False
-
-            if button1_pressed and self.current_pressed_uri is not None:
-                uri, layout, element = self.ptrpos_to_uri(event.x, event.y)
-                p_uri, p_layout, p_element = self.current_pressed_uri
-                self.current_pressed_uri = None
-                if (uri == p_uri and layout == p_layout and
-                    element == p_element):
-                    self.emit("uri-clicked-event", uri)
-
-    def do_style_set(self, previous_style):
-        if previous_style is None:
-            return False
-
-        new = self.style.font_desc.hash()
-        old = previous_style.font_desc.hash()
-        if new != old:
-            for layout in self.res_layout_list:
-                layout.recalc_char_widths()
-            self.wrap_relayout()
-
-    def do_move_position(self, keyval):
-        if keyval in (gtk.keysyms.Up, gtk.keysyms.Down,
-                            gtk.keysyms.Page_Up, gtk.keysyms.Page_Down,
-                            gtk.keysyms.Home):
-            value = self.adjustment.value
-            if keyval == gtk.keysyms.Up:
-                step_increment = self.adjustment.step_increment
-                value = value - step_increment
-            elif keyval == gtk.keysyms.Down:
-                step_increment = self.adjustment.step_increment
-                value = value + step_increment
-            elif keyval == gtk.keysyms.Page_Up:
-                step_increment = self.adjustment.page_increment
-                value = value - step_increment
-            elif keyval == gtk.keysyms.Page_Down:
-                step_increment = self.adjustment.page_increment
-                value = value + step_increment
-            elif keyval == gtk.keysyms.Home:
-                value = 0
-            self.jump(value)
-        elif keyval == gtk.keysyms.End:
-            self.jump_to_the_end()
-
-    def do_set_scroll_adjustments(self, hadjustment, vadjustment):
-        self.adjustment = vadjustment
-        if self.adjustment is not None:
-            self.adjustment.connect(
-                "value-changed", self.on_adjustment_value_changed)
-
     def on_copy_uri_activated(self, widget, uri):
         self._copy_text_to_clipboard(uri)
 
@@ -1003,20 +138,6 @@ class ThreadView(gtk.DrawingArea):
         self.emit("uri-clicked-event", selection)
 
 
-gtk.binding_entry_add_signal(ThreadView, gtk.keysyms.Up, 0,
-    "move-position", gobject.TYPE_INT, gtk.keysyms.Up)
-gtk.binding_entry_add_signal(ThreadView, gtk.keysyms.Down, 0,
-    "move-position", gobject.TYPE_INT, gtk.keysyms.Down)
-gtk.binding_entry_add_signal(ThreadView, gtk.keysyms.Page_Up, 0,
-    "move-position", gobject.TYPE_INT, gtk.keysyms.Page_Up)
-gtk.binding_entry_add_signal(ThreadView, gtk.keysyms.Page_Down, 0,
-    "move-position", gobject.TYPE_INT, gtk.keysyms.Page_Down)
-gtk.binding_entry_add_signal(ThreadView, gtk.keysyms.Home, 0,
-    "move-position", gobject.TYPE_INT, gtk.keysyms.Home)
-gtk.binding_entry_add_signal(ThreadView, gtk.keysyms.End, 0,
-    "move-position", gobject.TYPE_INT, gtk.keysyms.End)
-
-
 class ThreadViewScrollbar(gtk.VScrollbar):
 
     def __init__(self, adjustment=None):
index 3ec0869..aaa09f2 100644 (file)
@@ -381,7 +381,7 @@ class WinWrap(winwrapbase.WinWrapBase):
 
             self.http_get_dat(save_line_and_append_to_buffer)
             gtk.gdk.threads_enter()
-            self.threadview.redraw()
+            self.threadview.queue_draw()
             gtk.gdk.threads_leave()
             dat_file.close()