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=d7b0b432d43b33be0d83c624dacacbd60daa9a73;hp=a098e760b7b42416139323e44bed852db74eeb3e;hb=9ac09b5d8aba300c495f4e50faf452566e439180;hpb=b0a4289c9e1c8a8f1f2b48c808600cf3ce69d747 diff --git a/src/FukuiNoNamari/thread_view.py b/src/FukuiNoNamari/thread_view.py index a098e76..d7b0b43 100644 --- a/src/FukuiNoNamari/thread_view.py +++ b/src/FukuiNoNamari/thread_view.py @@ -19,9 +19,506 @@ import pygtk pygtk.require('2.0') import gtk import pango +import gobject +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 + + +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.HBox): + __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, )) + } + 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) @@ -29,12 +526,15 @@ class ThreadView(gtk.HBox): def __init__(self): gtk.HBox.__init__(self, False, 0) self.drawingarea = gtk.DrawingArea() + self.drawingarea.set_property("can_focus", True) + self.vscrollbar = gtk.VScrollbar() self.pack_start(self.drawingarea) self.pack_start(self.vscrollbar, expand=False) self.adjustment = self.vscrollbar.get_adjustment() self.drawingarea.add_events( + gtk.gdk.KEY_PRESS_MASK | gtk.gdk.SCROLL_MASK | gtk.gdk.POINTER_MOTION_MASK | gtk.gdk.BUTTON_PRESS_MASK | @@ -54,12 +554,16 @@ class ThreadView(gtk.HBox): "button-press-event", self.on_drawingarea_button_press_event) self.drawingarea.connect( "button-release-event", self.on_drawingarea_button_release_event) + self.drawingarea.connect( + "style-set", self.on_drawingarea_style_set) + self.drawingarea.connect( + "key-press-event", self.on_drawingarea_key_press_event) self.vscrollbar.connect( "value-changed", self.on_vscrollbar_value_changed) - self.initialize_buffer() + self.pango_layout = self.drawingarea.create_pango_layout("") - self.on_uri_clicked = self._on_uri_clicked + self.initialize_buffer() self.button1_pressed = False self.current_pressed_uri = None @@ -74,47 +578,67 @@ class ThreadView(gtk.HBox): self.menud_uri = None - def _on_uri_clicked(self, uri): - print uri, "clicked!!!!" + # for selection + self.button_pressed_pt = (None, None, None) + self.button_moving_pt = (None, None, None) 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) - self.redraw() + 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() 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() + 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.vscrollbar.get_value() + + self.relayout() + self.drawingarea_prev_width = self.drawingarea.allocation.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) + 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 + self.vscrollbar.set_range(0, sum_height) self.change_vscrollbar_visible() def change_vscrollbar_visible(self): - if self.adjustment.upper < self.adjustment.page_size: + if self.adjustment.upper <= self.adjustment.page_size: self.vscrollbar.hide() else: self.vscrollbar.show() @@ -130,51 +654,106 @@ 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 return False - def draw_viewport(self): + + 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.vscrollbar.get_value() self.drawingarea.window.draw_rectangle( self.drawingarea.style.base_gc[0], - True, 0, 0, - self.drawingarea.allocation.width, - self.drawingarea.allocation.height) - - gc = self.drawingarea.window.new_gc() - for layout in self.pangolayout: - 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) + 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.drawingarea, 0, layout.posY - int(view_y), + selection_start, selection_end) 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( 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_x = x adj_y = self.transform_coordinate_gdk_to_adj(y) - for lay in self.pangolayout: - width, height = lay.get_pixel_size() - if (adj_y >= lay.posY and adj_y < lay.posY + height and - adj_x >= lay.marginleft): - return lay - return None + return self.get_layout_on_y(adj_y) def ptrpos_to_uri(self, x, y): # x, y is GdkWindow coordinate @@ -184,46 +763,94 @@ 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) + # xy -> element + element = layout.get_element_from_xy(lay_x, lay_y) + if isinstance(element, ElementLink): + return element.href, layout, element - 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): + return None, layout, None - for i, (start, end, href) in enumerate(layout.urilist): - if idx >= start and idx < end: - return href, layout, i + def get_selected_text(self): + selection_start, selection_end = self._get_selection_start_end() + s_l, s_e, s_i = selection_start + e_l, e_e, e_i = selection_end - return None, layout, None + 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): + return "" - def on_drawingarea_expose_event(self, widget, event, data=None): - self.draw_viewport() + text = "" + index = s_l.list_index + end = e_l.list_index - def on_drawingarea_configure_event(self, widget, event, data=None): - if event.width != self.drawingarea_prev_width: + while index <= end: + layout = self.res_layout_list[index] + + text += layout.get_text(selection_start, selection_end) + if index != end: + text += "\n" + + index += 1 + + 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) - # before relayout, find top layout on gdkwindow - top_layout = None - for lay in self.pangolayout: - if lay.posY > self.adjustment.value: - break - top_layout = lay + if element is None: + return - self.relayout() - self.drawingarea_prev_width = event.width + index = element.xy_to_index(x, y) + if index is None: + return - # after relayout, set vscrollbar.value to top layout's posY - if top_layout is not None: - self.vscrollbar.set_value(top_layout.posY) + 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 on_drawingarea_expose_event(self, widget, event, data=None): + self.draw_viewport(event.area) + + def on_drawingarea_configure_event(self, widget, event, data=None): + if event.width != self.drawingarea_prev_width: + self.wrap_relayout() self.adjustment.page_size = self.drawingarea.allocation.height self.vscrollbar.set_increments(20, self.drawingarea.allocation.height) @@ -242,14 +869,38 @@ class ThreadView(gtk.HBox): 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.vscrollbar.get_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.drawingarea.queue_draw_area(0, y, n_width, height+1) + #self.drawingarea.window.process_updates(False) + 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: if uri is not None and uri != "": cursor = ThreadView.hand_cursor + self.emit("cursor-over-link-event", event, uri) self.drawingarea.window.set_cursor(cursor) @@ -257,13 +908,18 @@ 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) + else: + self._set_button_moving_pt((event.x, event.y)) + self._set_button_pressed_pt((event.x, event.y)) + self.drawingarea.queue_draw() + 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() @@ -276,9 +932,15 @@ class ThreadView(gtk.HBox): self.menu_openuri.uri = None self.menu_copylinkaddress.uri = None - self.menu_copyselection.hide() - self.menu_openasuri.hide() - self.menu_separator_selection.hide() + text = self.get_selected_text() + if text and len(text) > 0: + self.menu_copyselection.show() + self.menu_openasuri.show() + self.menu_separator_selection.show() + else: + self.menu_copyselection.hide() + self.menu_openasuri.hide() + self.menu_separator_selection.hide() self.popupmenu.popup(None, None, None, event.button, time) return True @@ -290,8 +952,46 @@ 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 - self.current_preesed_uri = None - if uri == p_uri and layout == p_layout and index == p_index: - self.on_uri_clicked(uri) + 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 on_drawingarea_style_set(self, widget, previous_style, data=None): + if previous_style is None: + return False + + new = widget.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 on_drawingarea_key_press_event(self, widget, event, data=None): + if event.type is not gtk.gdk.KEY_PRESS: + return + + if event.keyval in (gtk.keysyms.Up, gtk.keysyms.Down, + gtk.keysyms.Page_Up, gtk.keysyms.Page_Down, + gtk.keysyms.Home): + value = self.vscrollbar.get_value() + if event.keyval == gtk.keysyms.Up: + step_increment = self.adjustment.get_property("step-increment") + value = value - step_increment + elif event.keyval == gtk.keysyms.Down: + step_increment = self.adjustment.get_property("step-increment") + value = value + step_increment + elif event.keyval == gtk.keysyms.Page_Up: + step_increment = self.adjustment.get_property("page-increment") + value = value - step_increment + elif event.keyval == gtk.keysyms.Page_Down: + step_increment = self.adjustment.get_property("page-increment") + value = value + step_increment + elif event.keyval == gtk.keysyms.Home: + value = 0 + self.jump(value) + elif event.keyval == gtk.keysyms.End: + self.jump_to_the_end()