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