1 # Copyright (C) 2007 by Aiwota Programmer
2 # aiwotaprog@tetteke.tk
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 2 of the License, or
7 # (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
22 from FukuiNoNamariExt import thread_view_extend
29 def __init__(self, start_index, end_index, rectangle):
30 self.start_index = start_index
31 self.end_index = end_index
32 self.rectangle = rectangle
34 def is_on_xy(self, x, y):
35 left = self.rectangle.x
36 right = left + self.rectangle.width
37 top = self.rectangle.y
38 bottom = top + self.rectangle.height
39 return x >= left and x < right and y >= top and y < bottom
44 def __init__(self, text, pango_layout):
47 attrlist = self._get_attrs()
48 self.widths = thread_view_extend.get_char_width(
49 pango_layout.get_context(), text, attrlist)
54 attrs = pango.AttrList()
57 def is_on_xy(self, x, y):
58 for line in self.line_list:
59 if line.is_on_xy(x, y):
63 def build_line_list(self, x, y, width, left_margin):
66 current_line_start_index = 0
69 current_line_width = 0
71 for index, ch in enumerate(self.text):
72 ch_w = self.widths[index]
74 if current_line_x + current_line_width + ch_w > width:
76 current_line_start_index, index,
78 current_line_x, current_line_y,
79 current_line_width, Line.HEIGHT))
80 self.line_list.append(line)
82 current_line_start_index = index
83 current_line_x = left_margin
84 current_line_y += Line.HEIGHT
85 current_line_width = ch_w
87 current_line_width += ch_w
89 if current_line_start_index < len(self.text):
90 line = Line(current_line_start_index, len(self.text),
91 gtk.gdk.Rectangle(current_line_x,
95 self.line_list.append(line)
97 current_line_x += current_line_width
99 return current_line_x, current_line_y
101 def draw(self, drawable, y_offset, pango_layout):
103 for line in self.line_list:
104 text = self.text[line.start_index:line.end_index]
105 gc = drawable.new_gc()
106 pango_layout.set_text(text)
107 attrs = self._get_attrs()
109 pango_layout.set_attributes(attrs)
110 drawable.draw_layout(gc,
111 line.rectangle.x, line.rectangle.y + y_offset,
115 class ElementBoldText(ElementText):
117 def _get_attrs(self):
118 attrlist = pango.AttrList()
119 attr = pango.AttrWeight(pango.WEIGHT_BOLD,
121 attrlist.insert(attr)
125 class ElementLink(ElementText):
127 def __init__(self, text, href, pango_layout):
129 ElementText.__init__(self, text, pango_layout)
131 def _get_attrs(self):
132 attrlist = pango.AttrList()
133 attr = pango.AttrUnderline(pango.UNDERLINE_SINGLE,
135 attrlist.insert(attr)
142 def __init__(self, left_margin, resnum, pango_layout):
143 self.element_list = []
146 self.pango_layout = pango_layout
147 self.left_margin = left_margin
151 def add_text(self, text, bold, href):
153 element = ElementLink(text, href, self.pango_layout)
154 self.element_list.append(element)
156 element = ElementBoldText(text, self.pango_layout)
157 self.element_list.append(element)
159 element = ElementText(text, self.pango_layout)
160 self.element_list.append(element)
162 def get_element_from_xy(self, x, y):
163 for element in self.element_list:
164 if element.is_on_xy(x, y):
168 def set_width(self, width):
172 current_x = self.left_margin
175 for element in self.element_list:
176 current_x, current_y = element.build_line_list(
177 current_x, current_y, width, self.left_margin)
179 self.height = current_y + Line.HEIGHT
181 def get_pixel_size(self):
182 return self.width, self.height
184 def draw(self, drawable, x_offset, y_offset):
186 for element in self.element_list:
187 element.draw(drawable, y_offset, self.pango_layout)
190 class ThreadView(gtk.HBox):
191 hand_cursor = gtk.gdk.Cursor(gtk.gdk.HAND2)
192 regular_cursor = gtk.gdk.Cursor(gtk.gdk.XTERM)
193 arrow_cursor = gtk.gdk.Cursor(gtk.gdk.LEFT_PTR)
196 gtk.HBox.__init__(self, False, 0)
197 self.drawingarea = gtk.DrawingArea()
198 self.vscrollbar = gtk.VScrollbar()
199 self.pack_start(self.drawingarea)
200 self.pack_start(self.vscrollbar, expand=False)
201 self.adjustment = self.vscrollbar.get_adjustment()
203 self.drawingarea.add_events(
204 gtk.gdk.SCROLL_MASK |
205 gtk.gdk.POINTER_MOTION_MASK |
206 gtk.gdk.BUTTON_PRESS_MASK |
207 gtk.gdk.BUTTON_RELEASE_MASK)
209 self.drawingarea_prev_width = 0
211 self.drawingarea.connect(
212 "expose-event", self.on_drawingarea_expose_event)
213 self.drawingarea.connect(
214 "configure-event", self.on_drawingarea_configure_event)
215 self.drawingarea.connect(
216 "scroll-event", self.on_drawingarea_scroll_event)
217 self.drawingarea.connect(
218 "motion-notify-event", self.on_drawingrarea_motion_notify_event)
219 self.drawingarea.connect(
220 "button-press-event", self.on_drawingarea_button_press_event)
221 self.drawingarea.connect(
222 "button-release-event", self.on_drawingarea_button_release_event)
223 self.vscrollbar.connect(
224 "value-changed", self.on_vscrollbar_value_changed)
226 self.pango_layout = self.drawingarea.create_pango_layout("")
228 self.initialize_buffer()
230 self.on_uri_clicked = self._on_uri_clicked
232 self.button1_pressed = False
233 self.current_pressed_uri = None
235 self.popupmenu = None
236 self.menu_openuri = None
237 self.menu_copylinkaddress = None
238 self.menu_separator_link = None
239 self.menu_copyselection = None
240 self.menu_openasuri = None
241 self.menu_separator_selection = None
243 self.menud_uri = None
245 def _on_uri_clicked(self, uri):
246 print uri, "clicked!!!!"
248 def initialize_buffer(self):
249 self.res_layout_list = []
251 def add_layout(self, res_layout):
252 if (len(self.res_layout_list) != 0):
253 last = self.res_layout_list[len(self.res_layout_list)-1]
254 x, y = last.get_pixel_size()
255 res_layout.posY = last.posY + y
256 self.set_layout_width(res_layout)
257 self.res_layout_list.append(res_layout)
258 x, y = res_layout.get_pixel_size()
259 self.adjustment.upper = res_layout.posY + y
261 self.change_vscrollbar_visible()
263 def create_res_layout(self, left_margin, resnum):
264 return ResLayout(left_margin, resnum, self.pango_layout)
266 def set_layout_width(self, layout):
267 width = self.drawingarea.allocation.width
268 layout.set_width(width)
271 self.drawingarea.queue_draw()
274 width = self.drawingarea.allocation.width
276 for layout in self.res_layout_list:
277 layout.set_width(width)
278 layout.posY = sum_height
279 x, y = layout.get_pixel_size()
281 self.vscrollbar.set_range(0, sum_height)
282 self.change_vscrollbar_visible()
284 def change_vscrollbar_visible(self):
285 if self.adjustment.upper < self.adjustment.page_size:
286 self.vscrollbar.hide()
288 self.vscrollbar.show()
290 def jump(self, value):
291 self.vscrollbar.set_value(value)
293 def jump_to_layout(self, layout):
294 self.jump(layout.posY)
296 def jump_to_the_end(self):
297 value = self.adjustment.upper - self.adjustment.page_size
298 self.vscrollbar.set_value(value)
300 def jump_to_res(self, resnum):
301 for layout in self.res_layout_list:
302 if layout.resnum == resnum:
303 self.jump_to_layout(layout)
307 def draw_viewport(self):
308 view_y = self.vscrollbar.get_value()
309 self.drawingarea.window.draw_rectangle(
310 self.drawingarea.style.base_gc[0],
312 self.drawingarea.allocation.width,
313 self.drawingarea.allocation.height)
315 gc = self.drawingarea.window.new_gc()
316 for layout in self.res_layout_list:
317 w, h = layout.get_pixel_size()
318 layout_top = layout.posY
319 layout_bottom = layout.posY + h
321 area_bottom = view_y + self.drawingarea.allocation.height
322 if layout_top <= area_bottom and layout_bottom >= area_top:
323 layout.draw(self.drawingarea.window,
324 0, layout.posY - int(view_y))
326 def transform_coordinate_gdk_to_adj(self, y):
327 return y + self.vscrollbar.get_value()
329 def transform_coordinate_adj_to_layout(self, x, y, layout):
330 return x, y - layout.posY
332 def transform_coordinate_gdk_to_layout(self, x, y, layout):
333 return self.transform_coordinate_adj_to_layout(
334 x, self.transform_coordinate_gdk_to_adj(y), layout)
336 def ptrpos_to_layout(self, x, y):
337 # transform coordinate, GdkWindow -> adjustment
339 adj_y = self.transform_coordinate_gdk_to_adj(y)
340 for lay in self.res_layout_list:
341 width, height = lay.get_pixel_size()
342 if (adj_y >= lay.posY and adj_y < lay.posY + height and
343 adj_x >= lay.left_margin):
347 def ptrpos_to_uri(self, x, y):
348 # x, y is GdkWindow coordinate
350 layout = self.ptrpos_to_layout(x, y)
353 return None, None, None
355 # transform coordinate, GdkWindow -> res_layout_list
356 lay_x, lay_y = self.transform_coordinate_gdk_to_layout(x, y, layout)
359 element = layout.get_element_from_xy(lay_x, lay_y)
360 if isinstance(element, ElementLink):
361 return element.href, layout, element
363 return None, layout, None
365 def on_drawingarea_expose_event(self, widget, event, data=None):
368 def on_drawingarea_configure_event(self, widget, event, data=None):
369 if event.width != self.drawingarea_prev_width:
371 # before relayout, find top layout on gdkwindow
374 for lay in self.res_layout_list:
375 if lay.posY > self.adjustment.value:
379 if top_layout is not None:
380 delta = top_layout.posY - self.vscrollbar.get_value()
383 self.drawingarea_prev_width = event.width
385 # after relayout, set vscrollbar.value to top layout's posY
386 if top_layout is not None:
387 self.vscrollbar.set_value(top_layout.posY - delta)
389 self.adjustment.page_size = self.drawingarea.allocation.height
390 self.vscrollbar.set_increments(20, self.drawingarea.allocation.height)
392 # re-set 'value' for prevent overflow
393 self.vscrollbar.set_value(self.vscrollbar.get_value())
394 self.change_vscrollbar_visible()
396 def on_vscrollbar_value_changed(self, widget, data=None):
397 self.drawingarea.queue_draw()
399 def on_drawingarea_scroll_event(self, widget, event, data=None):
400 self.vscrollbar.emit("scroll-event", event)
402 def on_drawingrarea_motion_notify_event(self, widget, event, data=None):
403 if event.state & gtk.gdk.BUTTON1_MASK != gtk.gdk.BUTTON1_MASK:
404 self.button1_pressed = False
406 cursor = ThreadView.regular_cursor
408 uri, layout, element = self.ptrpos_to_uri(event.x, event.y)
410 cursor = ThreadView.arrow_cursor
412 if uri is not None and uri != "":
413 cursor = ThreadView.hand_cursor
415 self.drawingarea.window.set_cursor(cursor)
417 def on_drawingarea_button_press_event(self, widget, event, data=None):
418 if event.button == 1:
419 self.current_pressed_uri = None
420 self.button1_pressed = True
421 uri, layout, element = self.ptrpos_to_uri(event.x, event.y)
422 if uri is not None and layout is not None and element is not None:
423 self.current_pressed_uri = (uri, layout, element)
424 elif event.button == 3:
426 uri, layout, element = self.ptrpos_to_uri(event.x, event.y)
427 if uri is not None and layout is not None and element is not None:
428 self.menu_openuri.show()
429 self.menu_copylinkaddress.show()
430 self.menu_separator_link.show()
431 self.menu_openuri.uri = uri
432 self.menu_copylinkaddress.uri = uri
434 self.menu_openuri.hide()
435 self.menu_copylinkaddress.hide()
436 self.menu_separator_link.hide()
437 self.menu_openuri.uri = None
438 self.menu_copylinkaddress.uri = None
440 self.menu_copyselection.hide()
441 self.menu_openasuri.hide()
442 self.menu_separator_selection.hide()
444 self.popupmenu.popup(None, None, None, event.button, time)
448 def on_drawingarea_button_release_event(self, widget, event, data=None):
449 if event.button == 1:
450 button1_pressed = self.button1_pressed
451 self.button1_pressed = False
453 if button1_pressed and self.current_pressed_uri is not None:
454 uri, layout, element = self.ptrpos_to_uri(event.x, event.y)
455 p_uri, p_layout, p_element = self.current_pressed_uri
456 self.current_preesed_uri = None
457 if (uri == p_uri and layout == p_layout and
458 element == p_element):
459 self.on_uri_clicked(uri)