From 4d87fe6570a9d4d697a164fc66b4dd034c311392 Mon Sep 17 00:00:00 2001 From: Aiwota Programmer Date: Tue, 5 May 2009 01:04:11 +0900 Subject: [PATCH] The module "thread_view" is divided to the package "ThreadViewBase". (#16539) --- src/FukuiNoNamari/ThreadViewBase/__init__.py | 0 src/FukuiNoNamari/ThreadViewBase/drawable.py | 53 ++ src/FukuiNoNamari/ThreadViewBase/element.py | 288 +++++++ src/FukuiNoNamari/ThreadViewBase/layoutable.py | 165 ++++ .../ThreadViewBase/pointer_trackable.py | 185 +++++ src/FukuiNoNamari/ThreadViewBase/res_layout.py | 243 ++++++ src/FukuiNoNamari/ThreadViewBase/scrollable.py | 106 +++ src/FukuiNoNamari/thread_popup.py | 3 +- src/FukuiNoNamari/thread_view.py | 891 +-------------------- src/FukuiNoNamari/thread_window.py | 2 +- 10 files changed, 1049 insertions(+), 887 deletions(-) create mode 100644 src/FukuiNoNamari/ThreadViewBase/__init__.py create mode 100644 src/FukuiNoNamari/ThreadViewBase/drawable.py create mode 100644 src/FukuiNoNamari/ThreadViewBase/element.py create mode 100644 src/FukuiNoNamari/ThreadViewBase/layoutable.py create mode 100644 src/FukuiNoNamari/ThreadViewBase/pointer_trackable.py create mode 100644 src/FukuiNoNamari/ThreadViewBase/res_layout.py create mode 100644 src/FukuiNoNamari/ThreadViewBase/scrollable.py diff --git a/src/FukuiNoNamari/ThreadViewBase/__init__.py b/src/FukuiNoNamari/ThreadViewBase/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/FukuiNoNamari/ThreadViewBase/drawable.py b/src/FukuiNoNamari/ThreadViewBase/drawable.py new file mode 100644 index 0000000..7aef589 --- /dev/null +++ b/src/FukuiNoNamari/ThreadViewBase/drawable.py @@ -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 index 0000000..dc5da2f --- /dev/null +++ b/src/FukuiNoNamari/ThreadViewBase/element.py @@ -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 index 0000000..c3723bf --- /dev/null +++ b/src/FukuiNoNamari/ThreadViewBase/layoutable.py @@ -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 index 0000000..3033f6e --- /dev/null +++ b/src/FukuiNoNamari/ThreadViewBase/pointer_trackable.py @@ -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 index 0000000..724835c --- /dev/null +++ b/src/FukuiNoNamari/ThreadViewBase/res_layout.py @@ -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 index 0000000..c88b051 --- /dev/null +++ b/src/FukuiNoNamari/ThreadViewBase/scrollable.py @@ -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 diff --git a/src/FukuiNoNamari/thread_popup.py b/src/FukuiNoNamari/thread_popup.py index 8459eca..0d3151a 100644 --- a/src/FukuiNoNamari/thread_popup.py +++ b/src/FukuiNoNamari/thread_popup.py @@ -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() diff --git a/src/FukuiNoNamari/thread_view.py b/src/FukuiNoNamari/thread_view.py index 5e5ec65..d47fe41 100644 --- a/src/FukuiNoNamari/thread_view.py +++ b/src/FukuiNoNamari/thread_view.py @@ -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 @@ -18,532 +18,25 @@ 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): diff --git a/src/FukuiNoNamari/thread_window.py b/src/FukuiNoNamari/thread_window.py index 3ec0869..aaa09f2 100644 --- a/src/FukuiNoNamari/thread_window.py +++ b/src/FukuiNoNamari/thread_window.py @@ -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() -- 2.11.0