• R/O
  • HTTP
  • SSH
  • HTTPS

fukui-no-namari: Commit

Fukui no Namari


Commit MetaInfo

Revision4d87fe6570a9d4d697a164fc66b4dd034c311392 (tree)
Time2009-05-05 01:04:11
AuthorAiwota Programmer <aiwotaprog@tett...>
CommiterAiwota Programmer

Log Message

The module "thread_view" is divided to the package "ThreadViewBase". (#16539)

Change Summary

Incremental Difference

--- /dev/null
+++ b/src/FukuiNoNamari/ThreadViewBase/drawable.py
@@ -0,0 +1,53 @@
1+# Copyright (C) 2009 by Aiwota Programmer
2+# aiwotaprog@tetteke.tk
3+#
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.
8+#
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.
13+#
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
17+
18+import pygtk
19+pygtk.require('2.0')
20+import gtk
21+import itertools
22+from pointer_trackable import PointerTrackable
23+
24+
25+class Drawable(PointerTrackable):
26+
27+ __gsignals__ = {
28+ "expose-event": "override",
29+ }
30+
31+ def draw_viewport(self, area):
32+ view_y = self.adjustment.value
33+ self.window.draw_rectangle(
34+ self.style.base_gc[0],
35+ True, area.x, area.y, area.width, area.height)
36+
37+ selection_start, selection_end = self._get_selection_start_end()
38+
39+ top_layout = self.get_layout_on_y(view_y)
40+ if top_layout is None:
41+ return
42+ #area_top = view_y + area.y
43+ area_bottom = view_y + area.y + area.height
44+
45+ iter = range(top_layout.list_index, len(self.res_layout_list))
46+ iter = itertools.imap(lambda index: self.res_layout_list[index], iter)
47+ iter = itertools.takewhile(lambda lay: lay.posY <= area_bottom, iter)
48+ for layout in iter:
49+ layout.draw(self, 0, layout.posY - int(view_y),
50+ selection_start, selection_end)
51+
52+ def do_expose_event(self, event):
53+ self.draw_viewport(event.area)
--- /dev/null
+++ b/src/FukuiNoNamari/ThreadViewBase/element.py
@@ -0,0 +1,288 @@
1+# Copyright (C) 2009 by Aiwota Programmer
2+# aiwotaprog@tetteke.tk
3+#
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.
8+#
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.
13+#
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
17+
18+import pygtk
19+pygtk.require('2.0')
20+import gtk
21+import pango
22+import itertools
23+from FukuiNoNamariExt import thread_view_extend
24+
25+def get_approximate_char_height(pango_context):
26+ desc = pango_context.get_font_description()
27+ font = pango_context.load_font(desc)
28+ ink, log = font.get_glyph_extents(0)
29+ return log[3] / pango.SCALE + 2
30+
31+
32+class Rectangle:
33+ def __init__(self, x, y, width, height):
34+ self.x = x
35+ self.y = y
36+ self.width = width
37+ self.height = height
38+
39+
40+class Line:
41+
42+ HEIGHT = 15
43+
44+ def __init__(self, start_index, end_index, rectangle):
45+ self.start_index = start_index
46+ self.end_index = end_index
47+ self.rectangle = rectangle
48+
49+ def is_on_xy(self, x, y):
50+ left = self.rectangle.x
51+ right = left + self.rectangle.width
52+ top = self.rectangle.y
53+ bottom = top + self.rectangle.height
54+ return x >= left and x < right and y >= top and y < bottom
55+
56+
57+class ElementEmpty:
58+
59+ def __init__(self, pango_layout):
60+ self.pango_layout = pango_layout
61+ self.initialize()
62+
63+ def recalc_char_widths(self):
64+ pass
65+
66+ def initialize(self):
67+ self.line_list = []
68+
69+ def is_on_xy(self, x, y):
70+ for line in self.line_list:
71+ if line.is_on_xy(x, y):
72+ return True
73+ return False
74+
75+ def xy_to_index(self, x, y):
76+ return 0
77+
78+ def build_line_list(self, x, y, width, left_margin):
79+ self.initialize()
80+
81+ line = Line(0, 0, Rectangle(
82+ x, y, width - x,
83+ get_approximate_char_height(self.pango_layout.get_context())))
84+ self.line_list.append(line)
85+
86+ return width, y
87+
88+ def get_text(self, selection=False, start_index=0, end_index=0xffffff):
89+ return ""
90+
91+ def draw(self, drawingarea, y_offset, pango_layout,
92+ selection=False, start_index=0, end_index=0xffffff):
93+ pass
94+
95+
96+class ElementText:
97+
98+ ch_width_dict = {} # key: char, value: width
99+
100+ def __init__(self, text, pango_layout):
101+ self.text = text
102+ self.pango_layout = pango_layout
103+
104+ self.recalc_char_widths()
105+
106+ self.line_list = []
107+
108+ def recalc_char_widths(self):
109+ self.widths = [i for i in itertools.repeat(0, len(self.text))]
110+
111+ dict = self._get_ch_width_dict()
112+ need_to_get = False
113+ for index, ch in enumerate(self.text):
114+ if ch not in dict:
115+ need_to_get = True
116+ break
117+ else:
118+ width = dict[ch]
119+ self.widths[index] = width
120+
121+ if need_to_get:
122+ attrlist = self._get_attrs()
123+ self.widths = thread_view_extend.get_char_width(
124+ self.pango_layout.get_context(), self.text, attrlist)
125+ for index, width in enumerate(self.widths):
126+ dict[self.text[index]] = self.widths[index]
127+
128+ def _get_ch_width_dict(self):
129+ return ElementText.ch_width_dict
130+
131+ def _get_attrs(self):
132+ attrs = pango.AttrList()
133+ return attrs
134+
135+ def is_on_xy(self, x, y):
136+ for line in self.line_list:
137+ if line.is_on_xy(x, y):
138+ return True
139+ return False
140+
141+ def xy_to_index(self, x, y):
142+ for line in self.line_list:
143+ top = line.rectangle.y
144+ bottom = top + line.rectangle.height
145+ if y >= top and y < bottom:
146+ sum_of_widths = line.rectangle.x
147+ index = line.start_index
148+ for width in self.widths[line.start_index:line.end_index]:
149+ if sum_of_widths + width/2 > x:
150+ break
151+ sum_of_widths += width
152+ index += 1
153+ return index
154+
155+ def build_line_list(self, x, y, width, left_margin):
156+ self.line_list = []
157+
158+ current_line_start_index = 0
159+ current_line_x = x
160+ current_line_y = y
161+ current_line_width = 0
162+
163+ ch_h = get_approximate_char_height(self.pango_layout.get_context())
164+
165+ for index, ch in enumerate(self.text):
166+ ch_w = self.widths[index]
167+ if current_line_x + current_line_width + ch_w > width:
168+ line = Line(
169+ current_line_start_index, index,
170+ Rectangle(
171+ current_line_x, current_line_y,
172+ current_line_width, ch_h))
173+ self.line_list.append(line)
174+
175+ current_line_start_index = index
176+ current_line_x = left_margin
177+ current_line_y += ch_h
178+ current_line_width = ch_w
179+ else:
180+ current_line_width += ch_w
181+
182+ if current_line_start_index < len(self.text):
183+ line = Line(current_line_start_index, len(self.text),
184+ Rectangle(current_line_x,
185+ current_line_y,
186+ current_line_width,
187+ ch_h))
188+ self.line_list.append(line)
189+
190+ current_line_x += current_line_width
191+
192+ return current_line_x, current_line_y
193+
194+ def get_text(self, selection=False, start_index=0, end_index=0xffffff):
195+
196+ text = ""
197+
198+ for line in self.line_list:
199+
200+ t = self.text[line.start_index:line.end_index]
201+ if selection:
202+ s = start_index - line.start_index
203+ s = max(s, 0)
204+ s = min(s, line.end_index - line.start_index)
205+
206+ e = end_index - line.start_index
207+ e = min(e, line.end_index - line.start_index)
208+ e = max(e, 0)
209+
210+ t = t[s:e]
211+
212+ text += t
213+
214+ return text
215+
216+ def draw(self, drawingarea, y_offset, pango_layout,
217+ selection=False, start_index=0, end_index=0xffffff):
218+
219+ if drawingarea.get_property("has-focus"):
220+ selection_fg = drawingarea.style.text[gtk.STATE_SELECTED]
221+ selection_bg = drawingarea.style.base[gtk.STATE_SELECTED]
222+ else:
223+ selection_fg = drawingarea.style.text[gtk.STATE_ACTIVE]
224+ selection_bg = drawingarea.style.base[gtk.STATE_ACTIVE]
225+
226+ for line in self.line_list:
227+
228+ text = self.text[line.start_index:line.end_index]
229+ u_text = text.encode("utf8")
230+ gc = drawingarea.window.new_gc()
231+ gc.set_foreground(drawingarea.style.text[gtk.STATE_NORMAL])
232+ gc.set_background(drawingarea.style.base[gtk.STATE_NORMAL])
233+ attrs = self._get_attrs()
234+ if selection:
235+
236+ s = start_index - line.start_index
237+ s = max(s, 0)
238+ s = min(s, line.end_index - line.start_index)
239+ s = len(text[:s].encode("utf8"))
240+
241+ e = end_index - line.start_index
242+ e = min(e, line.end_index - line.start_index)
243+ e = max(e, 0)
244+ e = len(text[:e].encode("utf8"))
245+
246+ selection_all_attr_fg = pango.AttrForeground(
247+ selection_fg.red, selection_fg.green, selection_fg.blue,
248+ s, e)
249+ selection_all_attr_bg= pango.AttrBackground(
250+ selection_bg.red, selection_bg.green, selection_bg.blue,
251+ s, e)
252+ attrs.insert(selection_all_attr_fg)
253+ attrs.insert(selection_all_attr_bg)
254+
255+ pango_layout.set_text(u_text)
256+ pango_layout.set_attributes(attrs)
257+ drawingarea.window.draw_layout(
258+ gc, int(line.rectangle.x), line.rectangle.y + y_offset,
259+ pango_layout)
260+
261+
262+class ElementBoldText(ElementText):
263+
264+ def _get_attrs(self):
265+ attrlist = pango.AttrList()
266+ attr = pango.AttrWeight(pango.WEIGHT_BOLD,
267+ end_index=0xffffff)
268+ attrlist.insert(attr)
269+ return attrlist
270+
271+ def recalc_char_widths(self):
272+ attrlist = self._get_attrs()
273+ self.widths = thread_view_extend.get_char_width(
274+ self.pango_layout.get_context(), self.text, attrlist)
275+
276+
277+class ElementLink(ElementText):
278+
279+ def __init__(self, text, href, pango_layout):
280+ self.href = href
281+ ElementText.__init__(self, text, pango_layout)
282+
283+ def _get_attrs(self):
284+ attrlist = pango.AttrList()
285+ attr = pango.AttrUnderline(pango.UNDERLINE_SINGLE,
286+ end_index=0xffffff)
287+ attrlist.insert(attr)
288+ return attrlist
--- /dev/null
+++ b/src/FukuiNoNamari/ThreadViewBase/layoutable.py
@@ -0,0 +1,165 @@
1+# Copyright (C) 2009 by Aiwota Programmer
2+# aiwotaprog@tetteke.tk
3+#
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.
8+#
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.
13+#
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
17+
18+import pygtk
19+pygtk.require('2.0')
20+import gtk
21+from scrollable import Scrollable
22+from res_layout import ResLayout
23+from element import ElementLink
24+
25+
26+class Layoutable(Scrollable):
27+
28+ __gsignals__ = {
29+ "configure-event": "override",
30+ "style-set": "override",
31+ }
32+
33+ def __init__(self):
34+ super(Layoutable, self).__init__()
35+ self.initialize_buffer()
36+ self.pango_layout = self.create_pango_layout("")
37+ self.drawingarea_prev_width = 0
38+
39+ def initialize_buffer(self):
40+ self.res_layout_list = []
41+
42+ def transform_coordinate_adj_to_layout(self, x, y, layout):
43+ return x, y - layout.posY
44+
45+ def transform_coordinate_gdk_to_layout(self, x, y, layout):
46+ return self.transform_coordinate_adj_to_layout(
47+ x, self.transform_coordinate_gdk_to_adj(y), layout)
48+
49+ def ptrpos_to_layout(self, x, y):
50+ # transform coordinate, GdkWindow -> adjustment
51+ adj_y = self.transform_coordinate_gdk_to_adj(y)
52+ return self.get_layout_on_y(adj_y)
53+
54+ def ptrpos_to_uri(self, x, y):
55+ # x, y is GdkWindow coordinate
56+
57+ layout = self.ptrpos_to_layout(x, y)
58+
59+ if layout is None:
60+ return None, None, None
61+
62+ # transform coordinate, GdkWindow -> res_layout_list
63+ lay_x, lay_y = self.transform_coordinate_gdk_to_layout(x, y, layout)
64+
65+ # xy -> element
66+ element = layout.get_element_from_xy(lay_x, lay_y)
67+ if isinstance(element, ElementLink):
68+ return element.href, layout, element
69+
70+ return None, layout, None
71+
72+ def add_layout(self, res_layout):
73+ if (len(self.res_layout_list) != 0):
74+ last = self.res_layout_list[len(self.res_layout_list)-1]
75+ x, y = last.get_pixel_size()
76+ res_layout.posY = last.posY + y
77+ self.set_layout_width(res_layout)
78+ res_layout.list_index = len(self.res_layout_list)
79+ self.res_layout_list.append(res_layout)
80+
81+ x, y = res_layout.get_pixel_size()
82+ self.adjustment.upper = res_layout.posY + y
83+ # do not use this method in a loop because expensive.
84+ # self.redraw()
85+
86+ def create_res_layout(self, left_margin, resnum):
87+ return ResLayout(left_margin, resnum, self.pango_layout)
88+
89+ def set_layout_width(self, layout):
90+ layout.set_width(self.allocation.width)
91+
92+ def wrap_relayout(self):
93+ # before relayout, find top layout on gdkwindow
94+ top_layout = self.get_layout_on_y(self.adjustment.value)
95+ delta = 0
96+
97+ if top_layout is not None:
98+ delta = top_layout.posY - self.adjustment.value
99+
100+ self.relayout()
101+ self.drawingarea_prev_width = self.allocation.width
102+
103+ # after relayout, set adjustment.value to top layout's posY
104+ if top_layout is not None:
105+ self.adjustment.value = top_layout.posY - delta
106+
107+ def relayout(self):
108+ sum_height = 0
109+ for layout in self.res_layout_list:
110+ layout.set_width(self.allocation.width)
111+ layout.posY = sum_height
112+ x, y = layout.get_pixel_size()
113+ sum_height += y
114+
115+ self.adjustment.upper = sum_height
116+
117+ def get_layout_on_y(self, y):
118+
119+ def binary_search(lst, start, end, func):
120+
121+ if end - start <= 0:
122+ return None
123+
124+ m = (start + end) / 2
125+ ret = func(lst[m])
126+
127+ if ret == 0:
128+ return m
129+ if ret > 0:
130+ return binary_search(lst, start, m, func)
131+ return binary_search(lst, m+1, end, func)
132+
133+ def on_y(layout, _y):
134+ top = layout.posY
135+ width, height = layout.get_pixel_size()
136+ bottom = top + height
137+ if _y >= top and _y < bottom:
138+ return 0
139+ if _y < top:
140+ return 1
141+ return -1
142+
143+ ret = binary_search(
144+ self.res_layout_list, 0, len(self.res_layout_list),
145+ lambda x: on_y(x, y))
146+ if ret is not None:
147+ return self.res_layout_list[ret]
148+ return None
149+
150+ def do_configure_event(self, event):
151+ if event.width != self.drawingarea_prev_width:
152+ self.wrap_relayout()
153+
154+ Scrollable.do_configure_event(self, event)
155+
156+ def do_style_set(self, previous_style):
157+ if previous_style is None:
158+ return False
159+
160+ new = self.style.font_desc.hash()
161+ old = previous_style.font_desc.hash()
162+ if new != old:
163+ for layout in self.res_layout_list:
164+ layout.recalc_char_widths()
165+ self.wrap_relayout()
--- /dev/null
+++ b/src/FukuiNoNamari/ThreadViewBase/pointer_trackable.py
@@ -0,0 +1,185 @@
1+# Copyright (C) 2009 by Aiwota Programmer
2+# aiwotaprog@tetteke.tk
3+#
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.
8+#
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.
13+#
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
17+
18+import pygtk
19+pygtk.require('2.0')
20+import gtk
21+import gobject
22+from layoutable import Layoutable
23+
24+
25+class PointerTrackable(Layoutable):
26+
27+ __gsignals__ = {
28+ "uri-clicked-event":
29+ (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (object, )),
30+ "motion-notify-event": "override",
31+ "button-press-event": "override",
32+ "button-release-event": "override",
33+ }
34+
35+ hand_cursor = gtk.gdk.Cursor(gtk.gdk.HAND2)
36+ regular_cursor = gtk.gdk.Cursor(gtk.gdk.XTERM)
37+ arrow_cursor = gtk.gdk.Cursor(gtk.gdk.LEFT_PTR)
38+
39+ def __init__(self):
40+ super(PointerTrackable, self).__init__()
41+
42+ # for url click event
43+ self.button1_pressed = False
44+ self.current_pressed_uri = None
45+
46+ # for selection
47+ self.button_pressed_pt = (None, None, None)
48+ self.button_moving_pt = (None, None, None)
49+
50+ def _get_selection_start_end(self):
51+ pressed_layout, pressed_element, pressed_index = self.button_pressed_pt
52+ moving_layout, moving_element, moving_index = self.button_moving_pt
53+
54+ if (pressed_layout is None or pressed_element is None or
55+ pressed_index is None or moving_layout is None or
56+ moving_element is None or moving_index is None):
57+ return (None, None, None), (None, None, None)
58+
59+ if pressed_layout == moving_layout:
60+ if pressed_element == moving_element:
61+ if moving_index < pressed_index:
62+ return self.button_moving_pt, self.button_pressed_pt
63+ else:
64+ pressed_element_index = pressed_layout.element_list.index(
65+ pressed_element)
66+ moving_element_index = moving_layout.element_list.index(
67+ moving_element)
68+ if moving_element_index < pressed_element_index:
69+ return self.button_moving_pt, self.button_pressed_pt
70+ elif moving_layout.posY < pressed_layout.posY:
71+ return self.button_moving_pt, self.button_pressed_pt
72+
73+ return self.button_pressed_pt, self.button_moving_pt
74+
75+ def _set_button_moving_pt(self, pt):
76+ self.button_moving_pt = (None, None, None)
77+ if pt == None:
78+ return
79+
80+ x, y = pt
81+ layout = self.ptrpos_to_layout(x, y)
82+ if layout is None:
83+ return
84+
85+ x, y = self.transform_coordinate_gdk_to_layout(x, y, layout)
86+ element = layout.get_element_from_xy(x, y)
87+ if element is None:
88+ element = layout.get_close_element_from_xy(x, y)
89+
90+ if element is None:
91+ return
92+
93+ index = element.xy_to_index(x, y)
94+ if index is None:
95+ return
96+
97+ self.button_moving_pt = (layout, element, index)
98+
99+ def _set_button_pressed_pt(self, pt):
100+ self.button_pressed_pt = (None, None, None)
101+ if pt == None:
102+ return
103+
104+ x, y = pt
105+ layout = self.ptrpos_to_layout(x, y)
106+ if layout is None:
107+ return
108+
109+ x, y = self.transform_coordinate_gdk_to_layout(x, y, layout)
110+ element = layout.get_element_from_xy(x, y)
111+ if element is None:
112+ element = layout.get_close_element_from_xy(x, y)
113+
114+ if element is None:
115+ return
116+
117+ index = element.xy_to_index(x, y)
118+ if index is None:
119+ return
120+
121+ self.button_pressed_pt = (layout, element, index)
122+
123+ def do_motion_notify_event(self, event):
124+ if event.state & gtk.gdk.BUTTON1_MASK != gtk.gdk.BUTTON1_MASK:
125+ self.button1_pressed = False
126+
127+ if self.button1_pressed and self.current_pressed_uri is None:
128+ old_lay, old_elem, old_idx = self.button_moving_pt
129+ self._set_button_moving_pt((event.x, event.y))
130+ new_lay, new_elem, new_idx = self.button_moving_pt
131+ if (old_lay != new_lay
132+ or old_elem != new_elem
133+ or old_idx != new_idx):
134+ view_y = self.adjustment.value
135+ o_y = old_lay.posY
136+ n_y = new_lay.posY
137+ o_width, o_height = old_lay.get_pixel_size()
138+ n_width, n_height = new_lay.get_pixel_size()
139+
140+ y = min(o_y, n_y)
141+ height = max(o_y, n_y) - y
142+ if o_y > n_y: height += o_height
143+ else: height += n_height
144+
145+ y -= view_y
146+
147+ self.queue_draw_area(0, y, n_width, height+1)
148+ #self.window.process_updates(False)
149+
150+ cursor = PointerTrackable.regular_cursor
151+
152+ uri, layout, element = self.ptrpos_to_uri(event.x, event.y)
153+ if layout is None:
154+ cursor = PointerTrackable.arrow_cursor
155+ else:
156+ if uri is not None and uri != "":
157+ cursor = PointerTrackable.hand_cursor
158+ self.emit("cursor-over-link-event", event, uri)
159+
160+ self.window.set_cursor(cursor)
161+
162+ def do_button_press_event(self, event):
163+ if event.button == 1:
164+ self.current_pressed_uri = None
165+ self.button1_pressed = True
166+ uri, layout, element = self.ptrpos_to_uri(event.x, event.y)
167+ if uri is not None and layout is not None and element is not None:
168+ self.current_pressed_uri = (uri, layout, element)
169+ else:
170+ self._set_button_moving_pt((event.x, event.y))
171+ self._set_button_pressed_pt((event.x, event.y))
172+ self.queue_draw()
173+
174+ def do_button_release_event(self, event):
175+ if event.button == 1:
176+ button1_pressed = self.button1_pressed
177+ self.button1_pressed = False
178+
179+ if button1_pressed and self.current_pressed_uri is not None:
180+ uri, layout, element = self.ptrpos_to_uri(event.x, event.y)
181+ p_uri, p_layout, p_element = self.current_pressed_uri
182+ self.current_pressed_uri = None
183+ if (uri == p_uri and layout == p_layout and
184+ element == p_element):
185+ self.emit("uri-clicked-event", uri)
--- /dev/null
+++ b/src/FukuiNoNamari/ThreadViewBase/res_layout.py
@@ -0,0 +1,243 @@
1+# Copyright (C) 2009 by Aiwota Programmer
2+# aiwotaprog@tetteke.tk
3+#
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.
8+#
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.
13+#
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
17+
18+import pygtk
19+pygtk.require('2.0')
20+import gtk
21+from element import ElementEmpty, ElementText, ElementBoldText, ElementLink
22+from element import get_approximate_char_height
23+
24+
25+class ResLayout:
26+# represent one line
27+
28+ def __init__(self, left_margin, resnum, pango_layout):
29+ self.element_list = [ElementEmpty(pango_layout)]
30+ self.width = 0
31+ self.height = 0
32+ self.pango_layout = pango_layout
33+ self.left_margin = left_margin
34+ self.resnum = resnum
35+ self.posY = 0
36+ self.list_index = 0
37+
38+ def add_text(self, text, bold, href):
39+ if isinstance(self.element_list[0], ElementEmpty):
40+ self.element_list = []
41+
42+ if href:
43+ element = ElementLink(text, href, self.pango_layout)
44+ self.element_list.append(element)
45+ elif bold:
46+ element = ElementBoldText(text, self.pango_layout)
47+ self.element_list.append(element)
48+ else:
49+ element = ElementText(text, self.pango_layout)
50+ self.element_list.append(element)
51+
52+ def get_element_from_xy(self, x, y):
53+ for element in self.element_list:
54+ if element.is_on_xy(x, y):
55+ return element
56+ return None
57+
58+ def get_close_element_from_xy(self, x, y):
59+ x= max(x, self.left_margin)
60+ element = self.get_element_from_xy(x, y)
61+ if element is None and len(self.element_list) != 0:
62+ element = self.element_list[len(self.element_list) - 1]
63+ return element
64+
65+ def recalc_char_widths(self):
66+ for element in self.element_list:
67+ element.recalc_char_widths()
68+
69+ def set_width(self, width):
70+
71+ self.width = width
72+
73+ current_x = self.left_margin
74+ current_y = 0
75+
76+ for element in self.element_list:
77+ current_x, current_y = element.build_line_list(
78+ current_x, current_y, width, self.left_margin)
79+
80+ self.height = current_y + get_approximate_char_height(self.pango_layout.get_context())
81+
82+ def get_pixel_size(self):
83+ return self.width, self.height
84+
85+ def get_text(self, selection_start, selection_end):
86+ s_s = selection_start
87+ e_s = selection_end
88+ s_l, s_e, s_i = selection_start
89+ e_l, e_e, e_i = selection_end
90+
91+ text = ""
92+
93+ if (s_l is None or s_e is None or s_i is None or
94+ e_l is None or e_e is None or e_i is None or
95+ self.posY < s_l.posY or self.posY > e_l.posY):
96+
97+ # nothing to do
98+ pass
99+
100+ elif self.posY > s_s[0].posY and self.posY < e_s[0].posY:
101+
102+ for element in self.element_list:
103+ text += element.get_text(selection=True)
104+
105+ elif self == s_s[0] and self == e_s[0]:
106+
107+ selection = False
108+
109+ for element in self.element_list:
110+ if s_e == element:
111+ selection = True
112+ start = s_i
113+ end = 0xffffff
114+ if e_e == element:
115+ end = e_i
116+ selection = False
117+ text += element.get_text(selection=True, start_index=start,
118+ end_index=end)
119+ elif e_e == element:
120+ end = e_i
121+ selection = False
122+ text += element.get_text(
123+ selection=True, end_index=end)
124+ elif selection:
125+ text += element.get_text(selection=True)
126+
127+ elif self == s_s[0]:
128+
129+ selection = False
130+
131+ for element in self.element_list:
132+ if s_e == element:
133+ selection = True
134+ start = s_i
135+ text += element.get_text(selection=True, start_index=start)
136+ elif selection:
137+ text += element.get_text(selection=True)
138+
139+ elif self == e_s[0]:
140+
141+ selection = True
142+
143+ for element in self.element_list:
144+ if e_e == element:
145+ end = e_i
146+ text += element.get_text(selection=True, end_index=e_i)
147+ selection = False
148+ elif selection:
149+ text += element.get_text(selection=True)
150+
151+ else:
152+ # nothing to do
153+ pass
154+
155+ return text
156+
157+
158+ def draw(self, drawingarea, x_offset, y_offset,
159+ start_selection, end_selection):
160+
161+ s_s = start_selection
162+ e_s = end_selection
163+
164+ s_l, s_e, s_i = s_s
165+ e_l, e_e, e_i = e_s
166+
167+ if (s_l is None or s_e is None or s_i is None or
168+ e_l is None or e_e is None or e_i is None or
169+ self.posY < s_l.posY or self.posY > e_l.posY):
170+
171+ for element in self.element_list:
172+ element.draw(drawingarea, y_offset, self.pango_layout)
173+
174+ elif self.posY > s_s[0].posY and self.posY < e_s[0].posY:
175+
176+ for element in self.element_list:
177+ element.draw(drawingarea, y_offset, self.pango_layout,
178+ selection=True)
179+
180+ elif self == s_s[0] and self == e_s[0]:
181+
182+ selection = False
183+
184+ for element in self.element_list:
185+ if s_e == element:
186+ selection = True
187+ start = s_i
188+ end = 0xffffff
189+ if e_e == element:
190+ end = e_i
191+ selection = False
192+ element.draw(drawingarea, y_offset, self.pango_layout,
193+ selection=True,
194+ start_index=start,
195+ end_index=end)
196+ elif e_e == element:
197+ end = e_i
198+ selection = False
199+ element.draw(drawingarea, y_offset, self.pango_layout,
200+ selection=True, end_index=end)
201+ else:
202+ element.draw(drawingarea, y_offset, self.pango_layout,
203+ selection=selection)
204+
205+ elif self == s_s[0]:
206+
207+ selection = False
208+
209+ for element in self.element_list:
210+ if s_e == element:
211+ selection = True
212+ start = s_i
213+ element.draw(drawingarea, y_offset, self.pango_layout,
214+ selection=selection, start_index = start)
215+ else:
216+ element.draw(drawingarea, y_offset, self.pango_layout,
217+ selection=selection)
218+
219+ elif self == e_s[0]:
220+
221+ selection = True
222+
223+ for element in self.element_list:
224+ if e_e == element:
225+ end = e_i
226+ element.draw(drawingarea, y_offset, self.pango_layout,
227+ selection=selection, end_index=e_i)
228+ selection = False
229+ else:
230+ element.draw(drawingarea, y_offset, self.pango_layout,
231+ selection=selection)
232+
233+ else:
234+ for element in self.element_list:
235+ element.draw(drawingarea, y_offset, self.pango_layout)
236+
237+ def clone(self):
238+ import copy
239+ layout = ResLayout(self.left_margin, self.resnum, self.pango_layout)
240+ layout.element_list = []
241+ for element in self.element_list:
242+ layout.element_list.append(copy.copy(element))
243+ return layout
--- /dev/null
+++ b/src/FukuiNoNamari/ThreadViewBase/scrollable.py
@@ -0,0 +1,106 @@
1+# Copyright (C) 2009 by Aiwota Programmer
2+# aiwotaprog@tetteke.tk
3+#
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.
8+#
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.
13+#
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
17+
18+import pygtk
19+pygtk.require('2.0')
20+import gtk
21+import gobject
22+
23+
24+class Scrollable(gtk.DrawingArea):
25+
26+ __gsignals__ = {
27+ "configure-event": "override",
28+ "set-scroll-adjustments" : (gobject.SIGNAL_RUN_LAST,
29+ gobject.TYPE_NONE,
30+ (gtk.Adjustment, gtk.Adjustment)),
31+ "key-down": (gobject.SIGNAL_RUN_LAST | gobject.SIGNAL_ACTION,
32+ gobject.TYPE_NONE, ()),
33+ "key-up": (gobject.SIGNAL_RUN_LAST | gobject.SIGNAL_ACTION,
34+ gobject.TYPE_NONE, ()),
35+ "key-pagedown": (gobject.SIGNAL_RUN_LAST | gobject.SIGNAL_ACTION,
36+ gobject.TYPE_NONE, ()),
37+ "key-pageup": (gobject.SIGNAL_RUN_LAST | gobject.SIGNAL_ACTION,
38+ gobject.TYPE_NONE, ()),
39+ "key-home": (gobject.SIGNAL_RUN_LAST | gobject.SIGNAL_ACTION,
40+ gobject.TYPE_NONE, ()),
41+ "key-end": (gobject.SIGNAL_RUN_LAST | gobject.SIGNAL_ACTION,
42+ gobject.TYPE_NONE, ()),
43+ }
44+
45+ def __init__(self):
46+ super(Scrollable, self).__init__()
47+ self.set_set_scroll_adjustments_signal("set-scroll-adjustments")
48+
49+ def _set_adjustment_value(self, value):
50+ value = min(self.adjustment.upper - self.adjustment.page_size, value)
51+ value = max(self.adjustment.lower, value)
52+ self.adjustment.value = value
53+
54+ def jump(self, value):
55+ self._set_adjustment_value(value)
56+
57+ def jump_to_the_end(self):
58+ value = self.adjustment.upper - self.adjustment.page_size
59+ self._set_adjustment_value(value)
60+
61+ def transform_coordinate_gdk_to_adj(self, y):
62+ return y + self.adjustment.value
63+
64+ def do_configure_event(self, event):
65+ self.adjustment.page_size = self.allocation.height
66+ self.adjustment.step_increment = 20
67+ self.adjustment.page_increment = self.allocation.height
68+
69+ # re-set 'value' for prevent overflow
70+ self._set_adjustment_value(self.adjustment.value)
71+
72+ def do_set_scroll_adjustments(self, hadjustment, vadjustment):
73+ self.adjustment = vadjustment
74+ if self.adjustment is not None:
75+ self.adjustment.connect(
76+ "value-changed", self.on_adjustment_value_changed)
77+
78+ def do_key_up(self):
79+ self.jump(self.adjustment.value - self.adjustment.step_increment)
80+
81+ def do_key_down(self):
82+ self.jump(self.adjustment.value + self.adjustment.step_increment)
83+
84+ def do_key_pageup(self):
85+ self.jump(self.adjustment.value - self.adjustment.page_increment)
86+
87+ def do_key_pagedown(self):
88+ self.jump(self.adjustment.value + self.adjustment.page_increment)
89+
90+ def do_key_home(self):
91+ self.jump(self.adjustment.lower)
92+
93+ def do_key_end(self):
94+ self.jump_to_the_end()
95+
96+ def on_adjustment_value_changed(self, widget, data=None):
97+ self.queue_draw()
98+
99+
100+gtk.binding_entry_add_signal(Scrollable, gtk.keysyms.Up, 0, "key-up")
101+gtk.binding_entry_add_signal(Scrollable, gtk.keysyms.Down, 0, "key-down")
102+gtk.binding_entry_add_signal(Scrollable, gtk.keysyms.Page_Up, 0, "key-pageup")
103+gtk.binding_entry_add_signal(Scrollable, gtk.keysyms.Page_Down, 0, "key-pagedown")
104+gtk.binding_entry_add_signal(Scrollable, gtk.keysyms.Home, 0, "key-home")
105+gtk.binding_entry_add_signal(Scrollable, gtk.keysyms.End, 0, "key-end")
106+
\ No newline at end of file
--- a/src/FukuiNoNamari/thread_popup.py
+++ b/src/FukuiNoNamari/thread_popup.py
@@ -5,6 +5,7 @@ import re
55 import urlparse
66 import copy
77 import thread_view
8+from ThreadViewBase.element import ElementEmpty
89
910
1011 class ThreadViewForPopup(thread_view.ThreadView):
@@ -147,7 +148,7 @@ class ThreadPopup(gobject.GObject):
147148 for num in numlist:
148149 for layout in self._thread_view_list[0].res_layout_list:
149150 # skip empty line
150- if isinstance(layout.element_list[0], thread_view.ElementEmpty):
151+ if isinstance(layout.element_list[0], ElementEmpty):
151152 continue
152153 if layout.resnum == num:
153154 clone = layout.clone()
--- a/src/FukuiNoNamari/thread_view.py
+++ b/src/FukuiNoNamari/thread_view.py
@@ -1,4 +1,4 @@
1-# Copyright (C) 2007 by Aiwota Programmer
1+# Copyright (C) 2007, 2009 by Aiwota Programmer
22 # aiwotaprog@tetteke.tk
33 #
44 # This program is free software; you can redistribute it and/or modify
@@ -18,532 +18,25 @@
1818 import pygtk
1919 pygtk.require('2.0')
2020 import gtk
21-import pango
2221 import gobject
23-import itertools
24-from FukuiNoNamariExt import thread_view_extend
22+from ThreadViewBase.drawable import Drawable
2523
2624
27-def get_approximate_char_height(pango_context):
28- desc = pango_context.get_font_description()
29- font = pango_context.load_font(desc)
30- ink, log = font.get_glyph_extents(0)
31- return log[3] / pango.SCALE + 2
25+class ThreadView(Drawable):
3226
33-
34-class Rectangle:
35- def __init__(self, x, y, width, height):
36- self.x = x
37- self.y = y
38- self.width = width
39- self.height = height
40-
41-
42-class Line:
43-
44- HEIGHT = 15
45-
46- def __init__(self, start_index, end_index, rectangle):
47- self.start_index = start_index
48- self.end_index = end_index
49- self.rectangle = rectangle
50-
51- def is_on_xy(self, x, y):
52- left = self.rectangle.x
53- right = left + self.rectangle.width
54- top = self.rectangle.y
55- bottom = top + self.rectangle.height
56- return x >= left and x < right and y >= top and y < bottom
57-
58-
59-class ElementEmpty:
60-
61- def __init__(self, pango_layout):
62- self.pango_layout = pango_layout
63- self.initialize()
64-
65- def recalc_char_widths(self):
66- pass
67-
68- def initialize(self):
69- self.line_list = []
70-
71- def is_on_xy(self, x, y):
72- for line in self.line_list:
73- if line.is_on_xy(x, y):
74- return True
75- return False
76-
77- def xy_to_index(self, x, y):
78- return 0
79-
80- def build_line_list(self, x, y, width, left_margin):
81- self.initialize()
82-
83- line = Line(0, 0, Rectangle(
84- x, y, width - x,
85- get_approximate_char_height(self.pango_layout.get_context())))
86- self.line_list.append(line)
87-
88- return width, y
89-
90- def get_text(self, selection=False, start_index=0, end_index=0xffffff):
91- return ""
92-
93- def draw(self, drawingarea, y_offset, pango_layout,
94- selection=False, start_index=0, end_index=0xffffff):
95- pass
96-
97-
98-class ElementText:
99-
100- ch_width_dict = {} # key: char, value: width
101-
102- def __init__(self, text, pango_layout):
103- self.text = text
104- self.pango_layout = pango_layout
105-
106- self.recalc_char_widths()
107-
108- self.line_list = []
109-
110- def recalc_char_widths(self):
111- self.widths = [i for i in itertools.repeat(0, len(self.text))]
112-
113- dict = self._get_ch_width_dict()
114- need_to_get = False
115- for index, ch in enumerate(self.text):
116- if ch not in dict:
117- need_to_get = True
118- break
119- else:
120- width = dict[ch]
121- self.widths[index] = width
122-
123- if need_to_get:
124- attrlist = self._get_attrs()
125- self.widths = thread_view_extend.get_char_width(
126- self.pango_layout.get_context(), self.text, attrlist)
127- for index, width in enumerate(self.widths):
128- dict[self.text[index]] = self.widths[index]
129-
130- def _get_ch_width_dict(self):
131- return ElementText.ch_width_dict
132-
133- def _get_attrs(self):
134- attrs = pango.AttrList()
135- return attrs
136-
137- def is_on_xy(self, x, y):
138- for line in self.line_list:
139- if line.is_on_xy(x, y):
140- return True
141- return False
142-
143- def xy_to_index(self, x, y):
144- for line in self.line_list:
145- top = line.rectangle.y
146- bottom = top + line.rectangle.height
147- if y >= top and y < bottom:
148- sum_of_widths = line.rectangle.x
149- index = line.start_index
150- for width in self.widths[line.start_index:line.end_index]:
151- if sum_of_widths + width/2 > x:
152- break
153- sum_of_widths += width
154- index += 1
155- return index
156-
157- def build_line_list(self, x, y, width, left_margin):
158- self.line_list = []
159-
160- current_line_start_index = 0
161- current_line_x = x
162- current_line_y = y
163- current_line_width = 0
164-
165- ch_h = get_approximate_char_height(self.pango_layout.get_context())
166-
167- for index, ch in enumerate(self.text):
168- ch_w = self.widths[index]
169- if current_line_x + current_line_width + ch_w > width:
170- line = Line(
171- current_line_start_index, index,
172- Rectangle(
173- current_line_x, current_line_y,
174- current_line_width, ch_h))
175- self.line_list.append(line)
176-
177- current_line_start_index = index
178- current_line_x = left_margin
179- current_line_y += ch_h
180- current_line_width = ch_w
181- else:
182- current_line_width += ch_w
183-
184- if current_line_start_index < len(self.text):
185- line = Line(current_line_start_index, len(self.text),
186- Rectangle(current_line_x,
187- current_line_y,
188- current_line_width,
189- ch_h))
190- self.line_list.append(line)
191-
192- current_line_x += current_line_width
193-
194- return current_line_x, current_line_y
195-
196- def get_text(self, selection=False, start_index=0, end_index=0xffffff):
197-
198- text = ""
199-
200- for line in self.line_list:
201-
202- t = self.text[line.start_index:line.end_index]
203- if selection:
204- s = start_index - line.start_index
205- s = max(s, 0)
206- s = min(s, line.end_index - line.start_index)
207-
208- e = end_index - line.start_index
209- e = min(e, line.end_index - line.start_index)
210- e = max(e, 0)
211-
212- t = t[s:e]
213-
214- text += t
215-
216- return text
217-
218- def draw(self, drawingarea, y_offset, pango_layout,
219- selection=False, start_index=0, end_index=0xffffff):
220-
221- if drawingarea.get_property("has-focus"):
222- selection_fg = drawingarea.style.text[gtk.STATE_SELECTED]
223- selection_bg = drawingarea.style.base[gtk.STATE_SELECTED]
224- else:
225- selection_fg = drawingarea.style.text[gtk.STATE_ACTIVE]
226- selection_bg = drawingarea.style.base[gtk.STATE_ACTIVE]
227-
228- for line in self.line_list:
229-
230- text = self.text[line.start_index:line.end_index]
231- u_text = text.encode("utf8")
232- gc = drawingarea.window.new_gc()
233- gc.set_foreground(drawingarea.style.text[gtk.STATE_NORMAL])
234- gc.set_background(drawingarea.style.base[gtk.STATE_NORMAL])
235- attrs = self._get_attrs()
236- if selection:
237-
238- s = start_index - line.start_index
239- s = max(s, 0)
240- s = min(s, line.end_index - line.start_index)
241- s = len(text[:s].encode("utf8"))
242-
243- e = end_index - line.start_index
244- e = min(e, line.end_index - line.start_index)
245- e = max(e, 0)
246- e = len(text[:e].encode("utf8"))
247-
248- selection_all_attr_fg = pango.AttrForeground(
249- selection_fg.red, selection_fg.green, selection_fg.blue,
250- s, e)
251- selection_all_attr_bg= pango.AttrBackground(
252- selection_bg.red, selection_bg.green, selection_bg.blue,
253- s, e)
254- attrs.insert(selection_all_attr_fg)
255- attrs.insert(selection_all_attr_bg)
256-
257- pango_layout.set_text(u_text)
258- pango_layout.set_attributes(attrs)
259- drawingarea.window.draw_layout(
260- gc, int(line.rectangle.x), line.rectangle.y + y_offset,
261- pango_layout)
262-
263-
264-class ElementBoldText(ElementText):
265-
266- def _get_attrs(self):
267- attrlist = pango.AttrList()
268- attr = pango.AttrWeight(pango.WEIGHT_BOLD,
269- end_index=0xffffff)
270- attrlist.insert(attr)
271- return attrlist
272-
273- def recalc_char_widths(self):
274- attrlist = self._get_attrs()
275- self.widths = thread_view_extend.get_char_width(
276- self.pango_layout.get_context(), self.text, attrlist)
277-
278-
279-class ElementLink(ElementText):
280-
281- def __init__(self, text, href, pango_layout):
282- self.href = href
283- ElementText.__init__(self, text, pango_layout)
284-
285- def _get_attrs(self):
286- attrlist = pango.AttrList()
287- attr = pango.AttrUnderline(pango.UNDERLINE_SINGLE,
288- end_index=0xffffff)
289- attrlist.insert(attr)
290- return attrlist
291-
292-
293-class ResLayout:
294-# represent one line
295-
296- def __init__(self, left_margin, resnum, pango_layout):
297- self.element_list = [ElementEmpty(pango_layout)]
298- self.width = 0
299- self.height = 0
300- self.pango_layout = pango_layout
301- self.left_margin = left_margin
302- self.resnum = resnum
303- self.posY = 0
304- self.list_index = 0
305-
306- def add_text(self, text, bold, href):
307- if isinstance(self.element_list[0], ElementEmpty):
308- self.element_list = []
309-
310- if href:
311- element = ElementLink(text, href, self.pango_layout)
312- self.element_list.append(element)
313- elif bold:
314- element = ElementBoldText(text, self.pango_layout)
315- self.element_list.append(element)
316- else:
317- element = ElementText(text, self.pango_layout)
318- self.element_list.append(element)
319-
320- def get_element_from_xy(self, x, y):
321- for element in self.element_list:
322- if element.is_on_xy(x, y):
323- return element
324- return None
325-
326- def get_close_element_from_xy(self, x, y):
327- x= max(x, self.left_margin)
328- element = self.get_element_from_xy(x, y)
329- if element is None and len(self.element_list) != 0:
330- element = self.element_list[len(self.element_list) - 1]
331- return element
332-
333- def recalc_char_widths(self):
334- for element in self.element_list:
335- element.recalc_char_widths()
336-
337- def set_width(self, width):
338-
339- self.width = width
340-
341- current_x = self.left_margin
342- current_y = 0
343-
344- for element in self.element_list:
345- current_x, current_y = element.build_line_list(
346- current_x, current_y, width, self.left_margin)
347-
348- self.height = current_y + get_approximate_char_height(self.pango_layout.get_context())
349-
350- def get_pixel_size(self):
351- return self.width, self.height
352-
353- def get_text(self, selection_start, selection_end):
354- s_s = selection_start
355- e_s = selection_end
356- s_l, s_e, s_i = selection_start
357- e_l, e_e, e_i = selection_end
358-
359- text = ""
360-
361- if (s_l is None or s_e is None or s_i is None or
362- e_l is None or e_e is None or e_i is None or
363- self.posY < s_l.posY or self.posY > e_l.posY):
364-
365- # nothing to do
366- pass
367-
368- elif self.posY > s_s[0].posY and self.posY < e_s[0].posY:
369-
370- for element in self.element_list:
371- text += element.get_text(selection=True)
372-
373- elif self == s_s[0] and self == e_s[0]:
374-
375- selection = False
376-
377- for element in self.element_list:
378- if s_e == element:
379- selection = True
380- start = s_i
381- end = 0xffffff
382- if e_e == element:
383- end = e_i
384- selection = False
385- text += element.get_text(selection=True, start_index=start,
386- end_index=end)
387- elif e_e == element:
388- end = e_i
389- selection = False
390- text += element.get_text(
391- selection=True, end_index=end)
392- elif selection:
393- text += element.get_text(selection=True)
394-
395- elif self == s_s[0]:
396-
397- selection = False
398-
399- for element in self.element_list:
400- if s_e == element:
401- selection = True
402- start = s_i
403- text += element.get_text(selection=True, start_index=start)
404- elif selection:
405- text += element.get_text(selection=True)
406-
407- elif self == e_s[0]:
408-
409- selection = True
410-
411- for element in self.element_list:
412- if e_e == element:
413- end = e_i
414- text += element.get_text(selection=True, end_index=e_i)
415- selection = False
416- elif selection:
417- text += element.get_text(selection=True)
418-
419- else:
420- # nothing to do
421- pass
422-
423- return text
424-
425-
426- def draw(self, drawingarea, x_offset, y_offset,
427- start_selection, end_selection):
428-
429- s_s = start_selection
430- e_s = end_selection
431-
432- s_l, s_e, s_i = s_s
433- e_l, e_e, e_i = e_s
434-
435- if (s_l is None or s_e is None or s_i is None or
436- e_l is None or e_e is None or e_i is None or
437- self.posY < s_l.posY or self.posY > e_l.posY):
438-
439- for element in self.element_list:
440- element.draw(drawingarea, y_offset, self.pango_layout)
441-
442- elif self.posY > s_s[0].posY and self.posY < e_s[0].posY:
443-
444- for element in self.element_list:
445- element.draw(drawingarea, y_offset, self.pango_layout,
446- selection=True)
447-
448- elif self == s_s[0] and self == e_s[0]:
449-
450- selection = False
451-
452- for element in self.element_list:
453- if s_e == element:
454- selection = True
455- start = s_i
456- end = 0xffffff
457- if e_e == element:
458- end = e_i
459- selection = False
460- element.draw(drawingarea, y_offset, self.pango_layout,
461- selection=True,
462- start_index=start,
463- end_index=end)
464- elif e_e == element:
465- end = e_i
466- selection = False
467- element.draw(drawingarea, y_offset, self.pango_layout,
468- selection=True, end_index=end)
469- else:
470- element.draw(drawingarea, y_offset, self.pango_layout,
471- selection=selection)
472-
473- elif self == s_s[0]:
474-
475- selection = False
476-
477- for element in self.element_list:
478- if s_e == element:
479- selection = True
480- start = s_i
481- element.draw(drawingarea, y_offset, self.pango_layout,
482- selection=selection, start_index = start)
483- else:
484- element.draw(drawingarea, y_offset, self.pango_layout,
485- selection=selection)
486-
487- elif self == e_s[0]:
488-
489- selection = True
490-
491- for element in self.element_list:
492- if e_e == element:
493- end = e_i
494- element.draw(drawingarea, y_offset, self.pango_layout,
495- selection=selection, end_index=e_i)
496- selection = False
497- else:
498- element.draw(drawingarea, y_offset, self.pango_layout,
499- selection=selection)
500-
501- else:
502- for element in self.element_list:
503- element.draw(drawingarea, y_offset, self.pango_layout)
504-
505- def clone(self):
506- import copy
507- layout = ResLayout(self.left_margin, self.resnum, self.pango_layout)
508- layout.element_list = []
509- for element in self.element_list:
510- layout.element_list.append(copy.copy(element))
511- return layout
512-
513-
514-class ThreadView(gtk.DrawingArea):
51527 __gsignals__ = {
51628 "cursor-over-link-event":
51729 (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (object, object, )),
518- "uri-clicked-event":
519- (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (object, )),
520- "set-scroll-adjustments" : (gobject.SIGNAL_RUN_LAST,
521- gobject.TYPE_NONE,
522- (gtk.Adjustment, gtk.Adjustment)),
52330 "populate-popup": (gobject.SIGNAL_RUN_LAST,
52431 gobject.TYPE_NONE,
52532 (gtk.Menu, )),
526- "move-position": (gobject.SIGNAL_RUN_LAST | gobject.SIGNAL_ACTION,
527- gobject.TYPE_NONE, (gobject.TYPE_INT, )),
528- "configure-event": "override",
529- "expose-event": "override",
530- "motion-notify-event": "override",
53133 "button-press-event": "override",
532- "button-release-event": "override",
533- "style-set": "override",
534- }
535-
536- hand_cursor = gtk.gdk.Cursor(gtk.gdk.HAND2)
537- regular_cursor = gtk.gdk.Cursor(gtk.gdk.XTERM)
538- arrow_cursor = gtk.gdk.Cursor(gtk.gdk.LEFT_PTR)
34+ }
53935
54036 def __init__(self):
54137 super(ThreadView, self).__init__()
542- self.set_set_scroll_adjustments_signal("set-scroll-adjustments")
54338 self.set_property("can_focus", True)
54439
545- #self.adjustment = gtk.Adjustment()
546-
54740 self.add_events(
54841 gtk.gdk.KEY_PRESS_MASK |
54942 gtk.gdk.SCROLL_MASK |
@@ -551,86 +44,8 @@ class ThreadView(gtk.DrawingArea):
55144 gtk.gdk.BUTTON_PRESS_MASK |
55245 gtk.gdk.BUTTON_RELEASE_MASK)
55346
554- self.drawingarea_prev_width = 0
555-
556- self.pango_layout = self.create_pango_layout("")
557-
558- self.initialize_buffer()
559-
560- self.button1_pressed = False
561- self.current_pressed_uri = None
562-
563- # for selection
564- self.button_pressed_pt = (None, None, None)
565- self.button_moving_pt = (None, None, None)
566-
567- def initialize_buffer(self):
568- self.res_layout_list = []
569-
570- def add_layout(self, res_layout):
571- if (len(self.res_layout_list) != 0):
572- last = self.res_layout_list[len(self.res_layout_list)-1]
573- x, y = last.get_pixel_size()
574- res_layout.posY = last.posY + y
575- self.set_layout_width(res_layout)
576- res_layout.list_index = len(self.res_layout_list)
577- self.res_layout_list.append(res_layout)
578-
579- x, y = res_layout.get_pixel_size()
580- self.adjustment.upper = res_layout.posY + y
581- # do not use this method in a loop because expensive.
582- # self.redraw()
583-
584- def create_res_layout(self, left_margin, resnum):
585- return ResLayout(left_margin, resnum, self.pango_layout)
586-
587- def set_layout_width(self, layout):
588- width = self.allocation.width
589- layout.set_width(width)
590-
591- def redraw(self):
592- self.queue_draw()
593-
594- def wrap_relayout(self):
595- # before relayout, find top layout on gdkwindow
596- top_layout = self.get_layout_on_y(self.adjustment.value)
597- delta = 0
598-
599- if top_layout is not None:
600- delta = top_layout.posY - self.adjustment.value
601-
602- self.relayout()
603- self.drawingarea_prev_width = self.allocation.width
604-
605- # after relayout, set adjustment.value to top layout's posY
606- if top_layout is not None:
607- self.adjustment.value = top_layout.posY - delta
608-
609- def relayout(self):
610- width = self.allocation.width
611- sum_height = 0
612- for layout in self.res_layout_list:
613- layout.set_width(width)
614- layout.posY = sum_height
615- x, y = layout.get_pixel_size()
616- sum_height += y
617-
618- self.adjustment.upper = sum_height
619-
620- def _set_adjustment_value(self, value):
621- value = min(self.adjustment.upper - self.adjustment.page_size, value)
622- value = max(self.adjustment.lower, value)
623- self.adjustment.value = value
624-
625- def jump(self, value):
626- self._set_adjustment_value(value)
627-
62847 def jump_to_layout(self, layout):
62948 self.jump(layout.posY)
630-
631- def jump_to_the_end(self):
632- value = self.adjustment.upper - self.adjustment.page_size
633- self._set_adjustment_value(value)
63449
63550 def jump_to_res(self, resnum):
63651 for layout in self.res_layout_list:
@@ -639,119 +54,6 @@ class ThreadView(gtk.DrawingArea):
63954 return True
64055 return False
64156
642-
643- def _get_selection_start_end(self):
644- pressed_layout, pressed_element, pressed_index = self.button_pressed_pt
645- moving_layout, moving_element, moving_index = self.button_moving_pt
646-
647- if (pressed_layout is None or pressed_element is None or
648- pressed_index is None or moving_layout is None or
649- moving_element is None or moving_index is None):
650- return (None, None, None), (None, None, None)
651-
652- if pressed_layout == moving_layout:
653- if pressed_element == moving_element:
654- if moving_index < pressed_index:
655- return self.button_moving_pt, self.button_pressed_pt
656- else:
657- pressed_element_index = pressed_layout.element_list.index(
658- pressed_element)
659- moving_element_index = moving_layout.element_list.index(
660- moving_element)
661- if moving_element_index < pressed_element_index:
662- return self.button_moving_pt, self.button_pressed_pt
663- elif moving_layout.posY < pressed_layout.posY:
664- return self.button_moving_pt, self.button_pressed_pt
665-
666- return self.button_pressed_pt, self.button_moving_pt
667-
668- def draw_viewport(self, area):
669- view_y = self.adjustment.value
670- self.window.draw_rectangle(
671- self.style.base_gc[0],
672- True, area.x, area.y, area.width, area.height)
673-
674- selection_start, selection_end = self._get_selection_start_end()
675-
676- top_layout = self.get_layout_on_y(view_y)
677- if top_layout is None:
678- return
679- #area_top = view_y + area.y
680- area_bottom = view_y + area.y + area.height
681-
682- iter = range(top_layout.list_index, len(self.res_layout_list))
683- iter = itertools.imap(lambda index: self.res_layout_list[index], iter)
684- iter = itertools.takewhile(lambda lay: lay.posY <= area_bottom, iter)
685- for layout in iter:
686- layout.draw(self, 0, layout.posY - int(view_y),
687- selection_start, selection_end)
688-
689- def transform_coordinate_gdk_to_adj(self, y):
690- return y + self.adjustment.value
691-
692- def transform_coordinate_adj_to_layout(self, x, y, layout):
693- return x, y - layout.posY
694-
695- def transform_coordinate_gdk_to_layout(self, x, y, layout):
696- return self.transform_coordinate_adj_to_layout(
697- x, self.transform_coordinate_gdk_to_adj(y), layout)
698-
699- def get_layout_on_y(self, y):
700-
701- def binary_search(lst, start, end, func):
702-
703- if end - start <= 0:
704- return None
705-
706- m = (start + end) / 2
707- ret = func(lst[m])
708-
709- if ret == 0:
710- return m
711- if ret > 0:
712- return binary_search(lst, start, m, func)
713- return binary_search(lst, m+1, end, func)
714-
715- def on_y(layout, _y):
716- top = layout.posY
717- width, height = layout.get_pixel_size()
718- bottom = top + height
719- if _y >= top and _y < bottom:
720- return 0
721- if _y < top:
722- return 1
723- return -1
724-
725- ret = binary_search(
726- self.res_layout_list, 0, len(self.res_layout_list),
727- lambda x: on_y(x, y))
728- if ret is not None:
729- return self.res_layout_list[ret]
730- return None
731-
732- def ptrpos_to_layout(self, x, y):
733- # transform coordinate, GdkWindow -> adjustment
734- adj_y = self.transform_coordinate_gdk_to_adj(y)
735- return self.get_layout_on_y(adj_y)
736-
737- def ptrpos_to_uri(self, x, y):
738- # x, y is GdkWindow coordinate
739-
740- layout = self.ptrpos_to_layout(x, y)
741-
742- if layout is None:
743- return None, None, None
744-
745- # transform coordinate, GdkWindow -> res_layout_list
746- lay_x, lay_y = self.transform_coordinate_gdk_to_layout(x, y, layout)
747-
748- # xy -> element
749- element = layout.get_element_from_xy(lay_x, lay_y)
750- if isinstance(element, ElementLink):
751- return element.href, layout, element
752-
753- return None, layout, None
754-
75557 def get_selected_text(self):
75658 selection_start, selection_end = self._get_selection_start_end()
75759 s_l, s_e, s_i = selection_start
@@ -776,54 +78,6 @@ class ThreadView(gtk.DrawingArea):
77678
77779 return text
77880
779- def _set_button_pressed_pt(self, pt):
780- self.button_pressed_pt = (None, None, None)
781- if pt == None:
782- return
783-
784- x, y = pt
785- layout = self.ptrpos_to_layout(x, y)
786- if layout is None:
787- return
788-
789- x, y = self.transform_coordinate_gdk_to_layout(x, y, layout)
790- element = layout.get_element_from_xy(x, y)
791- if element is None:
792- element = layout.get_close_element_from_xy(x, y)
793-
794- if element is None:
795- return
796-
797- index = element.xy_to_index(x, y)
798- if index is None:
799- return
800-
801- self.button_pressed_pt = (layout, element, index)
802-
803- def _set_button_moving_pt(self, pt):
804- self.button_moving_pt = (None, None, None)
805- if pt == None:
806- return
807-
808- x, y = pt
809- layout = self.ptrpos_to_layout(x, y)
810- if layout is None:
811- return
812-
813- x, y = self.transform_coordinate_gdk_to_layout(x, y, layout)
814- element = layout.get_element_from_xy(x, y)
815- if element is None:
816- element = layout.get_close_element_from_xy(x, y)
817-
818- if element is None:
819- return
820-
821- index = element.xy_to_index(x, y)
822- if index is None:
823- return
824-
825- self.button_moving_pt = (layout, element, index)
826-
82781 def _copy_text_to_clipboard(self, text):
82882 if text and len(text) > 0:
82983 clip = gtk.Clipboard()
@@ -863,131 +117,12 @@ class ThreadView(gtk.DrawingArea):
863117 menu.show_all()
864118 menu.popup(None, None, None, button, time)
865119
866- def do_expose_event(self, event):
867- self.draw_viewport(event.area)
868-
869- def do_configure_event(self, event):
870- if event.width != self.drawingarea_prev_width:
871- self.wrap_relayout()
872-
873- self.adjustment.page_size = self.allocation.height
874- self.adjustment.step_increment = 20
875- self.adjustment.page_increment = self.allocation.height
876-
877- # re-set 'value' for prevent overflow
878- self._set_adjustment_value(self.adjustment.value)
879-
880- def on_adjustment_value_changed(self, widget, data=None):
881- self.queue_draw()
882-
883- def do_motion_notify_event(self, event):
884- if event.state & gtk.gdk.BUTTON1_MASK != gtk.gdk.BUTTON1_MASK:
885- self.button1_pressed = False
886-
887- if self.button1_pressed and self.current_pressed_uri is None:
888- old_lay, old_elem, old_idx = self.button_moving_pt
889- self._set_button_moving_pt((event.x, event.y))
890- new_lay, new_elem, new_idx = self.button_moving_pt
891- if (old_lay != new_lay
892- or old_elem != new_elem
893- or old_idx != new_idx):
894- view_y = self.adjustment.value
895- o_y = old_lay.posY
896- n_y = new_lay.posY
897- o_width, o_height = old_lay.get_pixel_size()
898- n_width, n_height = new_lay.get_pixel_size()
899-
900- y = min(o_y, n_y)
901- height = max(o_y, n_y) - y
902- if o_y > n_y: height += o_height
903- else: height += n_height
904-
905- y -= view_y
906-
907- self.queue_draw_area(0, y, n_width, height+1)
908- #self.window.process_updates(False)
909-
910- cursor = ThreadView.regular_cursor
911-
912- uri, layout, element = self.ptrpos_to_uri(event.x, event.y)
913- if layout is None:
914- cursor = ThreadView.arrow_cursor
915- else:
916- if uri is not None and uri != "":
917- cursor = ThreadView.hand_cursor
918- self.emit("cursor-over-link-event", event, uri)
919-
920- self.window.set_cursor(cursor)
921-
922120 def do_button_press_event(self, event):
923- if event.button == 1:
924- self.current_pressed_uri = None
925- self.button1_pressed = True
926- uri, layout, element = self.ptrpos_to_uri(event.x, event.y)
927- if uri is not None and layout is not None and element is not None:
928- self.current_pressed_uri = (uri, layout, element)
929- else:
930- self._set_button_moving_pt((event.x, event.y))
931- self._set_button_pressed_pt((event.x, event.y))
932- self.queue_draw()
933-
934- elif event.button == 3:
121+ Drawable.do_button_press_event(self, event)
122+ if event.button == 3:
935123 self._do_popup(event.x, event.y, event.button, event.time)
936124 return True
937125
938- def do_button_release_event(self, event):
939- if event.button == 1:
940- button1_pressed = self.button1_pressed
941- self.button1_pressed = False
942-
943- if button1_pressed and self.current_pressed_uri is not None:
944- uri, layout, element = self.ptrpos_to_uri(event.x, event.y)
945- p_uri, p_layout, p_element = self.current_pressed_uri
946- self.current_pressed_uri = None
947- if (uri == p_uri and layout == p_layout and
948- element == p_element):
949- self.emit("uri-clicked-event", uri)
950-
951- def do_style_set(self, previous_style):
952- if previous_style is None:
953- return False
954-
955- new = self.style.font_desc.hash()
956- old = previous_style.font_desc.hash()
957- if new != old:
958- for layout in self.res_layout_list:
959- layout.recalc_char_widths()
960- self.wrap_relayout()
961-
962- def do_move_position(self, keyval):
963- if keyval in (gtk.keysyms.Up, gtk.keysyms.Down,
964- gtk.keysyms.Page_Up, gtk.keysyms.Page_Down,
965- gtk.keysyms.Home):
966- value = self.adjustment.value
967- if keyval == gtk.keysyms.Up:
968- step_increment = self.adjustment.step_increment
969- value = value - step_increment
970- elif keyval == gtk.keysyms.Down:
971- step_increment = self.adjustment.step_increment
972- value = value + step_increment
973- elif keyval == gtk.keysyms.Page_Up:
974- step_increment = self.adjustment.page_increment
975- value = value - step_increment
976- elif keyval == gtk.keysyms.Page_Down:
977- step_increment = self.adjustment.page_increment
978- value = value + step_increment
979- elif keyval == gtk.keysyms.Home:
980- value = 0
981- self.jump(value)
982- elif keyval == gtk.keysyms.End:
983- self.jump_to_the_end()
984-
985- def do_set_scroll_adjustments(self, hadjustment, vadjustment):
986- self.adjustment = vadjustment
987- if self.adjustment is not None:
988- self.adjustment.connect(
989- "value-changed", self.on_adjustment_value_changed)
990-
991126 def on_copy_uri_activated(self, widget, uri):
992127 self._copy_text_to_clipboard(uri)
993128
@@ -1003,20 +138,6 @@ class ThreadView(gtk.DrawingArea):
1003138 self.emit("uri-clicked-event", selection)
1004139
1005140
1006-gtk.binding_entry_add_signal(ThreadView, gtk.keysyms.Up, 0,
1007- "move-position", gobject.TYPE_INT, gtk.keysyms.Up)
1008-gtk.binding_entry_add_signal(ThreadView, gtk.keysyms.Down, 0,
1009- "move-position", gobject.TYPE_INT, gtk.keysyms.Down)
1010-gtk.binding_entry_add_signal(ThreadView, gtk.keysyms.Page_Up, 0,
1011- "move-position", gobject.TYPE_INT, gtk.keysyms.Page_Up)
1012-gtk.binding_entry_add_signal(ThreadView, gtk.keysyms.Page_Down, 0,
1013- "move-position", gobject.TYPE_INT, gtk.keysyms.Page_Down)
1014-gtk.binding_entry_add_signal(ThreadView, gtk.keysyms.Home, 0,
1015- "move-position", gobject.TYPE_INT, gtk.keysyms.Home)
1016-gtk.binding_entry_add_signal(ThreadView, gtk.keysyms.End, 0,
1017- "move-position", gobject.TYPE_INT, gtk.keysyms.End)
1018-
1019-
1020141 class ThreadViewScrollbar(gtk.VScrollbar):
1021142
1022143 def __init__(self, adjustment=None):
--- a/src/FukuiNoNamari/thread_window.py
+++ b/src/FukuiNoNamari/thread_window.py
@@ -381,7 +381,7 @@ class WinWrap(winwrapbase.WinWrapBase):
381381
382382 self.http_get_dat(save_line_and_append_to_buffer)
383383 gtk.gdk.threads_enter()
384- self.threadview.redraw()
384+ self.threadview.queue_draw()
385385 gtk.gdk.threads_leave()
386386 dat_file.close()
387387
Show on old repository browser