1 """
2 Widget with switchable mouse controllers (Tools) and transparent overlays and layers.
3
4 Colors are given as a pair: COLORREF=(name_for_prefs, (r,g,b,a)). (r,g,b,a) is the initial default.
5 Each time it draws, a ToolSpace looks up its colors (and font) in self.appearance (type L{TS_Appearance}):
6
7 >>> r,g,b,a = self.appearance.color(COLORREF)
8
9 You can change the color:
10
11 >>> self.appearance.colors[COLORREF].set( (r,g,b,a) )
12
13 Or swap in a different TS_Appearance, e.g. for black and white printing:
14
15 >>> set_aside, self.appearance = self.appearance, black_and_white_appearance
16
17 Appearance prefs are saved by L{qubx.util_panels.SettingsFace}.
18
19 Layers and sublayers are laid out statically, in multiples of appearance.emsize.
20 Negative coordinates or dimensions indicate distance from the right or bottom edge.
21 When font size or widget dimensions change, it updates the layout.
22
23 Copyright 2008-2014 Research Foundation State University of New York
24 This file is part of QUB Express.
25
26 QUB Express is free software; you can redistribute it and/or modify
27 it under the terms of the GNU General Public License as published by
28 the Free Software Foundation, either version 3 of the License, or
29 (at your option) any later version.
30
31 QUB Express is distributed in the hope that it will be useful,
32 but WITHOUT ANY WARRANTY; without even the implied warranty of
33 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
34 GNU General Public License for more details.
35
36 You should have received a copy of the GNU General Public License,
37 named LICENSE.txt, in the QUB Express program directory. If not, see
38 <http://www.gnu.org/licenses/>.
39
40 """
41
42 import cairo
43 import collections
44 import ctypes
45 import gtk
46 import gobject
47 import traceback
48 import weakref
49
50 from gtk import gdk
51 from gtk import keysyms
52 gtkHas2Click = False
53 DBL_CLICK_MS = 250
54
55 from itertools import izip, count
56 from math import *
57
58 import qubx.pyenv
59 from qubx.util_types import *
60 from qubx.accept import acceptIntGreaterThanOrEqualTo
61 from qubx.GTK import build_menuitem
62
63 ColorInfo = collections.defaultdict(lambda: Anon())
64
65 LAYER_BG = ('toolspace.layer.bg', (.12,.12,.12,.85))
66 ColorInfo[LAYER_BG[0]].label = 'Default layer background'
67 LAYER_FG = ('toolspace.layer.fg', (.1,1,.1,.9))
68 ColorInfo[LAYER_FG[0]].label = 'Default layer foreground'
69 LAYER_BORDER = ('toolspace.layer.border', (.5, .5, .5, .5))
70 ColorInfo[LAYER_BORDER[0]].label = 'Default layer border'
71 COLOR_CLEAR = ('toolspace.clear', (0,0,0,0))
72 ColorInfo[COLOR_CLEAR[0]].label = 'Constant: transparent'
73 COLOR_BLACK = ('toolspace.black', (0,0,0,1))
74 ColorInfo[COLOR_BLACK[0]].label = 'Constant: black'
75 COLOR_WHITE = ('toolspace.white', (1,1,1,1))
76 ColorInfo[COLOR_WHITE[0]].label = 'Constant: white'
77 COLOR_LABEL = ('toolspace.label', LAYER_FG[1])
78 ColorInfo[COLOR_LABEL[0]].label = 'Default layer text'
79 COLOR_HOVER = ('toolspace.label.hover', COLOR_LABEL[1])
80 ColorInfo[COLOR_HOVER[0]].label = 'Default layer text mouseover'
81 COLOR_POPUP = ('toolspace.popup', (0, 0, 1, 1))
82 ColorInfo[COLOR_POPUP[0]].label = 'Default popup menu'
83 COLOR_DROPDOWN = ('toolspace.dropdown', LAYER_FG[1])
84 ColorInfo[COLOR_DROPDOWN[0]].label = 'Default dropdown menu text'
85 COLOR_RANGE = ('toolspace.range', (.1, .5, .7, .5))
86 ColorInfo[COLOR_RANGE[0]].label = 'Data scroll thumb'
87 COLOR_RANGE_HOVER = ('toolspace.range.hover', (.2, .7, 1, .6))
88 ColorInfo[COLOR_RANGE_HOVER[0]].label = 'Data scroll thumb mouseover'
89 COLOR_RANGE_EXPAND = ('toolspace.range.expand', (1, .2, .8, .9))
90 ColorInfo[COLOR_RANGE_EXPAND[0]].label = 'Data scroll thumb mouseover edge'
91 COLOR_TOOLTIP_BG = ('toolspace.tooltip.bg', (1,1,.3,.8))
92 ColorInfo[COLOR_TOOLTIP_BG[0]].label = 'Tooltip background'
93 COLOR_TOOLTIP_FG = ('toolspace.tooltip.fg', (0,0,0,.85))
94 ColorInfo[COLOR_TOOLTIP_FG[0]].label = 'Tooltip text'
95 COLOR_CHECK = ('toolspace.check', (1,.5,0,.8))
96 ColorInfo[COLOR_CHECK[0]].label = 'Default checkbox'
97 COLOR_PALETTE_TEXT = ('toolspace.palette.text', (1,1,1,1))
98 ColorInfo[COLOR_PALETTE_TEXT[0]].label = 'Color-chooser marker'
99 COLOR_PALETTE_MORE = ('toolspace.palette.more', (.1, .1, .1, 1))
100 ColorInfo[COLOR_PALETTE_MORE[0]].label = 'Color-chooser menu'
101 COLOR_RADIO_FILL = ('toolspace.radio.fill', (1, .5, 0, .8))
102 ColorInfo[COLOR_RADIO_FILL[0]].label = 'Radio button fill'
103 COLOR_FOCUSED = ('toolspace.focused', (.75, .45, 1, .5))
104 ColorInfo[COLOR_FOCUSED[0]].label = 'Panel border focused'
105 COLOR_MAG_RIM = ('toolspace.mag.rim', (1, 1, 1, .8))
106 ColorInfo[COLOR_MAG_RIM[0]].label = 'Zoom icon rim'
107 COLOR_MAG_LENS = ('toolspace.mag.lens', (1, 1, 1, .35))
108 ColorInfo[COLOR_MAG_LENS[0]].label = 'Zoom icon lens'
109 PALETTE_H_EMS = 2.3
110
111 TOOLBAR_PAD = 0.5
112
114 """Represents one named color.
115
116 @ivar name:
117 @ivar value: (r,g,b,a)
118 @ivar OnSet: L{WeakEvent}(TS_Color) when color.set() was called.
119 """
124 - def set(self, value):
127
129 """Holds color and font preferences.
130
131 @ivar colors: dict: name -> TS_Color
132 @ivar color_by_name: dict: name -> TS_Color
133 @ivar colordef_by_name: dict: name -> (r,g,b,a); values from color_preset() which haven't been requested by any components yet.
134 @ivar font_size: size in points of the default font
135 @ivar font_bold: (bool) default font is bold?
136 @ivar line_width: multiplier, default=1.0
137 @ivar emsize: width in pixels of a capital 'M'
138 @ivar opengl: True if OpenGL is allowed
139 @ivar hide_hidden_signals: False to show controls for all signals
140 @ivar multi_line_data: True to add lines when lo-res data is expanded
141 @ivar auto_scale_data: True to auto-scale data signals
142 @ivar color_idealized: True to draw idealization with class colors
143 @ivar gauss_intensity: True to draw fit curve with intensity gradient (otherwise +/- 1 std)
144 @ivar OnSetFontSize: L{WeakEvent}(font_size) when set_font_size() is called
145 @ivar OnSetLineWidth: L{WeakEvent}(line_width) when set_line_width() is called
146 @ivar OnSetOpenGL: L{WeakEvent}(opengl) when set_opengl() is called
147 @ivar OnAddColor: L{WeakEvent}(name, (r,g,b,a)) when a color is looked up for the first time
148 @ivar OnSetColor: L{WeakEvent}(name, (r,g,b,a)) when a color is changed
149 @ivar OnSetHideHiddenSignals: L{WeakEvent}(bool)
150 @ivar OnSetMultiLineData: L{WeakEvent}(bool)
151 @ivar OnSetAutoScaleData: L{WeakEvent}(bool)
152 @ivar OnSetColorIdealized: L{WeakEvent}(bool)
153 @ivar OnSetGaussIntensity: L{WeakEvent}(bool)
154 """
155 - def __init__(self, font_size=11, font_bold=True, opengl=False, line_width=1.0):
156 self.__ref = Reffer()
157 self.OnSetFontSize = WeakEvent()
158 self.set_font_size(font_size, font_bold)
159 self.OnSetOpenGL = WeakEvent()
160 self.__opengl = opengl
161 self.__hide_hidden_signals = False
162 self.__multi_line_data = False
163 self.__auto_scale_data = False
164 self.__color_idealized = False
165 self.__gauss_intensity = True
166 self.OnSetLineWidth = WeakEvent()
167 self.__line_width = line_width
168 self.colors = {}
169 self.color_by_name = {}
170 self.colordef_by_name = {}
171 self.OnAddColor = WeakEvent()
172 self.OnSetColor = WeakEvent()
173 self.OnSetHideHiddenSignals = WeakEvent()
174 self.OnSetMultiLineData = WeakEvent()
175 self.OnSetAutoScaleData = WeakEvent()
176 self.OnSetColorIdealized = WeakEvent()
177 self.OnSetGaussIntensity = WeakEvent()
178 - def color(self, nm_def):
195 """Initializes the named color with a new default, to override the one in its COLORREF tuple, as from saved preferences."""
196 if name in self.color_by_name:
197 self.color_by_name[name].set(default)
198 else:
199
200 self.colordef_by_name[name] = default
201 - def setup_context(self, context):
202 """Sets the font in a cairo drawing context."""
203 context.select_font_face('sans-serif', cairo.FONT_SLANT_NORMAL,
204 self.font_bold and cairo.FONT_WEIGHT_BOLD or cairo.FONT_WEIGHT_NORMAL)
205 context.set_font_size(self.font_size)
207 """Returns (emsize, fheight) -- width of a capital M, and overall font height (advisory)."""
208 surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 128, 128)
209 context = cairo.Context(surface)
210 self.setup_context(context)
211 fascent, fdescent, fheight, fxadvance, fyadvance = context.font_extents()
212 xbearing, ybearing, width, height, xadvance, yadvance = context.text_extents('M')
213 return width, fheight
215 """Changes the default font size and weight, and signals OnSetFontSize."""
216 self.__font_size = pts
217 if not (bold is None):
218 self.__font_bold = bold
219 self.emsize, fheight = self.calc_font_spacing()
220 self.emsize = int(round(self.emsize))
221 self.OnSetFontSize(pts)
222 font_size = property(lambda self: self.__font_size, lambda self, x: self.set_font_size(x, self.__font_bold))
223 font_bold = property(lambda self: self.__font_bold, lambda self, x: self.set_font_size(self.__font_size, x))
230 opengl = property(lambda self: self.__opengl, lambda self, x: self.set_opengl(x), doc="obsolete: poor cross-platform support")
232 if self.__line_width != x:
233 self.__line_width = x
234 self.OnSetLineWidth(x)
235 line_width = property(lambda self: self.__line_width, lambda self, x: self.set_line_width(x), doc="used mainly for data drawing")
237 if x != self.__hide_hidden_signals:
238 self.__hide_hidden_signals = x
239 self.OnSetHideHiddenSignals(x)
240 hide_hidden_signals = property(lambda self: self.__hide_hidden_signals, lambda self, x: self.set_hide_hidden_signals(x), doc="True to omit scope controls for hidden signals")
242 if x != self.__multi_line_data:
243 self.__multi_line_data = x
244 self.OnSetMultiLineData(x)
245 multi_line_data = property(lambda self: self.__multi_line_data, lambda self, x: self.set_multi_line_data(x), doc="True to allow multiple lines of data in the low-res panel if it is larger than hi-res")
247 if x != self.__auto_scale_data:
248 self.__auto_scale_data = x
249 self.OnSetAutoScaleData(x)
250 auto_scale_data = property(lambda self: self.__auto_scale_data, lambda self, x: self.set_auto_scale_data(x), doc="True to automatically rescale data onscreen.")
252 if x != self.__color_idealized:
253 self.__color_idealized = x
254 self.OnSetColorIdealized(x)
255 color_idealized = property(lambda self: self.__color_idealized, lambda self, x: self.set_color_idealized(x), doc="True to draw idealization with class colors")
257 if x != self.__gauss_intensity:
258 self.__gauss_intensity = x
259 self.OnSetGaussIntensity(x)
260 gauss_intensity = property(lambda self: self.__gauss_intensity, lambda self, x: self.set_gauss_intensity(x), doc="True to draw fit curves with intensity gradient")
261
262 Appearance = TS_Appearance()
263
265 """Modeless window for changing the (r,g,b,a) values in appearance.colors."""
266 - def __init__(self, app_name, appearance):
267 gtk.Dialog.__init__(self, '%s - Colors'%app_name, None, 0)
268 action_area = self.get_action_area()
269 btn = gtk.Button('OK')
270 btn.connect('clicked', lambda btn: self.response(gtk.RESPONSE_ACCEPT) or self.destroy())
271 btn.show()
272 action_area.pack_end(btn, False, True)
273 btn = gtk.Button('Apply')
274 btn.connect('clicked', lambda btn: self.on_apply())
275 btn.show()
276 action_area.pack_end(btn, False, True)
277 btn = gtk.Button('Cancel')
278 btn.connect('clicked', lambda btn: self.on_cancel() or self.response(gtk.RESPONSE_REJECT) or self.destroy())
279 btn.show()
280 action_area.pack_end(btn, False, True)
281 self.connect('destroy', lambda win: self.destroy())
282 self.set_size_request(300, 200)
283 scr = gtk.ScrolledWindow()
284 scr.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
285 scr.show()
286 self.vbox.pack_start(scr, True, True)
287 v = gtk.VBox()
288 v.show()
289 scr.add_with_viewport(v)
290 self.appearance = appearance
291 self.names = [(ColorInfo[key].label or key, key) for key in appearance.colors]
292 self.names.sort()
293 self.colors = [appearance.color_by_name[name] for lbl, name in self.names]
294 self.swatches = []
295 for lbl_name, color in izip(self.names, self.colors):
296 caption, name = lbl_name
297 h = gtk.HBox()
298 h.show()
299 lbl = gtk.Label(caption)
300 lbl.show()
301 h.pack_start(lbl, False, True)
302 swatch = gtk.ColorButton(qubx.GTK.RGBAtoGDK(color.value))
303 swatch.set_use_alpha(True)
304 swatch.set_alpha(int(round(65535.0*(color.value[3]))))
305 swatch.set_size_request(36, -1)
306 swatch.connect('color-set', self.__onClickSwatch, name)
307 swatch.show()
308 h.pack_end(swatch, False, True)
309 self.swatches.append(swatch)
310 v.pack_start(h, False, True)
313 - def show(self, on_cancel, on_apply):
314 """Shows the dialog and sets a restore action. The colors are changed as you pick them, and the old colors forgotten; you should save a copy.
315
316 @param on_cancel: When the user presses "Cancel", it calls on_cancel() so you can restore their old colors.
317 """
318 gtk.Dialog.show(self)
319 self.on_cancel = on_cancel
320 self.on_apply = on_apply
321
322
323 -def EditOneColor(colorref, caption="Pick a color", appearance=None):
324 """Runs a modal dialog so the user can change one color in the prefs."""
325 appr = appearance or Appearance
326 color = appr.color_by_name[colorref[0]]
327 dlg = gtk.ColorSelectionDialog(caption)
328 picker = dlg.get_color_selection()
329 picker.set_current_color(qubx.GTK.RGBAtoGDK(color.value))
330 picker.set_has_opacity_control(True)
331 picker.set_current_alpha(int(round(65535.0*(color.value[3]))))
332 dlg.ok_button.connect('clicked', lambda it: dlg.response(gtk.RESPONSE_ACCEPT))
333 response = dlg.run()
334 dlg.destroy()
335 if gtk.RESPONSE_ACCEPT == response:
336 color.set(SETALPHA(qubx.GTK.GDKtoRGBA(picker.get_current_color()), picker.get_current_alpha()/65535.0))
337
338
660
711
712 if not self._invalid:
713 OpenGL.GL.glEnable(OpenGL.GL.GL_TEXTURE_2D)
714 OpenGL.GL.glBlendFunc(OpenGL.GL.GL_ONE, OpenGL.GL.GL_ZERO)
715 OpenGL.GL.glBindTexture(OpenGL.GL.GL_TEXTURE_2D, self.texPrimary)
716 OpenGL.GL.glColor4f(1,1,1,1)
717 draw_whole_rect()
718 OpenGL.GL.glDisable(OpenGL.GL.GL_TEXTURE_2D)
719 OpenGL.GL.glBlendFunc(OpenGL.GL.GL_SRC_ALPHA, OpenGL.GL.GL_ONE_MINUS_SRC_ALPHA)
720
721
722 OpenGL.GL.glScalef(1.0, -1.0, 1.0)
723 OpenGL.GL.glTranslatef(0.0, -h, 0.0)
724 OpenGL.GL.glTranslatef(0.375, 0.375, 0.0)
725
726 if self._invalid:
727 self.OnDrawGL(gldrawable, glcontext, w, h)
728
729 OpenGL.GL.glBindTexture(OpenGL.GL.GL_TEXTURE_2D, self.texPrimary)
730 OpenGL.GL.glCopyTexSubImage2D(OpenGL.GL.GL_TEXTURE_2D, 0, 0, 0, 0, 0, w, h)
731
732
733 context = cairo.Context(self.surfOverlay)
734 context.set_operator(cairo.OPERATOR_CLEAR)
735 context.paint()
736 context.set_operator(cairo.OPERATOR_OVER)
737 self.draw_all_overlays(context, w, h)
738
739 OpenGL.GL.glBindTexture(OpenGL.GL.GL_TEXTURE_2D, self.texOverlay)
740 OpenGL.GL.glTexSubImage2D(OpenGL.GL.GL_TEXTURE_2D, 0, 0, 0, self.pow2w, self.pow2h, OpenGL.GL.GL_BGRA, OpenGL.GL.GL_UNSIGNED_BYTE, self.pixOverlay)
741
742 OpenGL.GL.glEnable(OpenGL.GL.GL_TEXTURE_2D)
743 OpenGL.GL.glColor4f(1,1,1,1)
744 draw_whole_rect()
745 OpenGL.GL.glDisable(OpenGL.GL.GL_TEXTURE_2D)
746
747 if self.can_focus and self.__focused:
748 OpenGL.GL.glColor4f(*self.appearance.color(COLOR_FOCUSED))
749 OpenGL.GL.glLineWidth(0.5*self.appearance.emsize)
750 OpenGL.GL.glBegin(OpenGL.GL.GL_LINE_STRIP)
751 OpenGL.GL.glVertex3f(0, 0, 0)
752 OpenGL.GL.glVertex3f(0, h, 0)
753 OpenGL.GL.glVertex3f(w, h, 0)
754 OpenGL.GL.glVertex3f(w, 0, 0)
755 OpenGL.GL.glVertex3f(0, 0, 0)
756 OpenGL.GL.glEnd()
757
758 if gldrawable.is_double_buffered():
759 gldrawable.swap_buffers()
760 else:
761 OpenGL.GL.glFlush()
762
763 gldrawable.gl_end()
764
765 else:
766
767 context = self.window.cairo_create()
768 if self._invalid:
769 subctx = cairo.Context(self.surfCairo)
770 self.appearance.setup_context(subctx)
771 subctx.set_operator(cairo.OPERATOR_CLEAR)
772 subctx.paint()
773 subctx.set_operator(cairo.OPERATOR_OVER)
774
775
776 self.OnDraw(subctx, w, h)
777
778 subctx = cairo.Context(self.surfOverlay)
779
780
781 subctx.set_source_rgba(0,0,0,1)
782 subctx.paint()
783 subctx.set_source_surface(self.surfCairo, 0, 0)
784 subctx.paint()
785 self.draw_all_overlays(subctx, w, h)
786 context.set_source_surface(self.surfOverlay, 0, 0)
787 context.paint()
788 if self.can_focus and self.__focused:
789 context.set_source_rgba(*self.appearance.color(COLOR_FOCUSED))
790 context.set_line_width(0.5*self.appearance.emsize)
791 context.rectangle(0, 0, w, h)
792 context.stroke()
793 self._invalid = False
845 - def draw_to_context(self, context, width, height, fat=1.0, unit_width=0.0, overlay=True):
846 """Draws everything to a cairo context with width and height.
847 @param fat: line width scaler
848 @param unit_width: kluge for printing
849 @param overlay: include overlays and layers (False: just OnDraw)
850 """
851 save_dim = self._dim
852 self._dim = width, height
853 f, u = self._fat, self._unit_width
854 self._fat, self._unit_width = fat, unit_width
855 w, h = width, height
856 self.appearance.setup_context(context)
857 context.save()
858
859
860 context.set_source_rgb(1, 1, 1)
861 context.rectangle(0, 0, w, h)
862 context.fill()
863 self.OnDraw(context, w, h)
864 context.restore()
865 if overlay:
866 self.draw_all_overlays(context, w, h)
867
868 self._fat, self._unit_width = f, u
869 self._dim = save_dim
870
871 GLSpace = ToolSpace
872 HAVE_OPENGL = False
873 if False:
874 try:
875 import __main__
876 import os
877 try:
878 glprefs_path = os.path.join(__main__.QUBX_HOME_PATH, 'Presets', 'Appearance', '__active.qpr')
879 try:
880 glprefs = qubx.tree.Open(glprefs_path, True).find('opengl')
881 wanted = glprefs.data and glprefs.data[0]
882 except:
883 wanted = True
884 if wanted:
885 import OpenGL
886 OpenGL.ERROR_CHECKING = False
887 import gtk.gtkgl
888 import gtk.gdkgl
889 import OpenGL.GL
890 import OpenGL.GLU
891 HAVE_OPENGL = True
894 GLSpace = ToolSpace_GL
895 finally:
896 try:
897 del glprefs
898 except:
899 pass
900 except:
901 traceback.print_exc()
902
903
904
907 self.layers = layers[:]
908 self.__space = None
909 space = property(lambda self: self.__space and self.__space())
938
940 if space.window:
941 space.window.set_cursor(cursor)
942 elif counter < 100:
943 gobject.idle_add(activate_cursor, space, cursor, counter+1)
944
1018
1019
1020
1022 """A floating, transparent, clickable area in a L{ToolSpace}.
1023
1024 @ivar appearance: L{TS_Appearance} of the L{ToolSpace}
1025 @ivar rq_x: requested x coord, in units of appearance.emsize
1026 @ivar rq_y: requested y coord, in units of appearance.emsize
1027 @ivar rq_w: requested width, in units of appearance.emsize; negative to specify distance from right edge
1028 @ivar rq_h: requested height, in units of appearance.emsize; negative to specify distance from bottom
1029 @ivar w_min: minimum requested width in ems, if rq_w is negative
1030 @ivar h_min: minimum requested height in ems, if rq_h is negative
1031 @ivar x: actual x coord, in pixels
1032 @ivar y: actualy coord, in pixels
1033 @ivar w: actual width, in pixels
1034 @ivar h: actual height, in pixels
1035 @ivar cBG: background COLORREF
1036 @ivar border: width of border in pixels
1037 @ivar cBorder: border COLORREF
1038 @ivar subs: list of L{SubLayer}
1039 @ivar surface: cairo image surface for double buffering
1040 @ivar invalid: True if surface needs to be repainted
1041 @ivar OnInvalidate: L{WeakEvent}(Layer) called when it needs repainting
1042 """
1045 """
1046 @param x: x >= 0: number of 'M' widths from the ToolSpace's left edge; x < 0: number of ... ToolSpace's right edge
1047 @param y: y >= 0: number of 'M' widths from the ToolSpace's top edge; y < 0: number of ... ToolSpace's bottom edge
1048 @param w: w >= 0: number of 'M' widths across; w < 0: number of 'M's between Layer's right and ToolSpace's right
1049 @param h: h >= 0: number of 'M' widths tall; h < 0: number of 'M's between Layer's bottom and ToolSpace's bottom
1050 @param cBG: COLORREF of background
1051 @param border: width in pixels of border
1052 @param cBorder: COLORREF of border
1053 @param w_min: minimum number of 'M' widths across, if w is negative
1054 @param h_min: minimum number of 'M' widths tall, if h is negative
1055 """
1056 self.__ref = Reffer()
1057 self.appearance = Appearance
1058 self.rq_x = x
1059 self.rq_y = y
1060 self.rq_w = w
1061 self.rq_h = h
1062 self.rq_w_min = w_min if (w < 0) else w
1063 self.rq_h_min = h_min if (h < 0) else h
1064 self.__x, self.__y = x, y
1065 self.__w, self.__h = w, h
1066 self.cBG = cBG
1067 self.border = border
1068 self.cBorder = cBorder
1069 self.subs = []
1070 self.sub = None
1071 self.__sub_hover = None
1072 self.surface = None
1073 self.invalid = True
1074 self.OnInvalidate = WeakEvent()
1075 self.__space = None
1077 if space is None:
1078 self.__space = None
1079 else:
1080 self.__space = weakref.ref(space)
1081 space = property(lambda self: self.__space and self.__space(), lambda self, x: self.set_space(x))
1083 if self.rq_x == x: return
1084 self.rq_x = x
1085 self.invalidate()
1087 if self.rq_y == y: return
1088 self.rq_y = y
1089 self.invalidate()
1091 if self.rq_w == w: return
1092 self.rq_w = w
1093 if w >= 0:
1094 self.rq_w_min = w
1095 self.invalidate()
1097 if self.rq_h == h: return
1098 self.rq_h = h
1099 if h >= 0:
1100 self.rq_h_min = h
1101 self.invalidate()
1103 if self.rq_w_min == w: return
1104 self.rq_w_min = w
1105 self.invalidate()
1107 if self.rq_h_min == h: return
1108 self.rq_h_min = h
1109 self.invalidate()
1110 x = property(lambda self: self.__x, lambda self, x: self.set_x(x))
1111 y = property(lambda self: self.__y, lambda self, x: self.set_y(x))
1112 w = property(lambda self: self.__w, lambda self, x: self.set_w(x))
1113 h = property(lambda self: self.__h, lambda self, x: self.set_h(x))
1114 w_min = property(lambda self: self.rq_w_min, lambda self, x: self.set_w_min(x))
1115 h_min = property(lambda self: self.rq_h_min, lambda self, x: self.set_h_min(x))
1117 """Returns True if x,y is inside this layer."""
1118 return (self.__x <= x < (self.__x + self.__w)) and (self.__y <= y < (self.__y + self.__h))
1120 """Requests repaint."""
1121 self.invalid = True
1122 self.OnInvalidate(self)
1123 - def __reposition(self, space_w, space_h, appearance, resurface=True):
1124 em = appearance.emsize
1125 self.__x = int(round(em*((self.rq_x < 0) and (space_w*1.0/em + self.rq_x) or self.rq_x)))
1126 self.__y = int(round(em*((self.rq_y < 0) and (space_h*1.0/em + self.rq_y) or self.rq_y)))
1127 w = max(0, min(16384, int(round(em*((self.rq_w < 0) and (space_w*1.0/em - self.__x*1.0/em + self.rq_w) or self.rq_w)))))
1128 h = max(0, min(16384, int(round(em*((self.rq_h < 0) and (space_h*1.0/em - self.__y*1.0/em + self.rq_h) or self.rq_h)))))
1129 if (w != self.__w) or (h != self.__h) or not self.surface:
1130 self.__w = w
1131 self.__h = h
1132 if resurface:
1133 self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h)
1134 self.invalid = True
1135 for sub in self.subs:
1136 if sub.rq_x < 0:
1137 sub.set_actual_x(w*1.0/em + sub.rq_x)
1138 if sub.rq_y < 0:
1139 sub.set_actual_y(h*1.0/em + sub.rq_y)
1140 - def render(self, space_w, space_h, appearance):
1141 """Repaints (and repositions) surface, if needed."""
1142 self.__reposition(space_w, space_h, appearance)
1143 if (not self.invalid) and (self.appearance == appearance):
1144 return
1145 self.appearance = appearance
1146 self.invalid = False
1147 context = cairo.Context(self.surface)
1148 appearance.setup_context(context)
1149 context.set_operator(cairo.OPERATOR_SOURCE)
1150 context.set_source_rgba(0,0,0,0)
1151 context.paint()
1152 context.set_operator(cairo.OPERATOR_OVER)
1153 context.translate(-self.x, -self.y)
1154 self.render_to(context, space_w, space_h, appearance)
1155 - def render_to(self, context, space_w, space_h, appearance):
1156 """Paints directly into a cairo context, as for copy/print."""
1157 saved = self.appearance, self.__w, self.__h, self.__x, self.__y
1158 self.appearance = appearance
1159 self.__reposition(space_w, space_h, appearance, resurface=False)
1160 w, h = self.__w, self.__h
1161 context.translate(self.x, self.y)
1162
1163 context.save()
1164 context.rectangle(0, 0, w, h)
1165 if self.border:
1166 context.set_line_width(self.border)
1167 context.set_source_rgba(* appearance.color(self.cBorder))
1168 context.stroke_preserve()
1169 context.set_source_rgba(* self.appearance.color(self.cBG))
1170 context.fill()
1171 context.restore()
1172 context.set_source_rgba(* self.appearance.color(LAYER_FG))
1173
1174 context.save()
1175 self.draw(context)
1176 context.restore()
1177
1178 em = appearance.emsize
1179 for sub in reversed(self.subs):
1180 context.save()
1181 context.translate(em*sub.x, em*sub.y)
1182 sub.draw(context, w, h, appearance)
1183 context.restore()
1184 self.appearance, self.__w, self.__h, self.__x, self.__y = saved
1186 """Adds a L{SubLayer} (label or control)."""
1187 self.subs.insert(0, sub)
1188 sub.layer = self
1189 sub.OnInvalidate += self.__ref(self.invalidate)
1190 self.invalidate()
1191 self.__w = 0
1192 return sub
1200 """Removes all sublayers."""
1201 for sub in self.subs:
1202 sub.layer = None
1203 sub.OnInvalidate -= self.__ref(self.invalidate)
1204 self.subs = []
1205 self.invalidate()
1206 - def draw(self, context):
1207 """Override this method to draw on the layer itself."""
1208 pass
1210 """Override this method to do something when the layer is added to a L{ToolSpace}."""
1211 pass
1213 """Override this method to do something when the layer is removed from a L{ToolSpace}."""
1214 pass
1216 """Override this method to do something when the mouse enters the layer."""
1217 pass
1219 """Override this method to do something when the mouse leaves the layer."""
1220 self.sub_hover = None
1225 """Returns the L{SubLayer} at local coordinates (x, y), or None."""
1226 em = self.appearance.emsize
1227 for sub in self.subs:
1228 if sub.pt_in_rect(x, y, em):
1229 return sub
1230 return None
1232 """If there is a L{SubLayer} at (x,y), passes it the event and returns False. (True if there's no sub)"""
1233 self.sub = self.find_sub(x, y)
1234 if self.sub:
1235 em = self.appearance.emsize
1236 self.sub.button_press(x-em*self.sub.x, y-em*self.sub.y, event)
1237 return False
1238 return True
1243 """If there is a L{SubLayer} at (x,y), passes it the event and returns False. (True if there's no sub)"""
1244 if self.sub:
1245 em = self.appearance.emsize
1246 self.sub.button_release(x-em*self.sub.x, y-em*self.sub.y, event)
1247 self.sub = None
1248 return False
1249 return True
1261 sub_hover = property(lambda self: self.__sub_hover, lambda self, x: self.set_sub_hover(x))
1263 """Override this method to handle mouse motion."""
1264 em = self.appearance.emsize
1265 if self.sub:
1266 self.sub.mouse_drag(x-em*self.sub.x, y-em*self.sub.y, event)
1267 return
1268 if self.sub_hover:
1269 if not self.sub_hover.pt_in_rect(x, y, em):
1270 self.sub_hover = None
1271 if not self.sub_hover:
1272 self.set_sub_hover(self.find_sub(x, y), x, y, event)
1273 if self.sub_hover:
1274 self.sub_hover.mouse_move(x-em*self.sub_hover.x, y-em*self.sub_hover.y, event)
1282
1283
1286
1288 """A specific display element or control on a L{Layer} in a L{ToolSpace}.
1289
1290 @ivar x: number of 'M' widths between Layer's left and SubLayer's left
1291 @ivar y: number of 'M' widths between Layer's top and SubLayer's top
1292 @ivar rq_w: requested width, in 'M' units
1293 @ivar rq_h: requested height, in 'M' units
1294 @ivar w: actual width, in pixels
1295 @ivar h: actual height, in pixels
1296 @ivar action: when clicked, calls action(x, y, event)
1297 @ivar scroll: when mouse-scroll-wheeled upon, calls scroll(x, y, event, offset)
1298 @ivar mouse_move: when the mouse moves over it, calls mouse_move(x, y, event)
1299 @ivar mouse_drag: when the mouse moves with the button down, calls mouse_drag(x, y, event)
1300 @ivar enter: when the mouse enters, calls mouse_enter(x, y, event)
1301 @ivar exit: when the mouse leaves, calls mouse_exit()
1302 @ivar border: width of border in pixels
1303 @ivar cBorder: border COLORREF
1304 @ivar cBG: background COLORREF, or None for transparent
1305 @ivar invalid: True if it needs to be repainted
1306 @ivar OnInvalidate: L{WeakEvent}(SubLayer) called when it needs repainting
1307 @ivar OnChangeTooltip: L{WeakEvent}(SubLayer)
1308 @ivar OnHideTooltip: L{WeakEvent}(SubLayer) subclasses can call to request tooltip hide
1309 """
1310 - def __init__(self, x=0, y=0, w=1, h=1, action=ignore_event, scroll=ignore_event,
1311 mouse_move=ignore_event, mouse_drag=ignore_event,
1312 enter=ignore_event, exit=ignore_event,
1313 border=0, cBorder=LAYER_BORDER, tooltip="", cBG=None, **kw):
1314 """
1315 @param x: number of 'M' widths from the Layer's left edge
1316 @param y: number of 'M' widths from the Layer's top edge
1317 @param w: w >= 0: number of 'M' widths across; w < 0: number of 'M's between SubLayer's right and Layer's right
1318 @param h: h >= 0: number of 'M' widths tall; h < 0: number of 'M's between SubLayer's bottom and Layer's bottom
1319 @param action: when clicked, calls action(x, y, event)
1320 @param scroll: when mouse-scroll-wheeled upon, calls scroll(x, y, event, offset)
1321 @param mouse_move: when the mouse moves over it, calls mouse_move(x, y, event)
1322 @param mouse_drag: when the mouse moves with the button down, calls mouse_drag(x, y, event)
1323 @param enter: when the mouse enters, calls mouse_enter(x, y, event)
1324 @param exit: when the mouse leaves, calls mouse_exit()
1325 @param border: width in pixels of border
1326 @param cBorder: COLORREF of border
1327 @param cBG: COLORREF of background, or None for transparent
1328 @param tooltip: mouse-over info
1329 """
1330 self.rq_x = x
1331 self.rq_y = y
1332 self.__x = x
1333 self.__y = y
1334 self.rq_w = w
1335 self.rq_h = h
1336 self.__w = w
1337 self.__h = h
1338 self.__tooltip = ""
1339 self.__action = WeakCall("%s.action"%self.__class__)
1340 self.__action.assign(action)
1341 self.__scroll = WeakCall("%s.scroll"%self.__class__)
1342 self.__scroll.assign(scroll)
1343 self.__mouse_move = WeakCall("%s.mouse_move"%self.__class__)
1344 self.__mouse_move.assign(mouse_move)
1345 self.__mouse_drag = WeakCall("%s.mouse_drag"%self.__class__)
1346 self.__mouse_drag.assign(mouse_drag)
1347 self.__enter = WeakCall("%s.enter"%self.__class__)
1348 self.__enter.assign(enter)
1349 self.__exit = WeakCall("%s.exit"%self.__class__)
1350 self.__exit.assign(exit)
1351 self.border = border
1352 self.cBorder = cBorder
1353 self.__cBG = cBG
1354 self.invalid = True
1355 self.OnInvalidate = WeakEvent()
1356 self.OnChangeTooltip = WeakEvent()
1357 self.OnHideTooltip = WeakEvent()
1358 self.__layer = None
1359 self.tooltip = tooltip
1360 for k in kw:
1361 self.__dict__[k] = kw[k]
1363 self.__layer = None if (layer is None) else weakref.ref(layer)
1364 layer = property(lambda self: self.__layer and self.__layer(), lambda self, x: self.set_layer(x))
1365 action = property(lambda self: self.__action, lambda self, x: self.__action.assign(x))
1366 scroll = property(lambda self: self.__scroll, lambda self, x: self.__scroll.assign(x))
1367 mouse_move = property(lambda self: self.__mouse_move, lambda self, x: self.__mouse_move.assign(x))
1368 mouse_drag = property(lambda self: self.__mouse_drag, lambda self, x: self.__mouse_drag.assign(x))
1369 enter = property(lambda self: self.__enter, lambda self, x: self.__enter.assign(x))
1370 exit = property(lambda self: self.__exit, lambda self, x: self.__exit.assign(x))
1372 if x != self.__x:
1373 self.__x = self.rq_x = x
1374 self.invalidate()
1376 if y != self.__y:
1377 self.__y = self.rq_y = y
1378 self.invalidate()
1380 if w != self.__w:
1381 self.rq_w = w
1382 self.invalidate()
1384 if h != self.__h:
1385 self.rq_h = h
1386 self.invalidate()
1393 x = property(lambda self: self.__x, lambda self, x: self.set_x(x))
1394 y = property(lambda self: self.__y, lambda self, x: self.set_y(x))
1395 w = property(lambda self: self.__w, lambda self, x: self.set_w(x))
1396 h = property(lambda self: self.__h, lambda self, x: self.set_h(x))
1397 tooltip = property(lambda self: self.__tooltip, lambda self, x: self.set_tooltip(x))
1398 cBG = property(lambda self: self.__cBG, lambda self, x: self.set_cBG(x))
1404 """Returns True if (x, y) Layer-relative coordinates are inside this SubLayer.
1405 @param em: width of 'M' in pixels
1406 """
1407 return (self.__x*em <= x < (self.__x*em + self.__w)) and (self.__y*em <= y < (self.__y*em + self.__h))
1409 """Marks this SubLayer as needing a repaint, and calls OnInvalidate."""
1410 self.invalid = True
1411 self.OnInvalidate(self)
1412 - def draw(self, context, w, h, appearance):
1413 """Override this method to paint the SubLayer. Don't forget to call SubLayer.draw(context, w, h, appearance)."""
1414 self.invalid = False
1415 em = appearance.emsize
1416 self.__w = em*max(1, (self.rq_w < 0) and (w*1.0/em - self.__x + self.rq_w) or self.rq_w)
1417 self.__h = em*max(1, (self.rq_h < 0) and (h*1.0/em - self.__y + self.rq_h) or self.rq_h)
1418 if self.cBG:
1419 context.set_source_rgba(* appearance.color(self.cBG))
1420 context.rectangle(0, 0, self.__w, self.__h)
1421 context.fill()
1422 if self.border:
1423 context.save()
1424 context.set_source_rgba(* appearance.color(self.cBorder))
1425 context.set_line_width(self.border)
1426 context.rectangle(0, 0, self.__w, self.__h)
1427 context.stroke()
1428 context.restore()
1436
1437
1439 """Represents a rectangle that has been rotated theta radians about (x0, y0).
1440
1441 @ivar l: left
1442 @ivar t: top
1443 @ivar r: right
1444 @ivar b: bottom
1445 @ivar x0: x-coord of the center of rotation
1446 @ivar y0: y-coord of the center of rotation
1447 @ivar theta: radians to rotate about x0, y0
1448 """
1449 - def __init__(self, l, t, r, b, x0=0, y0=0, theta=0, **kw):
1450 """
1451 @param l: left
1452 @param t: top
1453 @param r: right
1454 @param b: bottom
1455 @param x0: x-coord of the center of rotation
1456 @param y0: y-coord of the center of rotation
1457 @param theta: radians to rotate about x0, y0
1458 @param ...: adds any additional keyword arguments as instance variables
1459 """
1460 self.l = l
1461 self.t = t
1462 self.r = r
1463 self.b = b
1464 self.x0 = x0
1465 self.y0 = y0
1466 self.theta = theta
1467 for k,v in kw.iteritems():
1468 self.__dict__[k] = v
1470 """Returns True if (x,y) is within the rotated rectangle."""
1471 if self.theta or self.x0 or self.y0:
1472 costh, sinth = cos(-self.theta), sin(-self.theta)
1473 x, y = costh*(x-self.x0) - sinth*(y-self.y0), sinth*(x-self.x0) + costh*(y-self.y0)
1474 return (self.l <= x <= self.r) and (self.t <= y <= self.b)
1475
1476
1532
1533
1580
1581
1582
1584 """Demo tool to shine a spotlight on a L{ToolSpace}."""
1593 context.save()
1594 mask = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h)
1595 maskex = cairo.Context(mask)
1596 maskex.set_operator(cairo.OPERATOR_SOURCE)
1597 maskex.set_source_rgba(0,0,0,.2)
1598 maskex.paint()
1599 maskex.set_source_rgba(0,0,0,0)
1600 maskex.arc(self.x+4, self.y+6, 15, 0, 2*pi)
1601 maskex.fill()
1602 context.set_source_surface(mask)
1603 context.paint()
1604 context.restore()
1605
1606
1607
1696
1697
1814
1815
1816
1817
1819 """Displays a string of text.
1820
1821 @ivar label: a string
1822 @ivar color: COLORREF for text
1823 @ivar hover_color: COLORREF for mouse-over text
1824 """
1826 """See also: L{SubLayer.__init__}
1827
1828 @param label: a string
1829 @param justify: negative: left-justify; zero: center; positive: right-justify
1830 @param vcenter: True/nonzero to center vertically
1831 @param color: COLORREF for text
1832 @param hover_color: COLORREF for mouse-over text
1833 """
1834 self.__ref = Reffer()
1835 kw['enter'] = self.__ref(self.__enter)
1836 kw['exit'] = self.__ref(self.__exit)
1837 SubLayer.__init__(self, *args, **kw)
1838 self._label = label
1839 self.justify = justify
1840 self.vcenter = vcenter
1841 self._color = color
1842 self.hover_color = hover_color
1843 self._rotate = rotate
1844 self.bx = self.by = 0
1845 self.entered = False
1850 label = property(lambda self: self._label, lambda self, x: self.set_label(x))
1854 color = property(lambda self: self._color, lambda self, x: self.set_color(x))
1856 if self.entered:
1857 context.set_source_rgba(* appearance.color(self.hover_color))
1858 else:
1859 context.set_source_rgba(* appearance.color(self._color))
1863 rotate = property(lambda self: self._rotate, lambda self, x: self.set_rotate(x))
1864 - def draw(self, context, w, h, appearance):
1865 SubLayer.draw(self, context, w, h, appearance)
1866 context.save()
1867 self.set_if_color(context, appearance)
1868 if self.entered and not self._label:
1869 context.set_source_rgba(*SETALPHA(appearance.color(self.hover_color), .2))
1870 context.rectangle(0, 0, self.w, self.h)
1871 context.fill()
1872
1873 fascent, fdescent, fheight, fxadvance, fyadvance = context.font_extents()
1874 xbearing, ybearing, width, height, xadvance, yadvance = context.text_extents(self._label)
1875 vtotal = fascent + fdescent
1876 w, h = self.w, self.h
1877 if self._rotate:
1878 if pi/4 < abs(self._rotate) < 3*pi/4:
1879 w, h = h, w
1880 context.translate(self.w/2, self.h/2)
1881 context.rotate(-self._rotate)
1882 context.translate(-w/2, -h/2)
1883 if self.vcenter:
1884 self.by = (h - vtotal)/2 + fascent
1885 else:
1886 self.by = (h - vtotal) + fascent
1887 if self.justify < 0:
1888 self.bx = - xbearing
1889 elif self.justify > 0:
1890 self.bx = (w - width) - xbearing
1891 else:
1892 self.bx = (w - width)/2 - xbearing
1893
1894 context.move_to(self.bx, self.by)
1895 context.show_text(self.label)
1896 context.restore()
1898 self.entered = True
1899 self.OnInvalidate(self)
1901 self.entered = False
1902 self.OnInvalidate(self)
1903
1905 """The little up and down buttons, such as next to a number."""
1907 """See also: L{SubLayer.__init__}
1908
1909 @param up: when the up button is clicked, calls up()
1910 @param down: when the down button is clicked, calls down()
1911 """
1912 self.__ref = Reffer()
1913 kw['action'] = self.__ref(self.__action)
1914 kw['mouse_move'] = self.__ref(self.__mouse_move)
1915 kw['exit'] = self.__ref(self.__exit)
1916 SubLayer.__init__(self, *args, **kw)
1917 self.__up = WeakCall("SubLayer_UpDown.up")
1918 self.__up.assign(up)
1919 self.__down = WeakCall("SubLayer_UpDown.down")
1920 self.__down.assign(down)
1921 self.mouse_x = self.mouse_y = -1
1922 self.color = color
1923 up = property(lambda self: self.__up, lambda self, x: self.__up.assign(x))
1924 down = property(lambda self: self.__down, lambda self, x: self.__down.assign(x))
1926 if y < self.h/2:
1927 self.up()
1928 else:
1929 self.down()
1931 self.mouse_x, self.mouse_y = x, y
1932 self.OnInvalidate(self)
1934 self.mouse_x = self.mouse_y = -1
1935 self.OnInvalidate(self)
1936 - def draw(self, context, w, h, appearance):
1937 SubLayer.draw(self, context, w, h, appearance)
1938 context.set_source_rgba(* appearance.color(self.color))
1939 context.set_line_width(appearance.emsize / 5.0)
1940 context.save()
1941 context.move_to(self.w/2, 2)
1942 context.line_to(1, self.h/2-2)
1943 context.line_to(self.w-1, self.h/2-2)
1944 if 0 <= self.mouse_y < self.h/2:
1945 context.fill_preserve()
1946 context.stroke()
1947 else:
1948 context.fill()
1949 context.restore()
1950 context.save()
1951 context.move_to(self.w/2, self.h-2)
1952 context.line_to(1, self.h/2+2)
1953 context.line_to(self.w-1, self.h/2+2)
1954 if self.h/2 <= self.mouse_y:
1955 context.fill_preserve()
1956 context.stroke()
1957 else:
1958 context.fill()
1959 context.restore()
1960
1961
1963 """A box that is active or not; click to toggle."""
1980 return self.__active
1982 if self.__active != x:
1983 self.__active = x
1984 self.invalidate()
1985 active = property(get_active, set_active)
1987 self.__active = not self.__active
1988 self.OnToggle(self, self.__active)
1989 self.invalidate()
1990 - def draw(self, context, w, h, appearance):
1991 SubLayer.draw(self, context, w, h, appearance)
1992 r, g, b, a = appearance.color(self.color)
1993 if self.entered:
1994 h, s, v = RGBtoHSV(r, g, b)
1995 r, g, b = HSVtoRGB(h, .99, .99)
1996 context.set_source_rgba(r, g, b, a)
1997 context.set_line_width(appearance.emsize/5.0)
1998 side = 0.8 * min(self.w, self.h)
1999 y0 = (self.h - side) / 2
2000 if self.caption:
2001 x0 = .75*appearance.emsize
2002 else:
2003 x0 = (self.w - side) / 2
2004 context.rectangle(x0, y0, side, side)
2005 if self.__active:
2006 context.stroke_preserve()
2007 context.fill()
2008 else:
2009 context.stroke()
2010 if self.caption:
2011 fascent, fdescent, fheight, fxadvance, fyadvance = context.font_extents()
2012 xbearing, ybearing, width, height, xadvance, yadvance = context.text_extents(self.caption)
2013 vtotal = fascent + fdescent
2014 self.by = (self.h - vtotal)/2 + fascent
2015 self.bx = side + 1.5*appearance.emsize - xbearing
2016 context.move_to(self.bx, self.by)
2017 context.show_text(self.caption)
2019 self.entered = True
2020 self.OnInvalidate(self)
2022 self.entered = False
2023 self.OnInvalidate(self)
2024
2025
2026
2028 """A circle with inscribed triangle, which shows a menu when clicked."""
2030 """See also: L{SubLayer.__init__}
2031
2032 @param popup: a gtk.Menu
2033 @param color: COLORREF for the circle and triangle
2034 @param tooltip: a string to display on mouse-over
2035 @param on_popup: lambda: should_show to modify popup contents or cancel, before showing
2036 """
2037 self.__ref = Reffer()
2038 kw['action'] = self.__ref(self.__action)
2039 kw['enter'] = self.__ref(self.__enter)
2040 kw['exit'] = self.__ref(self.__exit)
2041 SubLayer.__init__(self, *args, **kw)
2042 self.popup = popup
2043 self.color = color
2044 self.tooltip = tooltip
2045 self.__on_popup = WeakCall("SubLayer_Popup.on_popup")
2046 self.__on_popup.assign(on_popup)
2047 self.caption = caption
2048 self.entered = False
2049 on_popup = property(lambda self: self.__on_popup, lambda self, x: self.__on_popup.assign(x))
2059 SubLayer.draw(self, context, w, h, appearance)
2060 if self.entered:
2061 context.set_source_rgba(* SETALPHA(appearance.color(self.color), 1))
2062 context.set_line_width(1.6)
2063 else:
2064 context.set_source_rgba(* SETALPHA(appearance.color(self.color), .7))
2065 context.set_line_width(.6)
2066 context.move_to(self.w-1, self.h/2)
2067 context.arc(self.w - self.h/2, self.h/2, self.h/2-1, 0, 2*pi)
2068 context.stroke()
2069 context.save()
2070 context.translate(2+self.w-self.h, 2)
2071 qubx.GTK.draw_inscribed_triangle(context, self.h-4, self.h-4, NOALPHA(appearance.color(self.color)), 3*pi/2)
2072 context.restore()
2073 if not self.caption:
2074 return
2075 fascent, fdescent, fheight, fxadvance, fyadvance = context.font_extents()
2076 xbearing, ybearing, width, height, xadvance, yadvance = context.text_extents(self.caption)
2077 vtotal = fascent + fdescent
2078 self.by = (self.h - vtotal)/2 + fascent
2079 self.bx = - xbearing
2080 context.set_source_rgba(*appearance.color(self.color))
2081 context.move_to(self.bx+0.25*appearance.emsize, self.by)
2082 context.show_text(self.caption)
2088 self.entered = False
2089 self.OnInvalidate(self)
2090
2112
2134
2135
2137 """A collection of horizontal lines, which shows a menu when clicked."""
2139 """See also: L{SubLayer.__init__}
2140
2141 @param popup: a gtk.Menu
2142 @param tooltip: a string to display on mouse-over
2143 @param on_popup: lambda: should_show to modify popup contents or cancel, before showing
2144 """
2145 self.__ref = Reffer()
2146 kw['action'] = self.__ref(self.__action)
2147 kw['enter'] = self.__ref(self.__enter)
2148 kw['exit'] = self.__ref(self.__exit)
2149 SubLayer.__init__(self, *args, **kw)
2150 self.popup = popup
2151 self.tooltip = tooltip
2152 self.__on_popup = WeakCall("SubLayer_MenuLines.on_popup")
2153 self.__on_popup.assign(on_popup)
2154 self.entered = False
2155 on_popup = property(lambda self: self.__on_popup, lambda self, x: self.__on_popup.assign(x))
2165 SubLayer.draw(self, context, w, h, appearance)
2166 context.set_source_rgba(.5, .5, .5, .75)
2167 context.rectangle(1, 1, self.w-2, self.h-2)
2168 context.fill()
2169 if self.entered:
2170 context.set_source_rgba(1, 1, 1, 1)
2171 else:
2172 context.set_source_rgba(0, 0, 0, .9)
2173 num_line = 3
2174 num_div = 2*num_line + 1
2175 div = (self.h-2) * 1.0 / num_div
2176 context.save()
2177 context.translate(1, 1)
2178 for i in xrange(num_line):
2179 cutout = .1 if i else 0.0
2180 context.rectangle((.1+cutout)*self.w, (2*i+1)*div, (.7-cutout)*self.w, 0.7*div)
2181 context.fill()
2182 context.restore()
2189
2211
2212
2214 """A combo-box in the style of csDropDownList (enumerated choices only). Uses L{Tool_DropDown} to show the menu items.
2215 Displays the menu using a L{Tool}, therefore can't be used in L{Tool}.layers, only in L{ToolSpace} proper.
2216
2217 @ivar label: string that's showing when the menu is not open
2218 @ivar menu: you directly edit this list of menu items (label, action), where label is a string, and action() is called when clicked.
2219 @ivar OnDropDown: L{WeakEvent}(SubLayer_DropDown) before the menu is shown
2220 """
2222 """See also: L{SubLayer.__init__}
2223
2224 @param label: string that shows when the menu is not open
2225 @param justify: negative: left-justify; zero: center: positive; right-justify
2226 @param vcenter: True/nonzero to center text vertically
2227 @param color: COLORREF for the inscribed triangle
2228 """
2229 self.__ref = Reffer()
2230 kw['action'] = self.__ref(self._action)
2231 SubLayer.__init__(self, *args, **kw)
2232 self._label = label
2233 self.justify = justify
2234 self.vcenter = vcenter
2235 self.menu = []
2236 self._color = color
2237 self.bx = self.by = 0
2238 self.OnDropDown = WeakEvent()
2243 label = property(lambda self: self._label, lambda self, x: self.set_label(x))
2247 color = property(lambda self: self._color, lambda self, x: self.set_color(x))
2248 - def draw(self, context, w, h, appearance):
2249 SubLayer.draw(self, context, w, h, appearance)
2250 lblw = self.w - appearance.emsize - self.h
2251
2252 fascent, fdescent, fheight, fxadvance, fyadvance = context.font_extents()
2253 xbearing, ybearing, width, height, xadvance, yadvance = context.text_extents(self._label)
2254 vtotal = fascent + fdescent
2255 if self.vcenter:
2256 self.by = (self.h - vtotal)/2 + fascent
2257 else:
2258 self.by = (self.h - vtotal) + fascent
2259 if self.justify < 0:
2260 self.bx = - xbearing
2261 elif self.justify > 0:
2262 self.bx = (lblw - width) - xbearing
2263 else:
2264 self.bx = (lblw - appearance.emsize - self.h - width)/2 - xbearing
2265
2266
2267 if self.color:
2268 context.set_source_rgba(* appearance.color(self._color))
2269 context.move_to(self.bx, self.by)
2270 context.show_text(self.label)
2271 context.translate(lblw+appearance.emsize+2, 2)
2272 qubx.GTK.draw_inscribed_triangle(context, self.h-4, self.h-4, NOALPHA(appearance.color(self.color)), 3*pi/2)
2279
2280
2281
2282
2283
2284
2350
2351
2352
2354 """A slightly nonstandard scroll bar; grab the left or right edge to resize the thumb.
2355
2356 @ivar bounds: (lo, hi) smallest and largest possible value
2357 @ivar quantum: smallest delta value
2358 @ivar left: value at left edge of thumb
2359 @ivar right: value at right edge of thumb
2360 @ivar OnMoving: L{WeakEvent}(SubLayer_Range, left, right) called when the user is still dragging
2361 @ivar OnSet: L{WeakEvent}(SubLayer_Range, left, right, by_mouse) called when left and/or right have changed
2362 """
2363 - def __init__(self, lo, hi, quantum, *args, **kw):
2379 lo, hi = x
2380 hi = max(hi, lo+self.quantum)
2381 if (hi != self._hi) or (lo != self._lo):
2382 self._lo, self._hi = lo, hi
2383 self._range = hi - lo
2384 l,r = self._left, self._right
2385 wid = min(r-l, hi-lo)
2386 if l < lo:
2387 l += (lo - l)
2388 r = l + wid
2389 elif r > hi:
2390 r -= (r - hi)
2391 l = r - wid
2392 if (r != self._right) or (l != self._left):
2393 self._left, self._right = l,r
2394 if event_if_moved: self.OnSet(self, l, r, False)
2395 self.invalidate()
2396 - def set_range(self, l, r, event_if_moved=True, by_mouse=False):
2397 l = max(self._lo, min(r-self.quantum, l))
2398 r = min(self._hi, max(l+self.quantum, r))
2399 if (l != self._left) or (r != self._right):
2400 self._left, self._right = l,r
2401 self.invalidate()
2402 if event_if_moved:
2403 self.doOnSet(by_mouse)
2405 l = max(self._lo, min(self._right-self.quantum, x))
2406 if l != self._left:
2407 self._left = l
2408 self.invalidate()
2409 self.doOnSet()
2411 r = min(self._hi, max(self._left+self.quantum, x))
2412 if r != self._right:
2413 self._right = r
2414 self.invalidate()
2415 self.doOnSet()
2416 - def doOnSet(self, by_mouse=False):
2417 if self._inhandle or self._inleft or self._inright:
2418 self.OnMoving(self, self._left, self._right)
2419 else:
2420 self.OnSet(self, self._left, self._right, by_mouse)
2422 self._quantum = x
2423 if (self._right - self._left) < x:
2424 self.set_range(self._left, self._left + x)
2425 bounds = property(lambda self: (self._lo, self._hi), lambda self, x: self.set_bounds(x))
2426 left = property(lambda self: self._left, lambda self, x: self.set_left(x))
2427 right = property(lambda self: self._right, lambda self, x: self.set_right(x))
2428 quantum = property(lambda self: self._quantum, lambda self, x: self.set_quantum(x))
2430 return int(round( self.w * float(x - self._lo) / self._range ))
2432 return self._range * float(p) / self.w + self._lo
2434 em = self.layer.appearance.emsize
2435 pl, pr = self.x2p(self._left), self.x2p(self._right)
2436 if (pr - pl) < (2*em):
2437 pl -= em
2438 pr += em
2439 inleft = (-em/2) <= (x - pl) <= (em/2)
2440 if inleft:
2441 inright = False
2442 else:
2443 inright = (-em/2) <= (x - pr) <= (em/2)
2444 if inleft or inright:
2445 inhandle = False
2446 else:
2447 inhandle = pl <= x <= pr
2448 if (inhandle != self._inhandle) or (inleft != self._inleft) or (inright != self._inright):
2449 self._inhandle, self._inleft, self._inright = inhandle, inleft, inright
2450 self.invalidate()
2452 self._inhandle = self._inleft = self._inright = False
2453 self.invalidate()
2472 dx = self._range * float(x - self.bx) / self.w
2473 if self._inleft:
2474 self.left = self.vv + dx
2475 elif self._inright:
2476 self.right = self.vv + dx
2477 elif self._inhandle:
2478 left = self.vv + dx
2479 range_x = (self._right - self._left)
2480 left = max(left, self._lo)
2481 left = min(left, self._hi - range_x)
2482 dx = left - self.vv
2483 self._right = left + range_x
2484 self.left = left
2485
2486 - def draw(self, context, w, h, appearance):
2495 color(self._inhandle)
2496 context.set_line_width(self.h/8)
2497 context.move_to(0, self.h/2)
2498 context.line_to(self.w, self.h/2)
2499 context.stroke()
2500 context.set_line_width(4*(self.h/8))
2501 context.move_to(self.x2p(self.left), self.h/2)
2502 context.line_to(self.x2p(self.right), self.h/2)
2503 context.stroke()
2504 fascent, fdescent, fheight, fxadvance, fyadvance = context.font_extents()
2505 xbearing, ybearing, char_w, height, xadvance, yadvance = context.text_extents('[')
2506 baseline = (self.h - fascent - fdescent) / 2 + fascent
2507 color(False, self._inleft)
2508 context.move_to(self.x2p(self._left) - char_w/2, baseline)
2509 context.show_text('[')
2510 color(False, self._inright)
2511 context.move_to(self.x2p(self._right) - char_w/2, baseline)
2512 context.show_text(']')
2513
2515 """Draws a magnifying glass under the label."""
2517 kw['justify'] = 0
2518 kw['vcenter'] = 1
2519 if not ('appearance' in kw):
2520 em = Appearance.emsize
2521 else:
2522 em = kw['appearance'].emsize
2523 if not ('w' in kw):
2524 kw['w'] = 2.5
2525 if not ('h' in kw):
2526 kw['h'] = 3
2527 SubLayer_Label.__init__(self, *args, **kw)
2528 self.color_rim = color_rim
2529 self.color_lens = color_lens
2530 - def draw(self, context, w, h, appearance):
2531 SubLayer.draw(self, context, w, h, appearance)
2532 r = .75 * self.w/2
2533 cx, cy = self.w/2, r+1
2534 context.save()
2535 context.set_line_width(.5)
2536 context.set_source_rgba(* appearance.color(self.color_rim))
2537 context.move_to(cx+r, cy)
2538 context.arc(cx, cy, r, 0, 2*pi)
2539 context.stroke_preserve()
2540 context.set_source_rgba(* appearance.color(self.color_lens))
2541 context.fill()
2542
2543 cy += r/2
2544 theta = atan2(self.h - r/2 - 1, self.w/2)
2545 dx = r*sin(theta)
2546 dy = r*cos(theta)
2547 context.set_source_rgba(* appearance.color(self.color_rim))
2548 context.set_line_width(1.5)
2549 context.move_to(cx+dx, cy+dy)
2550 context.line_to(self.w, self.w)
2551 context.stroke()
2552 context.restore()
2553
2554 em = appearance.emsize
2555 height, self.rq_h = self.rq_h, int(round(self.h*0.68/em))
2556 SubLayer_Label.draw(self, context, w, h, appearance)
2557 self.rq_h = height
2558 SubLayer.draw(self, context, w, h, appearance)
2559
2561 - def __init__(self, path, tooltip=None, **kw):
2566 - def draw(self, context, w, h, appearance):
2567 SubLayer.draw(self, context, w, h, appearance)
2568 if not self.image_surface:
2569 self.image_surface = cairo.ImageSurface.create_from_png(self.path)
2570 img_height = self.image_surface.get_height()
2571 img_width = self.image_surface.get_width()
2572 width_ratio = float(self.w) / float(img_width)
2573 height_ratio = float(self.h) / float(img_height)
2574 scale_xy = min(height_ratio, width_ratio)
2575 context.save()
2576 context.translate((self.w - scale_xy*img_width)/2, (self.h - scale_xy*img_height)/2)
2577 context.scale(scale_xy, scale_xy)
2578 context.set_source_surface(self.image_surface)
2579 context.rectangle(0, 0, self.w/scale_xy, self.h/scale_xy)
2580 context.fill()
2581 context.restore()
2582
2596 on_popup = property(lambda self: self.__on_popup, lambda self, x: self.__on_popup.assign(x))
2610 self.entered = False
2611 self.OnInvalidate(self)
2612
2613
2615 """A little icon suitable for L{Layer_Toolbar}."""
2617 if not ('appearance' in kw):
2618 em = Appearance.emsize
2619 else:
2620 em = kw['appearance'].emsize
2621 if not ('w' in kw):
2622 kw['w'] = 2.5
2623 if not ('h' in kw):
2624 kw['h'] = 2.5
2625 SubLayer.__init__(self, *args, **kw)
2626
2627
2629 """A little yellow ruler icon."""
2630 - def draw(self, context, w, h, appearance):
2631 hh = self.h
2632 ww = min(self.w, int(round(.4*hh)))
2633 x0 = (self.w - ww) / 2.0
2634 SubLayer.draw(self, context, w, h, appearance)
2635 context.set_line_width(.5)
2636 context.rectangle(x0+1, 1, ww-2, hh-2)
2637 context.set_source_rgb(1,1,0)
2638 context.fill_preserve()
2639 context.set_source_rgb(0,0,0)
2640 context.stroke()
2641 context.set_line_width(.25)
2642 y = 3.5
2643 for i in count(1):
2644 if y >= hh: break
2645 x = (i%3) and (ww/3) or (2*ww/3)
2646 context.move_to(x0+1, y)
2647 context.line_to(x0+x, y)
2648 context.stroke()
2649 y += 2
2650
2651
2653 """An eraser icon."""
2654 - def draw(self, context, w, h, appearance):
2655 SubLayer.draw(self, context, w, h, appearance)
2656 ww = self.w - 4
2657 hw = ww/2
2658 face_h = hw/1.816
2659 dx = hw - hw/1.234
2660 a = 2
2661 b = a + dx
2662 c = a + hw
2663 d = c + dx
2664 f = self.w - 2
2665 e = f - dx
2666 A = self.h - 3
2667 B = A - face_h
2668 D = 3
2669 C = D + face_h
2670
2671 context.set_source_rgb(.5,.3,.8)
2672 context.move_to(b,B)
2673 context.line_to(d,B)
2674 context.line_to(f,D)
2675 context.line_to(c,D)
2676 context.line_to(b,B)
2677 context.fill()
2678 context.move_to(d,B)
2679 context.line_to(f,D)
2680 context.line_to(e,C)
2681 context.line_to(c,A)
2682 context.line_to(d,B)
2683 context.fill()
2684
2685 context.move_to(a,A)
2686 context.line_to(c,A)
2687 context.line_to(d,B)
2688 context.line_to(b,B)
2689 context.line_to(a,A)
2690
2691 context.set_line_width(.5)
2692 context.set_source_rgb(.9,.4,.6)
2693 context.stroke()
2694 context.move_to(c,A)
2695 context.line_to(e,C)
2696 context.line_to(f,D)
2697 context.line_to(c,D)
2698 context.line_to(b,B)
2699 context.stroke()
2700 context.move_to(d,B)
2701 context.line_to(f,D)
2702 context.stroke()
2703
2704
2706 """A rubber stamp icon."""
2707 - def draw(self, context, w, h, appearance):
2710
2712 m = w * .18
2713 ww = w - 2*m
2714 hw = ww/2
2715 context.set_source_rgb(.6, .5, .5)
2716 context.set_line_width(appearance.emsize/4)
2717 context.rectangle(m, m+.6*ww, ww, .4*ww)
2718 context.stroke()
2719 context.move_to(m+.33*ww, m+.7*ww)
2720 context.line_to(m+.67*ww, m+.7*ww)
2721 context.line_to(m+.5*ww, m+.95*ww)
2722 context.fill()
2723 context.rectangle(m+.45*ww, m+.2*ww, .1*ww, .45*ww)
2724 context.fill()
2725 context.arc(m+.5*ww, m+.2*ww, .15*ww, 0, 2*pi)
2726 context.stroke()
2727
2728
2730 """An arrow."""
2738 angle = property(lambda self: self.__angle, lambda self, x: self.set_angle(x))
2742 color = property(lambda self: self.__color, lambda self, x: self.set_color(x))
2743 - def draw(self, context, w, h, appearance):
2747
2748
2750 """Progress bar, from 0 to 100.
2751
2752 @ivar progress: float from 0 to 100
2753 """
2761 progress = property(lambda self: self.__progress, lambda self, x: self.set_progress(x))
2762 - def draw(self, context, w, h, appearance):
2763 SubLayer.draw(self, context, w, h, appearance)
2764 context.set_source_rgba( *appearance.color(self.__color) )
2765 context.rectangle(1, 1, int(round((self.w-2)*self.__progress/100.0)), self.h-2)
2766 context.fill()
2767 context.set_line_width(.5)
2768 context.rectangle(1, 1, self.w-2, self.h-2)
2769 context.set_source_rgba(.5, .5, .5, .5)
2770 context.stroke()
2771
2772
2774 """Label with radio button behavior. All events via fellow.on_radio.
2775
2776 @ivar active: bool
2777 """
2779 self.__ref = Reffer()
2780 kw['action'] = self.__ref(self.__action)
2781 if not ('justify' in kw):
2782 kw['justify'] = -1
2783 if not ('vcenter' in kw):
2784 kw['vcenter'] = 1
2785 SubLayer_Label.__init__(self, *args, **kw)
2786 self.fellow = fellow
2787 self.depends = []
2788 if self.fellow:
2789 self.fellow.depends.append(self)
2790 self.on_radio = on_radio
2791 self.__color = self.color
2792 self.fill_color = fill_color
2793 self.__active = (not self.fellow) if (active is None) else active
2794 self.color = self.__color
2796 self.__active = x
2797 if self.fellow:
2798 if x:
2799 self.fellow.set_active(False, self, user)
2800 else:
2801 for d in self.depends:
2802 if d != sub:
2803 d.active = False
2804 if user:
2805 self.on_radio(sub or self)
2806 self.invalidate()
2807 active = property(lambda self: self.__active, lambda self, x: self.set_active(x))
2810 - def draw(self, context, w, h, appearance):
2811 SubLayer.draw(self, context, w, h, appearance)
2812 side = min(self.w, self.h)
2813 context.save()
2814 context.translate(1.5*side, 0)
2815 SubLayer_Label.draw(self, context, w, h, appearance)
2816 context.restore()
2817 r, g, b, a = appearance.color(self.fill_color)
2818 if self.entered:
2819 h, s, v = RGBtoHSV(r, g, b)
2820 r, g, b = HSVtoRGB(h, .99, .99)
2821 context.set_source_rgba(r, g, b, a)
2822 context.set_line_width(appearance.emsize/5.0)
2823 rad = side * 0.35
2824 context.move_to(side/2+rad, side/2)
2825 context.arc(side/2, side/2, rad, 0, 2*pi)
2826 if self.__active:
2827 context.stroke_preserve()
2828 context.fill()
2829 else:
2830 context.stroke()
2831
2832
2833
2834
2835 COLOR = collections.defaultdict(lambda:(.5, .5, .5))
2836 for i,c in enumerate([(0,0,0), (1,0,0), (0,0,1), (0,1,0), (0,1,1), (1,1,0), (1,0,1)]):
2837 COLOR[i] = c
2838 offset = len(COLOR)
2839 for i in xrange(20):
2840 COLOR[offset+i] = HSVtoRGB((i*2.0/20)%1.0, (40.0-i)/40, (20.0+i)/40)
2841 COLOR_CLASS = lambda c: ('modelGTK.class[%i]' % c, COLOR[c])
2842
2844 __explore_featured = ['OnClickColor', 'color', 'do_click']
2845 - def __init__(self, vertical=True, columns=1):
2846 ToolSpace.__init__(self)
2847 self.__ref = Reffer()
2848 self.OnClickColor = WeakEvent()
2849 self.__ref = Reffer()
2850 self.OnDraw += self.__ref(self.__onDraw)
2851 self.__labels = []
2852 x = y = 0
2853 w = h = PALETTE_H_EMS
2854 dx = dy = 0
2855 if vertical:
2856 self.set_size_request(15*columns,220)
2857 dy = h
2858 else:
2859 self.set_size_request(220, 15*columns)
2860 dx = w
2861 for c in xrange(columns):
2862 if vertical: y = 0
2863 else: x = 0
2864 for i in xrange(10):
2865 layer = Layer(x=x, y=y, w=w, h=h, cBG=COLOR_CLASS(c*10+i))
2866 self.add_layer(layer)
2867 sub = SubLayer_Label(((c*10+i) == 1) and 'O' or '', 0, 1, w=w, h=h, border=3,
2868 color=COLOR_PALETTE_TEXT, hover_color=COLOR_PALETTE_TEXT, action=self.__ref(bind(self.do_click, c*10+i)))
2869 layer.add_sublayer(sub)
2870 self.__labels.append(sub)
2871 x += dx
2872 y += dy
2873 if vertical: x += dy
2874 else: y += dx
2875 if vertical:
2876 x = 0
2877 w = -.01
2878 else:
2879 y = 0
2880 h = -.01
2881 layer = Layer(x=x, y=y, w=w, h=h, cBG=COLOR_PALETTE_MORE)
2882 self.add_layer(layer)
2883 self.subMore = SubLayer_Label('...', 0, 1, w=w, h=h, border=3, color=COLOR_PALETTE_TEXT,
2884 hover_color=COLOR_PALETTE_TEXT, action=self.__ref(self.__onClickMenu))
2885 layer.add_sublayer(self.subMore)
2886 self.__color = 1
2891 menu = gtk.Menu()
2892 for i in xrange(16):
2893 build_menuitem(str(i), self.__ref(bind(self.do_click, i)), menu=menu, item_class=gtk.CheckMenuItem, active=(i == self.__color))
2894 build_menuitem('Other...', self.__ref(self.__onClickOther), menu=menu, item_class=gtk.CheckMenuItem, active=(self.__color >= 16))
2895 menu.popup(None, None, None, 0, e.time)
2897 grp = qubx.pyenvGTK.prompt_entry('Pick color/group:', self.__color, acceptIntGreaterThanOrEqualTo(0))
2898 if grp is None:
2899 return
2900 self.set_color(grp)
2901 self.OnClickColor(self, grp)
2903 if self.__color == x:
2904 return
2905 if 0 <= self.__color < len(self.__labels):
2906 self.__labels[self.__color].label = ''
2907 self.__color = x
2908 if 0 <= x < len(self.__labels):
2909 self.__labels[self.__color].label = 'O'
2910 color = property(lambda self: self.__color, lambda self, x: self.set_color(x))
2912 context.set_source_rgb(0,0,0)
2913 context.paint()
2914
2915
2916
2917 ZOOM_FACTOR = 1.02
2918 ZOOM_FACTOR_PER_PIXEL = .001
2919 ZOOM_DELAY_MS = 20
2920 COLOR_ZOOM = ('qubx.toolspace.zoom', (0, 0, 0, 1))
2921 ColorInfo[COLOR_ZOOM[0]].label = 'Chart zoom button'
2922 COLOR_ZOOM_HOVER = ('qubx.toolspace.zoom.hover', (.5, 0, 0, 1))
2923 ColorInfo[COLOR_ZOOM_HOVER[0]].label = 'Chart zoom button mouseover'
2924
2926 - def __init__(self, symbol='+', zoom_out=False, **kw):
2950 if self.__timer:
2951 gobject.source_remove(self.__timer)
2952 self.__timer = None
2954 self.__x, self.__y, self.__e_state = x, y, e.state
2955 dx = x - self.__x0
2956 dy = y - self.__y0
2957 if (not self.__dx) and (not self.__dy):
2958 self.__dx, self.__dy = dx, dy
2959 if dx or dy:
2960 self.__dm = sqrt(dx*dx + dy*dy)
2961 else:
2962
2963 self.__accel_pix = (dx*self.__dx + dy*self.__dy) / self.__dm
2965 factor = ZOOM_FACTOR + self.__accel_pix * ZOOM_FACTOR_PER_PIXEL
2966 if self.__zoom_out:
2967 factor = 1.0 / factor
2968 self.OnZoom(self, factor)
2969 return True
2970
2971
2973 w = int(w)
2974 h = int(h)
2975 margin = min(w, h)/6
2976 cr.save()
2977 cr.translate(margin, margin)
2978 w -= 2*margin
2979 h -= 2*margin
2980 cr.set_line_width(1.0)
2981 cr.set_source_rgba(1, 1, 1, 1)
2982 cr.rectangle(0, 0, w, h)
2983 cr.fill_preserve()
2984 cr.set_source_rgba(0,0,0,1)
2985 cr.stroke()
2986
2987 margin = min(w, h)/10.0
2988 cr.set_line_width(0.5)
2989 cr.move_to(w/2, h/2)
2990 cr.line_to(w/2, margin)
2991 cr.line_to(w/2+margin, 2*margin)
2992 cr.stroke()
2993 cr.move_to(w/2, margin)
2994 cr.line_to(w/2-margin, 2*margin)
2995 cr.stroke()
2996 cr.move_to(w/2, h/2)
2997 cr.line_to(w/2, h-margin)
2998 cr.line_to(w/2+margin, h-2*margin)
2999 cr.stroke()
3000 cr.move_to(w/2, h-margin)
3001 cr.line_to(w/2-margin, h-2*margin)
3002 cr.stroke()
3003 cr.move_to(w/2, h/2)
3004 cr.line_to(margin, h/2)
3005 cr.line_to(2*margin, h/2+margin)
3006 cr.stroke()
3007 cr.move_to(margin, h/2)
3008 cr.line_to(2*margin, h/2-margin)
3009 cr.stroke()
3010 cr.move_to(w/2, h/2)
3011 cr.line_to(w-margin, h/2)
3012 cr.line_to(w-2*margin, h/2+margin)
3013 cr.stroke()
3014 cr.move_to(w-margin, h/2)
3015 cr.line_to(w-2*margin, h/2-margin)
3016 cr.stroke()
3017 cr.restore()
3018
3020 - def draw(self, context, w, h, appearance):
3023
3024
3026 cr.set_font_size(points)
3027 xbearing, ybearing, width, height, xadvance, yadvance = cr.text_extents(s)
3028 fascent, fdescent, fheight, fxadvance, fyadvance = cr.font_extents()
3029 return Anon(xbearing=xbearing, ybearing=ybearing, width=width, height=height, xadvance=xadvance, yadvance=yadvance,
3030 font=Anon(ascent=fascent, descent=fdescent, height=fheight, xadvance=fxadvance, yadvance=fyadvance))
3031