I'm in the process of porting an application from PyGTK to PyGObject. Mostly it's going well because mostly I did conventional things with PyGTK. But there's one somewhat ugly hack I was using to display the value of a SpinButton as currency (with a $ in front of it).
I originally got this solution from the PyGTK mailing list back in the days before Stack Overflow. As you can see, the magic happens on the input and output signals:
import gtk, ctypes
def _currency_input(spinbutton, gpointer):
text = spinbutton.get_text()
if text.startswith('$'):
text = text[1:]
double = ctypes.c_double.from_address(hash(gpointer))
double.value = float(text)
return True
def _currency_output(spinbutton):
text = '$%.*f' % (int(spinbutton.props.digits),
spinbutton.props.adjustment.value)
spinbutton.set_text(text)
return True
def format_spinbutton_currency(spinbutton):
spinbutton.connect('input', _currency_input)
spinbutton.connect('output', _currency_output)
def _test():
s = gtk.SpinButton(gtk.Adjustment(value=1, lower=0, upper=1000,
step_incr=1))
s.props.digits = 2
format_spinbutton_currency(s)
w = gtk.Window()
w.props.border_width = 12
w.add(s)
w.show_all()
w.connect('destroy', gtk.main_quit)
gtk.main()
if __name__ == '__main__':
_test()
Doing my best to translate that into PyGObject, I came up with:
from gi.repository import Gtk
import ctypes
def _currency_input(spinbutton, gpointer):
text = spinbutton.get_text()
if text.startswith('$'):
text = text[1:]
double = ctypes.c_double.from_address(hash(gpointer))
double.value = float(text)
return True
def _currency_output(spinbutton):
text = '$%.*f' % (int(spinbutton.props.digits),
spinbutton.get_value())
spinbutton.set_text(text)
return True
def format_spinbutton_currency(spinbutton):
spinbutton.connect('input', _currency_input)
spinbutton.connect('output', _currency_output)
def _test():
s = Gtk.SpinButton()
s.set_adjustment(Gtk.Adjustment(value=1, lower=0, upper=1000,
step_increment=1))
s.props.digits = 2
format_spinbutton_currency(s)
w = Gtk.Window()
w.props.border_width = 12
w.add(s)
w.show_all()
w.connect('destroy', Gtk.main_quit)
Gtk.main()
if __name__ == '__main__':
_test()
Unfortunately, this doesn't work. It shows up fine initially, but when I click the up or down error, it crashes and I see:
/usr/lib/python2.7/dist-packages/gi/types.py:43: Warning: g_value_get_double: assertion `G_VALUE_HOLDS_DOUBLE (value)' failed
return info.invoke(*args, **kwargs)
Segmentation fault
Any idea what this error message means?
Or what part of my code might not work under PyGObject?
Or, better yet, how to fix this error?
Or, even better still, a more straightforward solution to my original problem (displaying a $ in front of the SpinButton contents)?
From the PyGtk documentation:
http://developer.gnome.org/pygtk/stable/class-gtkspinbutton.html#signal-gtkspinbutton--input
The "input" signal is emitted when the value changes. The value_ptr is a GPointer to the value that cannot be accessed from PyGTK. This signal cannot be handled in PyGTK.
So, I'm atonished to see something like:
double = ctypes.c_double.from_address(hash(gpointer))
This is a real hack, so you got and awful error "Segmentation Fault" which means your are messing in memory you don't have to, and it's quite generic, it happens for example when in C you try to manually access a memory pointer not handled by your application.
This will be a hard one, I tried for one hour and all approaches I tried had problems. I know is not and answer, but as a workaround and if you only need the currency symbol (not grouping) you can experiment adding a currency symbol image to the inherited Gtk.Entry set_icon_from_pixbuf():
(Obviously set the image to a currency image)
Kind regards
Or, even better still, a more straightforward solution to my original problem (displaying a $ in front of the SpinButton contents)?
I got this working by hooking up the GTKSpinButton's output signal to a simple handler:
def onSpinOutput(self, spinbutton):
adjustment = spinbutton.get_adjustment()
spinbutton.set_text(str(int(adjustment.get_value())) + "%")
return True
In my case I'm adding a percentage sign after the number, but you can easily change this to insert something else in front instead. Note that this sets the text and not the value of the entry; this will not work for numerical spin buttons.
Related
I need to continually change the styling of parts of a TextView to do syntax highlighting. However with about 1,000 Gtk.TextTags it becomes extremely slow. I don't want to use a Gtk.SourceView because I want to customize styling myself. It seems rather odd that 1,000 tags is enough to make the TextView choppy since a word processing document might have that many.
This code gives an example of the slowness. With 1,000s of characters it starts taking about half a second to update the tags.
from gi.repository import Gtk
class MyWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self)
self.set_default_size(600, 400)
self.scrolledwindow = Gtk.ScrolledWindow()
self.textview = Gtk.TextView()
buffer = self.textview.get_buffer()
buffer.create_tag("tag", background="orange")
buffer.connect("end-user-action", self.action)
self.scrolledwindow.add(self.textview)
self.add(self.scrolledwindow)
def action(self, buffer):
start = buffer.get_start_iter()
veryend = buffer.get_end_iter()
buffer.remove_all_tags(start, veryend)
end = buffer.get_iter_at_offset(1)
for i in range(len(buffer.get_text(start, veryend, False))//2+1):
buffer.apply_tag_by_name("tag", start, end)
start.forward_chars(2)
end.forward_chars(2)
win = MyWindow()
win.connect("delete-event", Gtk.main_quit)
win.show_all()
Gtk.main()
Can I speed this up? Are there any workarounds?
If someone ever finds this question via google let me tell you that the problem here is the removing of all tags and adding it again. This is of course a very time consuming operation and shouldn't be coded this way.
When using gtk.AccelGroup any combination with Tab charater is invalid. Now I do understand that UI navigation is done using this key but in some special cases I need to override that behavior. Is there a way to make AccelGroup allow usage of this key?
For example:
group = gtk.AccelGroup()
group.connect(gtk.gdk.keyval_from_name('Tab'), gtk.gdk.CONTROL_MASK, 0, callback)
You can easily get key names and values with this :
#!/usr/bin/env python
import gtk
import gtk
def catch_button(window, event, label):
keyval = event.keyval
name = gtk.gdk.keyval_name(keyval)
mod = gtk.accelerator_get_label(keyval, event.state)
label.set_markup('<span size="xx-large">%s\n%d</span>'% (mod, keyval))
window = gtk.Window()
window.set_size_request(640,480)
label = gtk.Label()
label.set_use_markup(True)
window.connect('key-press-event',catch_button, label)
window.connect('destroy', gtk.main_quit)
window.add(label)
window.show_all()
gtk.main()
But I found that the keynames returned were locale-dependent, of no great use for me. The keyval can probably be used.
Cheers,
Louis
This below is one way to do it. Although if you don't wish for the program to listen for every keypress as you stated above, I should say that I've never run across a way of tying Tab to an AccelGroup. I've tried various things myself, but to no avail.
widget.connect("key-press-event",self.on_key_pressed)
def on_key_pressed(self,widget,event,*args):
if event.keyval == gtk.keysyms.Tab:
do_something()
I have modified a short piece of pyqt code to produce real-time rendering of a user's expression. I have used sympy's pretty-printing function for this, however the output does not appear correctly as the QTextBrowser uses a proportional rather than a monospaced font.
As a beginner I would also welcome any other thoughts you had on the code.
Many thanks and best wishes,
Geddes
from __future__ import division
import sys
import sympy
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class Form(QDialog):
def __init__(self, parent=None):
super(Form, self).__init__(parent)
self.browser = QTextBrowser()
self.lineedit = QLineEdit("please type an expression")
self.lineedit.selectAll()
layout = QVBoxLayout()
layout.addWidget(self.browser)
layout.addWidget(self.lineedit)
self.setLayout(layout)
self.lineedit.setFocus()
self.connect(self.lineedit, SIGNAL("textChanged (const QString&)"),self.updateUi)
def updateUi(self):
text = unicode(self.lineedit.text())
for z in range(0,9):
text = text.replace('x'+str(z),'x^'+str(z))
text = text.replace(')'+str(z),')^'+str(z))
text = text.replace(str(z)+'x',str(z)+'*x')
text = text.replace(str(z)+'(',str(z)+'*(')
try:
self.browser.append(sympy.printing.pretty(sympy.sympify(text)))
self.browser.clear()
self.browser.append(sympy.printing.pretty(sympy.sympify(text)))
except:
if text=='': self.browser.clear()
app = QApplication(sys.argv)
form = Form()
form.show()
app.exec_()
You should be able to change the font with setFontFamily.
Concerning your code: I haven't really worked with PyQt yet (only some hacks like the font family in qbzr...), so I can't tell you if everything is okay. But the following is not a good idea:
except:
if text=='': self.browser.clear()
Never catch all exceptions with except:. This will also catch BaseExceptions like SystemExit, which shouldn't be caught unless you have a reason to do so. Always catch specific exceptions, or if you're at the highest level (before the unhandled exception handler is executed) and want to log errors, rather use except Exception: which will only handle exceptions based on Exception.
if text=='' - I think if not text is more "pythonic".
QTextBrowser inherits QTextEdit, so you can use the setCurrentFont(QFont) method to set a monospace font.
self.browser = QTextBrowser()
self.browser.setCurrentFont(QFont("Courier New")) #Or whatever monospace font family you want...
As for general comments on style, there's probably a way do change your text replacement stuff in updateUi() to regex, but I can't be sure without seeing sample data to figure out what you're trying to do.
Also, you should probably refactor
try:
self.browser.append(sympy.printing.pretty(sympy.sympify(text)))
self.browser.clear()
self.browser.append(sympy.printing.pretty(sympy.sympify(text)))
except:
if text=='': self.browser.clear()
Into something more like:
self.browser.clear()
try:
self.browser.append(sympy.printing.pretty(sympy.sympify(text)))
except:
if text=='': self.browser.clear()
Except probably catching the actual Exception you're expecting.
EDIT
Here's something for the equation normalizing it looks like you're trying to do, it works with lowercase a-z and real numbers:
def updateUi(self):
text = unicode(self.lineedit.text())
text = re.sub(r'(\d+)([\(]|[a-z])',r'\1*\2',text) #for multiplication
text = re.sub(r'([a-z]|[\)])(\d+)',r'\1^\2',text) #for exponentiation
The first pattern looks for 1 or more digits \d+ followed by an open parenthesis, or a single letter a-z [\(]|[a-z]. It uses parentheses to capture the digit part of the pattern and the variable part of the pattern, and inserts a * between them. \1*\2.
The second pattern looks for a variable a-z or a close parenthesis [a-z]|[\)], followed by one or more digits \d+. It uses the grouping parentheses to capture the digit and the variable again, and inserts a ^ between them \1^\2.
It's not quite perfect (doesn't handle xy --> x*y) but its closer. If you want to make a full computer algebra system you'll probably need to build a dedicated parser :)
I have a treeview with a CellRendererCombo in a given column. I use the following code to set up the column:
crc = gtk.CellRendererCombo()
crc.set_property('model', comboModel)
crc.set_property('text-column', 0)
crc.set_property('editable', True)
crc.set_property('has_entry', False)
cl = gtk.TreeViewColumn(ctitle, crc, text=i)
def changed(cell, path, newiter):
treeViewModel[path][0] = "HAH"
crc.connect("changed", changed)
treeView.append_column(cl)
treeView is a TreeView, treeViewModel is its model, and comboModel is the model for the combo entry containing just two strings.
If I run the code, then the combo works as expected, except that the first time I select an entry I get the following errors:
c:\python25\lib\site-packages\twisted\internet\gtk2reactor.py:255: Warning: inva
lid unclassed pointer in cast to `GObject'
gtk.main()
c:\python25\lib\site-packages\twisted\internet\gtk2reactor.py:255: Warning: g_ob
ject_notify: assertion `G_IS_OBJECT (object)' failed
gtk.main()
On the second time I get:
c:\python25\lib\site-packages\twisted\internet\gtk2reactor.py:255: Warning: inva
lid uninstantiatable type `<invalid>' in cast to `GObject'
gtk.main()
and on the third time the program crashes. If I change the connect line to:
crc.connect("edited", changed)
...then the code works fine. However, the value only changes after clicking out of the combo box, and I'd rather have it change every time an object is selected. How can I do the latter?
EDIT: I just noticed this in the API docs for pygtk:
Note that as soon as you change the model displayed in the tree view, the tree view will immediately cease the editing operating. This means that you most probably want to refrain from changing the model until the combo cell renderer emits the edited or editing_canceled signal.
It doesn't mention that the code would crash, though. In any case, I'd like it that after clicking an entry in the combobox, editing stops, without having to press ENTER or click somewhere else. How can I accomplish that?
Ending editing of a CellRendererCombo immediately after an item is selected is a two stage process.
In the first stage, you must capture the combo itself, since it is not accessible later. To capture the combo, connect to the editing-started CellRenderer signal. You may define the connection in Glade or create it manually in code.
In the second stage, emit a focus-out-event in a changed signal handler for CellRendererCombo.
Here is your original code modified to demonstrate:
comboEditable = None
crc = gtk.CellRendererCombo()
crc.set_property('model', comboModel)
crc.set_property('text-column', 0)
crc.set_property('editable', True)
crc.set_property('has_entry', False)
cl = gtk.TreeViewColumn(ctitle, crc, text=i)
def changed(cell, path, newiter):
treeViewModel[path][0] = "HAH"
e = gtk.gdk.Event(gtk.gdk.FOCUS_CHANGE)
e.window = treeView.window
e.send_event = True
e.in_ = False
comboEditable.emit('focus-out-event', e)
def started(cell, editable, path):
# Or to make life more predictable, use a class and set self.comboEditable
global comboEditable
comboEditable = editable
crc.connect('changed', changed)
crc.connect('editing-started', started)
treeView.append_column(cl)
Note that in more recent versions of GTK+, you don't ordinarily modify the TreeModel in the changed signal handler. You should use the edited signal handler.
Here is the final version:
comboEditable = None
crc = gtk.CellRendererCombo()
crc.set_property('model', comboModel)
crc.set_property('text-column', 0)
crc.set_property('editable', True)
crc.set_property('has_entry', False)
cl = gtk.TreeViewColumn(ctitle, crc, text=i)
def changed(cell, path, newiter):
e = gtk.gdk.Event(gtk.gdk.FOCUS_CHANGE)
e.window = treeView.window
e.send_event = True
e.in_ = False
comboEditable.emit('focus-out-event', e)
def started(cell, editable, path):
# Or to make life more predictable, use a class and set self.comboEditable
global comboEditable
comboEditable = editable
def edited(cell, path, newtext):
treeViewModel[path][columnNumber] = newText
crc.connect('changed', changed)
crc.connect('editing-started', started)
crc.connect('edited', edited)
treeView.append_column(cl)
i'm not sure, but i guess the fastest way to get an answer is to search the pygtk mailing list, and if you cant find a similar post, try posting it to the mailing list.
pygtk mailing list
This doesn't work well:
image_log = gtk.Image()
image_log.set_from_file("test.png")
self.out_button = gtk.Button()
self.out_button.add(image_log)
self.err_button = gtk.Button()
self.err_button.add(image_log)
another_box.pack_start(self.out_button, False)
another_box.pack_start(self.err_button, False)
The problem is, image_log is used twice and GTK doesn't like it. Is there some .copy() method? Or should I just use plain vanilla deepcopy?
EDIT: Looks like there is no default way to clone objects in GTK. Factory will do the trick in this case.
GTK warning:
app/gui.py:248: GtkWarning: gtk_box_pack: assertion `child->parent == NULL' failed
hbox_errlog.pack_start(image_log)
You could use a factory function to reduce code duplication
def make_image_from_file(fname):
im = gtk.Image()
im.set_from_file(fname)
return im
self.out_button.set_image(make_image_from_file(..))
Revisiting
There is a much more natural way. You will like it. In PyGTK 2.12+:
gtk.image_new_from_file(filename)
I had something in the back of my mind telling me this, but I didn't look it up.
http://www.pygtk.org/docs/pygtk/class-gtkimage.html#function-gtk--image-new-from-file
Use
def clone_widget(widget):
widget2=widget.__class__()
for prop in dir(widget):
if prop.startswith("set_") and prop not in ["set_buffer"]:
prop_value=None
try:
prop_value=getattr(widget, prop.replace("set_","get_") )()
except:
try:
prop_value=getattr(widget, prop.replace("set_","") )
except:
continue
if prop_value == None:
continue
try:
getattr(widget2, prop)( prop_value )
except:
pass
return widget2
All this try ... except blocks are there because not all properties could be copied by using
set_prop(get_prop). I haven't tested this for all properties and widgets yet, but it worked well for gtkEntry. Maybe this is slow, but it's nice the use :)
Why not
image_log = gtk.Image()
image_log.set_from_file("test.png")
image_logb = gtk.Image()
image_logb.set_from_file("test.png")
self.out_button = gtk.Button()
self.out_button.add(image_log)
self.err_button = gtk.Button()
self.err_button.add(image_logb)
another_box.pack_start(self.out_button, False)
another_box.pack_start(self.err_button, False)
It is only an extra 2 lines of code, and maybe more efficient than cloning/copying the first image object.
That way you can treat out_button and err_button independently. But it should make sense to use the same gtk.Image() object for both buttons ... it is just an image.
Edit
To avoid duplication (seems like overkill though) you could write a factory for gtk.Image() objects from the same image.
def gtkimage_factory(num_objs, image_file):
i=0
imglist = []
while i<num_objs:
img_ob = gtk.Image()
img_ob.set_from_file(image_file)
imglist.append( img_ob )
i+=1
return imglist
Or something along those lines, you get the idea. But a factory seems like overkill unless you are producing loads of these things and need them independently parented in GTK.
Then...
image_list = gtkimg_factory(2, "test.png")
self.out_button = gtk.Button()
self.out_button.add(image_list[0])
self.err_button = gtk.Button()
self.err_button.add(image_list[1])
another_box.pack_start(self.out_button, False)
another_box.pack_start(self.err_button, False)
Maybe it is something to do with GTK resource management?
all if you want to use the collection of widgets arranged in some fashion, again and again, let's say a box with one entry box and a label(can be complex as hell if you want) and want to use it multiple time in your application like depending upon condition how many similar tabs are needed but obviously working with different data that use composites with glade.
With pygi(gi_composites) python library you can make your own widgets and use them multiple times.
[https://github.com/virtuald/pygi-composite-templates][1]