gtk: trouble modifying TreeView model on CellRendererCombo 'changed' signal - python

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

Related

GTK+: How can I add a Gtk.CheckButton to a Gtk.FileChooserDialog?

I want to present a folder chooser to users, and allow them to specify whether that folder should be processed recursively. I tried
do_recursion = False
def enable_recurse(widget, data=None):
nonlocal do_recursion
do_recursion = widget.get_active()
choose_file_dialog = Gtk.FileChooserDialog(use_header_bar=use_header_bar,
title=_(da_title), # _( invokes GLib.dgettext
action=Gtk.FileChooserAction.SELECT_FOLDER)
choose_file_dialog.add_button("_Cancel", Gtk.ResponseType.CANCEL)
choose_file_dialog.add_button("_OK", Gtk.ResponseType.OK)
check_box_1 = Gtk.CheckButton("_RECURSE")
check_box_1.connect("toggled", enable_recurse)
choose_file_dialog.add(check_box_1)
But that fails, and generates the warning:
Gtk-WARNING **: 14:03:31.139: Attempting to add a widget with type GtkCheckButton to a GtkFileChooserDialog, but as a GtkBin subclass a GtkFileChooserDialog can only contain one widget at a time; it already contains a widget of type GtkBox
What is a correct way to do this?
As noted above, an answer is to use set_extra_widget instead of add
check_box_1 = Gtk.CheckButton(label="Recurse source directory")
check_box_1.connect("toggled", enable_recurse)
choose_file_dialog.set_extra_widget(check_box_1)
But I do not like the placement of the checkbox in the lower left corner, so I hope someone has a better answer.

python GtkComboBox.remove_all(): how to prevent changed signal to be triggered

I have designed a gtk3 layout with Glade, including some comboboxtext widgets.
The bookings_tour_selector ComboBoxText has the changed signal connected, so when user selects an option, this is detected. That part works fine.
Now the problem, when I make the call: bookings_tour_selector.remove_all() the changed signal is triggered once for every single item being removed. That's not the expected behaviour. I expect it to not trigger the signal at all.
How to prevent this signal to be triggered when removing items?
Just add conditional in your callback, i.e:
def on_changed_combobox (self, widget):
if self.bookings_tour_selector.get_active () != -1:
#do whatever you want when combo box changed
#if not, it simply, does nothing

Rebuild interface to change the language (GTK)

I am beginning to work on a program in which i want multilingual support, but since it is pretty modular (and i want it to be even more in the future), a language change means "destroy what you had of interface and build again with the content which language modules have". (You can see the source as of now on GitHub)
This full-modular approach may give many problems, but i still want it, and so the problem is: Whenever i destroy the widgets i had, until i am alone with the raw Gtk.Window itself, i am not able to assign once again widgets to it. They won't get displayed at all, sometimes silently, sometimes with errors depending on my approach.
Lets suppose the class window, which inherits from a Gtk.Window.
This class is the raw window, and i assign to it a Gtk.Box -
self.interface.
self.interface itself, has two Gtk.Box's, one sidebar and one stack of contents.
To rebuild i tried to:
Change the language variable
Use the method destroy on self.interface, which removes the widget and its child's.
Reuse the function to build the widgets stack on top of self.interface
Re-add self.interface to self (window).
This approach returns:
g_object_unref: assertion 'G_IS_OBJECT (object)' failed
Gtk.main()
Pointing to the .show_all() method in this file.
I've already tried to leave interface without using .destroy on it, applying only on its child's, and then creating them again over it, but didn't worked. The window was left blank with no error at all.
The code i am trying right now is:
#Remember, self is a Gtk.Window()
def __init__(self):
[...]
self.interface = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
self.stack = None
self.add(interface)
self.build_interface()
def build_interface(self):
self.interface.pack_start(
self.create_side_navigation(
self.interface_data["menu"][self.language]["name"])
, False, False, 0
)
self.stack = self.create_content_stack(self.interface_data["menu"][self.language])
self.interface.pack_start(self.stack, True, True, 0)
###Code to rebuild(this is a question dialog):
if response == Gtk.ResponseType.OK:
self.language = self.new_language["Index"]
self.new_language = None
self.stack.destroy()
self.interface.destroy()
self.interface = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
self.build_interface()
self.add(self.interface)
This code will cause the previously told "g_object_unref" error.
What is causing this? Why can't i add anything once deleted?
Feel free to clone the repo and try, the code is well commented(yet i am not sure if poorly written, i am a python newbie) and its quite easy to understand where is the problematic part. Its not too big.
PS: It should need GTK+3.12 because of the popovers.
As a GTK Dev showed to me, GTK has by default all the widgets invisible.
The error was caused in the line which declared the whole interface visibility (windowclass.show_all()), but since the interface changed since when it was applied, it threw that warning.
He pointed me to .remove() instead of .destroy(), and to set .show_all() to the new elements after set up.
The next commit(or the following) on that git, has the solution.
The best way to be multilingual is to keep your widgets the same and merely change the text of labels and titles. This can be done without disturbing the overall setup. For example:
s='Stop'
if lang=='fr': s='Arret'
...
somelabel.set_label(s)

Can I exclude specific controllers from the standard tab traversal?

I've made a custom dialog that contains a series of text controls. Every text control has a couple of buttons beside them for adding specific values more conveniently. I don't want these buttons to receive focus when the user it tab traversing through the dialog, since the user, in most cases, won't need to use the buttons.
Is there any convenient way to exclude specific controllers from the standard tab traversal?
A simple way to prevent a button from being focused with the keyboard is to derive from wx.lib.buttons.GenButton or wx.lib.buttons.ThemedGenButton which are based on wx.PyControl that supports overriding of AcceptsFocusFromKeyboard():
class NoFocusButton(wx.lib.buttons.ThemedGenButton):
def __init__(self, parent, id=wx.ID_ANY, label=wx.EmptyString, pos=wx.DefaultPosition, size=wx.DefaultSize, style=0, validator=wx.DefaultValidator, name=wx.ButtonNameStr):
wx.lib.buttons.ThemedGenButton.__init__(self,parent,id,label,pos,size,style,validator,name)
def AcceptsFocusFromKeyboard(self):
return False # does not accept focus
For more complex navigation rules or controls, you could handle wx.EVT_NAVIGATION_KEY and manage the navigation yourself. To get the list of windows to navigate, you can use self.GetChildren(). The index of the the currently focused window in the wx.WindowList can be obtained through .index(mywindow).
With that information, you can navigate through the list whenever the user presses the "navigation key" and set the focus to the next applicable control, skipping those that you don't want to focus.
To make navigating through the list easier, you could create a generator:
def CycleList(thelist,index,forward):
for unused in range(len(thelist)): # cycle through the list ONCE
if forward:
index = index+1 if index+1 < len(thelist) else 0
else:
index = index-1 if index-1 >= 0 else len(thelist)-1
yield thelist[index]
In the dialog, handle wx.EVT_NAVIGATION_KEY:
self.Bind(wx.EVT_NAVIGATION_KEY, self.OnNavigationKey)
def OnNavigationKey(self,event):
children = self.GetChildren() # list of child windows
focused = self.FindFocus() # current focus
# avoid accessing elements that do not exist
if not focused or focused not in children:
event.Skip() # use default behavior
return
index = children.index(focused)
for child in CycleList(children,index,event.GetDirection()):
# default behavior:
if child.AcceptsFocusFromKeyboard():
child.SetFocus()
return
The example above emulates the default behavior: it cycles through focusable controls (skipping unfocusable controls like static texts).
You could expand the check to exclude specific controls or create a custom button class that implements AcceptsFocusFromKeyboard returning False.
NOTE: While wx.PyWindow, wx.PyPanel and wx.PyControl implement the mechanism to allow overriding of AcceptsFocusFromKeyboard, the standard wxPython controls do not.
However, handling wx.EVT_NAVIGATION_KEY and checking AcceptsFocusFromKeyboard on the python side will access the actual python object which will invoke the overridden method.
If you were using C++ this problem has a straightforward solution as described in the remainder of this answer. In wxPython it seems you cannot specialize wxWidgets classes - which seems to me a fatal snag.
You could create a specialization of the button control which will be left out of the tab traversal by overriding AcceptsFocusFromKeyboard() to return FALSE.
http://docs.wxwidgets.org/trunk/classwx_window.html#a2370bdd3ab08e7ef3c7555c6aa8301b8
The following C++ code works fine: focus jumps from the first to the third button when tab is pressed
class cNoTabButton : public wxButton
{
public:
cNoTabButton(wxWindow *parent,
wxWindowID id,
const wxString& label = wxEmptyString,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = 0 )
: wxButton(parent,id,label,pos,size,style)
{}
bool AcceptsFocusFromKeyboard() const {
return false;
}
};
MyFrame::MyFrame(const wxString& title)
: wxFrame(NULL, wxID_ANY, title)
{
// set the frame icon
SetIcon(wxICON(sample));
wxPanel * panel = new wxPanel(this,-1,wxPoint(0,0),wxSize(500,500));
new wxButton(panel,-1,"TabPlease",wxPoint(20,20));
new cNoTabButton(panel,-1,"NoTabThanks",wxPoint(100,20));
new wxButton(panel,-1,"OKWithMe",wxPoint(200,20));
}
This is not a perfect solution but one way to do this is actually to delay your control's getting the focus back by half a second.
Your main window will get focus back and buttons still work. I use it because I want my main windows to handle all key presses but still to contain buttons on it that are used with the mouse.
So you bind your KILL_FOCUS event in the control that is supposed to preserve the focus and you create a list of controls that cannot get the focus from it.
First a helper function to get all children:
def GetAllChildren(control):
children = []
for c in control.GetChildren():
children.append(c)
children += GetAllChildren(c)
return children
In my case I want all the children of the window to not get focus
self.not_allowed_focus = GetAllChildren(self)
self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)
In my KILL FOCUS handler I ask for the focus back after half a second
def OnKillFocus(self,evt):
print "OnKillFocus"
win = evt.GetWindow()
if win in self.not_allowed_focus:
wx.CallLater(500,self.SetFocus)

Formatting a spinbutton's display in PyGObject/GTK+3

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.

Categories

Resources