# # Copyright (c) 2003, 2004, 2006, 2007 Art Haas # # This file is part of PythonCAD. # # PythonCAD is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # PythonCAD is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with PythonCAD; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # # GTK interface code for dealing with text entities # import pygtk pygtk.require('2.0') import gtk import pango from PythonCAD.Generic.text import TextStyle, TextBlock def set_textblock_bounds(gtkimage, tblock): # print "set_textblock_bounds() ..." _text = tblock.getText() if len(_text) == 0: tblock.setBounds(0, 0) tblock.setFontScale(1/float(pango.SCALE)) return _family = tblock.getFamily() _style = tblock.getStyle() _weight = tblock.getWeight() _size = tblock.getSize() _da = gtkimage.getDA() _upp = gtkimage.getUnitsPerPixel() # # initial test layout ... # _layout = _da.create_pango_layout(_text) _fd = pango.FontDescription() _fd.set_family(_family) _fd.set_style(_style) _fd.set_weight(_weight) # # use an 18-point font as first size guess # _fs = 18 _fd.set_size(pango.SCALE * _fs) _layout.set_font_description(_fd) _nlines = _layout.get_line_count() # # the pixel height of the TextBlock # _th = _nlines * _size # print "TextBlock height: %g" % _th _ph = _th/_upp _iph = int(_ph) if _iph/_nlines < 3: # tiny text - use the 18-point values for boundary _w, _h = _layout.get_size() _tw = (_w * _th)/float(_h) tblock.setBounds(_tw, _th) tblock.setFontScale(1/float(pango.SCALE)) _layout = None return # print "TextBlock pixel height: %g [%d]" % (_ph, _iph) _w, _h = _layout.get_pixel_size() # print "first layout: w: %d; h: %d" % (_w, _h) _i = 0 if _h != _iph: # # loop until the layout pixel height equals the "correct" pixel height # _diff = abs(_h - _iph) _ofs = _fs _fs = (_fs * _ph)/float(_h) # print "adjusted font size: %g" % (_fs) while True: _layout = _da.create_pango_layout(_text) _fd = pango.FontDescription() _fd.set_family(_family) _fd.set_style(_style) _fd.set_weight(_weight) _fd.set_size(int(pango.SCALE * _fs)) _layout.set_font_description(_fd) _w, _h = _layout.get_pixel_size() # print "adjusted layout: w: %d; h: %d" % (_w, _h) # # tests to bail out # # all the inexact comparisons and iteration max # count text are arbitrary ... # if _h == _iph: # print "exact match" break if ((_iph > 10) and (abs(_iph - _h) < 2)): # print "within 2" break if ((_iph > 100) and (abs(_iph - _h) < 10)): # print "within 10" break if ((_iph > 1000) and (abs(_iph - _h) < 40)): # print "within 40" break if _i > 25: # print "25 iterations" break # # bah, another iteration # _d = abs(_h - _iph) if _d < _diff: _diff = _d _ofs = _fs _fs = (_fs * _ph)/float(_h) else: # split the difference in the changed font size # and try again, but do not reset the old font # size _fs = _ofs + ((_fs - _ofs)/2.0) # print "adjusted font size: %g" % (_fs) _i = _i + 1 # # with the layout sized "correctly", calculate the TextBlock width # _w, _h = _layout.get_size() _tw = (_w * _th)/float(_h) # print "TextBlock width: %g" % _tw tblock.setBounds(_tw, _th) tblock.setFontScale(_fs) _layout = None def _make_pango_layout(gtkimage, text, family, style, weight, size): _layout = gtkimage.getDA().create_pango_layout(text) _fd = pango.FontDescription() _fd.set_family(family) if style == TextStyle.FONT_NORMAL: _style = pango.STYLE_NORMAL elif style == TextStyle.FONT_OBLIQUE: _style = pango.STYLE_OBLIQUE elif style == TextStyle.FONT_ITALIC: _style = pango.STYLE_ITALIC else: raise ValueError, "Unexpected font style: %d" % style _fd.set_style(_style) if weight == TextStyle.WEIGHT_NORMAL: _weight = pango.WEIGHT_NORMAL elif weight == TextStyle.WEIGHT_LIGHT: _weight = pango.WEIGHT_LIGHT elif weight == TextStyle.WEIGHT_BOLD: _weight = pango.WEIGHT_BOLD elif weight == TextStyle.WEIGHT_HEAVY: _weight = pango.WEIGHT_HEAVY else: raise ValueError, "Unexpected font weight: %d" % weight _fd.set_weight(_weight) _sz = int(pango.SCALE * (size/gtkimage.getUnitsPerPixel())) if _sz < pango.SCALE: _sz = pango.SCALE _fd.set_size(_sz) _layout.set_font_description(_fd) return _layout def text_button_press(gtkimage, widget, event, tool): _tol = gtkimage.getTolerance() _image = gtkimage.getImage() _x, _y = _image.getCurrentPoint() _pt, _flag = _image.findPoint(_x, _y, _tol) if _pt is not None: _x, _y = _pt.getCoords() tool.getTextBlock().setLocation(_x, _y) _image.startAction() try: tool.create(_image) finally: _image.endAction() # tool.clearCurrentPoint() return True def text_motion_notify(gtkimage, widget, event, tool): _tblock = tool.getTextBlock() _tw, _th = tool.getPixelSize() _gc = gtkimage.getGC() _x = int(event.x) _y = int(event.y) _align = _tblock.getAlignment() if _align == TextStyle.ALIGN_LEFT: _xoff = 0 elif _align == TextStyle.ALIGN_CENTER: _xoff = _tw//2 elif _align == TextStyle.ALIGN_RIGHT: _xoff = _tw else: raise ValueError, "Unexpected alignment value: %d" % _align _cp = tool.getCurrentPoint() if _cp is not None: _xc, _yc = _cp _xc = _xc - _xoff widget.window.draw_rectangle(_gc, False, _xc, _yc, _tw, _th) tool.setCurrentPoint(_x, _y) _x = _x - _xoff widget.window.draw_rectangle(_gc, False, _x, _y, _tw, _th) return True def text_add_init(gtkimage, tool=None): _image = gtkimage.getImage() _x, _y = _image.getCurrentPoint() _tool = _image.getTool() _text = _tool.getText() _ts = _image.getOption('TEXT_STYLE') _tb = TextBlock(_x, _y, _text, _ts) _f = _image.getOption('FONT_FAMILY') if _f != _ts.getFamily(): _tb.setFamily(_f) _s = _image.getOption('FONT_STYLE') if _s != _ts.getStyle(): _tb.setStyle(_s) _w = _image.getOption('FONT_WEIGHT') if _w != _ts.getWeight(): _tb.setWeight(_w) _c = _image.getOption('FONT_COLOR') if _c != _ts.getColor(): _tb.setColor(_c) _sz = _image.getOption('TEXT_SIZE') if abs(_sz - _ts.getSize()) > 1e-10: _tb.setSize(_sz) _a = _image.getOption('TEXT_ANGLE') if abs(_a - _ts.getAngle()) > 1e-10: _tb.setAngle(_a) _al = _image.getOption('TEXT_ALIGNMENT') if _al != _ts.getAlignment(): _tb.setAlignment(_al) _tool.setTextBlock(_tb) _layout = _make_pango_layout(gtkimage, _text, _f, _s, _w, _sz) _tool.setLayout(_layout) _lw, _lh = _layout.get_pixel_size() _tool.setPixelSize(_lw, _lh) _upp = gtkimage.getUnitsPerPixel() # # the width and height calculations can be somewhat inaccurate # as the unitsPerPixel value gets large # _w = _lw * _upp _h = _lh * _upp _tool.setBounds(_w, _h) _tool.setHandler("motion_notify", text_motion_notify) _tool.setHandler("button_press", text_button_press) gtkimage.setPrompt(_('Click where to place the text')) _gc = gtkimage.getGC() _gc.set_line_attributes(1, gtk.gdk.LINE_SOLID, gtk.gdk.CAP_BUTT, gtk.gdk.JOIN_MITER) _gc.set_function(gtk.gdk.INVERT) def text_add_dialog(gtkimage): _window = gtkimage.getWindow() _dialog = gtk.Dialog(_('Enter text'), _window, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, (gtk.STOCK_OK, gtk.RESPONSE_OK, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)) _tb = gtk.TextBuffer() _tv = gtk.TextView(_tb) _sw = gtk.ScrolledWindow() _sw.set_size_request(400, 300) _sw.add_with_viewport(_tv) _dialog.vbox.pack_start(_sw, False, False, 0) _dialog.show_all() _text = None _response = _dialog.run() if _response == gtk.RESPONSE_OK: _start_iter, _end_iter = _tb.get_bounds() _text = _tb.get_text(_start_iter, _end_iter) _dialog.destroy() return _text