Updating download.html page with Git information
[pythoncad.git] / PythonCAD / Interface / Gtk / gtktext.py
blobb346daa6044e382a07628e02c2c3177a7cfdcd1f
2 # Copyright (c) 2003, 2004, 2006, 2007 Art Haas
4 # This file is part of PythonCAD.
5 #
6 # PythonCAD is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # PythonCAD is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with PythonCAD; if not, write to the Free Software
18 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 # GTK interface code for dealing with text entities
24 import pygtk
25 pygtk.require('2.0')
26 import gtk
27 import pango
29 from PythonCAD.Generic.text import TextStyle, TextBlock
31 def set_textblock_bounds(gtkimage, tblock):
32 # print "set_textblock_bounds() ..."
33 _text = tblock.getText()
34 if len(_text) == 0:
35 tblock.setBounds(0, 0)
36 tblock.setFontScale(1/float(pango.SCALE))
37 return
38 _family = tblock.getFamily()
39 _style = tblock.getStyle()
40 _weight = tblock.getWeight()
41 _size = tblock.getSize()
42 _da = gtkimage.getDA()
43 _upp = gtkimage.getUnitsPerPixel()
45 # initial test layout ...
47 _layout = _da.create_pango_layout(_text)
48 _fd = pango.FontDescription()
49 _fd.set_family(_family)
50 _fd.set_style(_style)
51 _fd.set_weight(_weight)
53 # use an 18-point font as first size guess
55 _fs = 18
56 _fd.set_size(pango.SCALE * _fs)
57 _layout.set_font_description(_fd)
58 _nlines = _layout.get_line_count()
60 # the pixel height of the TextBlock
62 _th = _nlines * _size
63 # print "TextBlock height: %g" % _th
64 _ph = _th/_upp
65 _iph = int(_ph)
66 if _iph/_nlines < 3: # tiny text - use the 18-point values for boundary
67 _w, _h = _layout.get_size()
68 _tw = (_w * _th)/float(_h)
69 tblock.setBounds(_tw, _th)
70 tblock.setFontScale(1/float(pango.SCALE))
71 _layout = None
72 return
73 # print "TextBlock pixel height: %g [%d]" % (_ph, _iph)
74 _w, _h = _layout.get_pixel_size()
75 # print "first layout: w: %d; h: %d" % (_w, _h)
76 _i = 0
77 if _h != _iph:
79 # loop until the layout pixel height equals the "correct" pixel height
81 _diff = abs(_h - _iph)
82 _ofs = _fs
83 _fs = (_fs * _ph)/float(_h)
84 # print "adjusted font size: %g" % (_fs)
85 while True:
86 _layout = _da.create_pango_layout(_text)
87 _fd = pango.FontDescription()
88 _fd.set_family(_family)
89 _fd.set_style(_style)
90 _fd.set_weight(_weight)
91 _fd.set_size(int(pango.SCALE * _fs))
92 _layout.set_font_description(_fd)
93 _w, _h = _layout.get_pixel_size()
94 # print "adjusted layout: w: %d; h: %d" % (_w, _h)
96 # tests to bail out
98 # all the inexact comparisons and iteration max
99 # count text are arbitrary ...
101 if _h == _iph:
102 # print "exact match"
103 break
104 if ((_iph > 10) and (abs(_iph - _h) < 2)):
105 # print "within 2"
106 break
107 if ((_iph > 100) and (abs(_iph - _h) < 10)):
108 # print "within 10"
109 break
110 if ((_iph > 1000) and (abs(_iph - _h) < 40)):
111 # print "within 40"
112 break
113 if _i > 25:
114 # print "25 iterations"
115 break
117 # bah, another iteration
119 _d = abs(_h - _iph)
120 if _d < _diff:
121 _diff = _d
122 _ofs = _fs
123 _fs = (_fs * _ph)/float(_h)
124 else:
125 # split the difference in the changed font size
126 # and try again, but do not reset the old font
127 # size
128 _fs = _ofs + ((_fs - _ofs)/2.0)
129 # print "adjusted font size: %g" % (_fs)
130 _i = _i + 1
132 # with the layout sized "correctly", calculate the TextBlock width
134 _w, _h = _layout.get_size()
135 _tw = (_w * _th)/float(_h)
136 # print "TextBlock width: %g" % _tw
137 tblock.setBounds(_tw, _th)
138 tblock.setFontScale(_fs)
139 _layout = None
141 def _make_pango_layout(gtkimage, text, family, style, weight, size):
142 _layout = gtkimage.getDA().create_pango_layout(text)
143 _fd = pango.FontDescription()
144 _fd.set_family(family)
145 if style == TextStyle.FONT_NORMAL:
146 _style = pango.STYLE_NORMAL
147 elif style == TextStyle.FONT_OBLIQUE:
148 _style = pango.STYLE_OBLIQUE
149 elif style == TextStyle.FONT_ITALIC:
150 _style = pango.STYLE_ITALIC
151 else:
152 raise ValueError, "Unexpected font style: %d" % style
153 _fd.set_style(_style)
154 if weight == TextStyle.WEIGHT_NORMAL:
155 _weight = pango.WEIGHT_NORMAL
156 elif weight == TextStyle.WEIGHT_LIGHT:
157 _weight = pango.WEIGHT_LIGHT
158 elif weight == TextStyle.WEIGHT_BOLD:
159 _weight = pango.WEIGHT_BOLD
160 elif weight == TextStyle.WEIGHT_HEAVY:
161 _weight = pango.WEIGHT_HEAVY
162 else:
163 raise ValueError, "Unexpected font weight: %d" % weight
164 _fd.set_weight(_weight)
165 _sz = int(pango.SCALE * (size/gtkimage.getUnitsPerPixel()))
166 if _sz < pango.SCALE:
167 _sz = pango.SCALE
168 _fd.set_size(_sz)
169 _layout.set_font_description(_fd)
170 return _layout
172 def text_button_press(gtkimage, widget, event, tool):
173 _tol = gtkimage.getTolerance()
174 _image = gtkimage.getImage()
175 _x, _y = _image.getCurrentPoint()
176 _pt, _flag = _image.findPoint(_x, _y, _tol)
177 if _pt is not None:
178 _x, _y = _pt.getCoords()
179 tool.getTextBlock().setLocation(_x, _y)
180 _image.startAction()
181 try:
182 tool.create(_image)
183 finally:
184 _image.endAction()
185 # tool.clearCurrentPoint()
186 return True
188 def text_motion_notify(gtkimage, widget, event, tool):
189 _tblock = tool.getTextBlock()
190 _tw, _th = tool.getPixelSize()
191 _gc = gtkimage.getGC()
192 _x = int(event.x)
193 _y = int(event.y)
194 _align = _tblock.getAlignment()
195 if _align == TextStyle.ALIGN_LEFT:
196 _xoff = 0
197 elif _align == TextStyle.ALIGN_CENTER:
198 _xoff = _tw//2
199 elif _align == TextStyle.ALIGN_RIGHT:
200 _xoff = _tw
201 else:
202 raise ValueError, "Unexpected alignment value: %d" % _align
203 _cp = tool.getCurrentPoint()
204 if _cp is not None:
205 _xc, _yc = _cp
206 _xc = _xc - _xoff
207 widget.window.draw_rectangle(_gc, False, _xc, _yc, _tw, _th)
208 tool.setCurrentPoint(_x, _y)
209 _x = _x - _xoff
210 widget.window.draw_rectangle(_gc, False, _x, _y, _tw, _th)
211 return True
213 def text_add_init(gtkimage, tool=None):
214 _image = gtkimage.getImage()
215 _x, _y = _image.getCurrentPoint()
216 _tool = _image.getTool()
217 _text = _tool.getText()
218 _ts = _image.getOption('TEXT_STYLE')
219 _tb = TextBlock(_x, _y, _text, _ts)
220 _f = _image.getOption('FONT_FAMILY')
221 if _f != _ts.getFamily():
222 _tb.setFamily(_f)
223 _s = _image.getOption('FONT_STYLE')
224 if _s != _ts.getStyle():
225 _tb.setStyle(_s)
226 _w = _image.getOption('FONT_WEIGHT')
227 if _w != _ts.getWeight():
228 _tb.setWeight(_w)
229 _c = _image.getOption('FONT_COLOR')
230 if _c != _ts.getColor():
231 _tb.setColor(_c)
232 _sz = _image.getOption('TEXT_SIZE')
233 if abs(_sz - _ts.getSize()) > 1e-10:
234 _tb.setSize(_sz)
235 _a = _image.getOption('TEXT_ANGLE')
236 if abs(_a - _ts.getAngle()) > 1e-10:
237 _tb.setAngle(_a)
238 _al = _image.getOption('TEXT_ALIGNMENT')
239 if _al != _ts.getAlignment():
240 _tb.setAlignment(_al)
241 _tool.setTextBlock(_tb)
242 _layout = _make_pango_layout(gtkimage, _text, _f, _s, _w, _sz)
243 _tool.setLayout(_layout)
244 _lw, _lh = _layout.get_pixel_size()
245 _tool.setPixelSize(_lw, _lh)
246 _upp = gtkimage.getUnitsPerPixel()
248 # the width and height calculations can be somewhat inaccurate
249 # as the unitsPerPixel value gets large
251 _w = _lw * _upp
252 _h = _lh * _upp
253 _tool.setBounds(_w, _h)
254 _tool.setHandler("motion_notify", text_motion_notify)
255 _tool.setHandler("button_press", text_button_press)
256 gtkimage.setPrompt(_('Click where to place the text'))
257 _gc = gtkimage.getGC()
258 _gc.set_line_attributes(1, gtk.gdk.LINE_SOLID,
259 gtk.gdk.CAP_BUTT, gtk.gdk.JOIN_MITER)
260 _gc.set_function(gtk.gdk.INVERT)
262 def text_add_dialog(gtkimage):
263 _window = gtkimage.getWindow()
264 _dialog = gtk.Dialog(_('Enter text'), _window,
265 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
266 (gtk.STOCK_OK, gtk.RESPONSE_OK,
267 gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
268 _tb = gtk.TextBuffer()
269 _tv = gtk.TextView(_tb)
270 _sw = gtk.ScrolledWindow()
271 _sw.set_size_request(400, 300)
272 _sw.add_with_viewport(_tv)
273 _dialog.vbox.pack_start(_sw, False, False, 0)
274 _dialog.show_all()
275 _text = None
276 _response = _dialog.run()
277 if _response == gtk.RESPONSE_OK:
278 _start_iter, _end_iter = _tb.get_bounds()
279 _text = _tb.get_text(_start_iter, _end_iter)
280 _dialog.destroy()
281 return _text