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)
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 |
"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
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()
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
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
- # before relayout, find top layout on gdkwindow
- top_layout = None
- delta = 0
- for lay in self.pangolayout:
- if lay.posY > self.adjustment.value:
- break
- top_layout = lay
+ 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 top_layout is not None:
- delta = top_layout.posY - self.vscrollbar.get_value()
+ 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 - delta)
+ 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)
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)
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()
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
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()