Manually toggling of widgets ability to emit signals in pygtk - python

I'm probably missing something basic in my pygtk programming, but I want to connect a signal to e.g. an gtk.Entry and then make it only emit the connected signal when I allow it to do so.
That is, I want to add something to toggle_signalling in this minimal code (only for interactive use) so that Hello is only printed when signalling is "allowed":
import gtk
signal_states = ['On', 'Off']
signal_state = True
def reporter_function(*args,**kwargs):
print "Hello"
def toggle_signaling(widget, **kwargs):
global signal_state
signal_state = not signal_state
widget.set_label(signal_states[signal_state])
print ['Emit allowed', 'Emit not allowed'][not signal_state]
w = gtk.Window()
e = gtk.Entry()
b = gtk.Button(label=signal_states[signal_state])
hbox = gtk.HBox()
hbox.pack_start(e)
hbox.pack_end(b)
e.connect("changed", reporter_function)
b.connect("clicked", toggle_signaling)
w.add(hbox)
w.show_all()
I've previously let there be a boolean flag for such send signal state e.g. self._updating in my custom widget-classes and let the callback-functions check this state before doing anything. That is not what I want.
I want a gtk-native way of letting the widget know that it shouldn't send the signal (when I've clicked the button in the example). I'm pretty sure I've stumbled upon a way of doing this once but I'm lost at finding it again.
Also, to be absolutely clear, the widget must still be allowed to be enabled.

I don't think there's a way around the boolean flag. Wether a widget is allowed to emit a signal or not is additional application logic and therefore has to be kept somewhere.
Based on your previous research on the topic and the quite acurately described functionality you're most probably looking for chapter 20.1.2 of the PyGTK tutorial.
I put comprehensive example code together. The only thing to keep around except the boolean indicator is the handler_id of the connected signal. As you might notice, it's programmed in Gtk3, but the important methods handler_block and handler_unblock function the exact same way in both Gtk 2 and 3.
from gi.repository import Gtk
class TestWindow(Gtk.Window):
def __init__(self, *args, **kwargs):
Gtk.Window.__init__(self, *args, **kwargs)
self.connect("destroy", Gtk.main_quit)
self.is_allowed = True
self.create_widgets()
self.show_all()
def create_widgets(self):
box = Gtk.HBox()
self.entry = Gtk.Entry()
self.handler_id = self.entry.connect("changed", self.on_entry_changed)
box.pack_start(self.entry, True, True, 0)
button = Gtk.Button("Toggle")
button.connect("clicked", self.on_button_clicked)
box.pack_start(button, True, True, 0)
self.add(box)
def on_entry_changed(self, *args):
print "entry has changed"
def on_button_clicked(self, *args):
if self.is_allowed:
self.entry.handler_block(self.handler_id)
print "now blocking"
else:
self.entry.handler_unblock(self.handler_id)
print "now unblocking"
self.is_allowed = not self.is_allowed
TestWindow()
Gtk.main()

Related

Menubar sometimes does not become un-greyed when QFileDialog closes

OS: W10. This may be significant. If you have different results on a different platform, feedback would be helpful.
Here is an MRE. If you run it and go Ctrl+O, the menu labels become greyed. If you select a file in the QFileDialog by clicking the "Open" button or using its mnemonic (Alt+O), the open-file dialog is dismissed and the "Files" and "Help" menus become un-greyed.
However, if you go Ctrl+O again, and this time enter the name of a file in the "File name" box (QLineEdit), and then press Return, the dialog is dismissed (with a successful selection result) but the "Files" and "Help" menus remain greyed-out. It looks like this:
import sys, os
from PyQt5 import QtWidgets, QtCore, QtGui
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('Greying of menus MRE')
self.setGeometry(QtCore.QRect(100, 100, 400, 200))
menubar = QtWidgets.QMenuBar(self)
self.setMenuBar(menubar)
self.files_menu = QtWidgets.QMenu('&Files', self)
menubar.addMenu(self.files_menu)
self.help_menu = QtWidgets.QMenu('&Help', self)
menubar.addMenu(self.help_menu)
self.new_action = QtWidgets.QAction('&New', self)
self.files_menu.addAction(self.new_action)
self.open_action = QtWidgets.QAction('&Open', self)
self.files_menu.addAction(self.open_action)
self.open_action.setShortcut("Ctrl+O")
self.open_action.triggered.connect(self.open_file)
def focusInEvent(self, event ):
print('main_window focusInEvent')
super().focusInEvent(event)
def focusOutEvent(self, event ):
print('main_window focusOutEvent')
super().focusInEvent(event)
def activateWindow(self):
print('main_window activateWindow')
super().activateWindow()
def open_file(self):
print('open file')
main_window_self = self
# open_doc_dialog = QtWidgets.QFileDialog(self.get_main_window())
class OpenDocFileDialog(QtWidgets.QFileDialog):
def accepted(self):
print('accepted')
super().accepted()
def accept(self):
print('accept')
super().accept()
def close(self):
print('close')
super().close()
def done(self, r):
print(f'done r {r}')
# neither of these solves the problem:
# main_window_self.activateWindow()
# main_window_self.files_menu.activateWindow()
super().done(r)
def hide(self):
print(f'hide')
super().hide()
def focusInEvent(self, event ):
print('focusInEvent')
super().focusInEvent(event)
def focusOutEvent(self, event ):
print('focusOutEvent')
super().focusInEvent(event)
def activateWindow(self):
print('activateWindow')
super().activateWindow()
open_doc_dialog = OpenDocFileDialog(self)
open_doc_dialog.setWindowTitle('Choose file')
open_doc_dialog.setDirectory(os.getcwd())
# we cannot use the native dialog, because we need control over the UI
options = open_doc_dialog.Options(open_doc_dialog.DontUseNativeDialog)
open_doc_dialog.setOptions(options)
open_doc_button = open_doc_dialog.findChild(QtWidgets.QDialogButtonBox).button(QtWidgets.QDialogButtonBox.Open)
lineEdit = open_doc_dialog.findChild(QtWidgets.QLineEdit)
# this does not solve the problem
# lineEdit.returnPressed.disconnect()
# lineEdit.returnPressed.connect(open_doc_button.click)
print(f'open_doc_button {open_doc_button}, lineEdit {lineEdit}')
# show the dialog
dialog_code = open_doc_dialog.exec()
if dialog_code != QtWidgets.QDialog.Accepted: return
sel_files = open_doc_dialog.selectedFiles()
print(f'sel_files: {sel_files}')
app = QtWidgets.QApplication([])
main_window = MainWindow()
main_window.show()
sys.exit(app.exec())
This problem can be understood, if not solved, with reference to this answer.
Note that this greying-out is not disablement. As explained in the above link, this has to do with "active/inactive states" of the menus (or their labels). The menus remain enabled throughout, although in this case it's impossible to know that while the open-file dialog is showing because it is modal. Clicking on one menu after the dialog has gone, or just hovering over it, is enough to un-grey them both...
The explanation, as I understand it, is that the "File name" box QLineEdit has a signal, returnPressed, which appears to activate something subtley different to the slot which is invoked when you use the "Choose" button. You can see I have experimented with trying to re-wire that signal, to no avail.
The method done of the QFileDialog appears to be called however the dialog closes (unlike close!), so I tried "activating" the main window... and then the individual QMenus... Doesn't work.
I am not clear how to get a handle on this "active state" business or why the slot connected to returnPressed is (seemingly) unable to give the "active state" back to the menus when the other slot manages to do so.
Edit
Searching on Musicamante's "unpolishing" suggestion led me to this:
lineEdit.returnPressed.disconnect()
def return_pressed():
style = main_window_self.menubar.style()
style.unpolish(main_window_self.menubar)
open_doc_button.click()
lineEdit.returnPressed.connect(return_pressed)
... unfortunately this doesn't work.
This looks like a possible Windows-related bug, since I can't reproduce it on Linux. As a work-around, you could try forcing a repaint after the dialog closes:
# show the dialog
dialog_code = open_doc_dialog.exec()
self.menubar.repaint()
Finally got it, thanks to Musicamante's suggestion:
lineEdit.returnPressed.disconnect()
def return_pressed():
style = main_window_self.menubar.style()
style.unpolish(main_window_self.menubar)
open_doc_button.click()
main_window_self.menubar.repaint()
lineEdit.returnPressed.connect(return_pressed)
... I actually tried this several times, just to make sure it was doing what was intended. So in fact, fortunately, no single-shot timer was needed in this case.

python3 gtk3 GUI only partially updating despite using GObject.idle_add [duplicate]

I have been using this website pretty often in order to solve small issues that I have while programming in Python. This time, somehow I could not find a suitable solution for my situation. So, here is my problem:
I want to dynamically add entries to a gtk.VBox widget. The problem is that it doesn't work the way I want it to work. I simply have a button, whose action is to add an additional widget to a VBox. Unfortunately the widget doesn't appear on the window. I guess, I have to add something like a repaint function call, but I didn't find anything like that. Here is a sample code, showing my problem:
import gtk
class DynamicVbox:
def __init__(self):
self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
self.window.connect("destroy", self.close_application)
self.window.set_size_request(400,320)
#a hBox to put the button and the dynamic vBox
hBox = gtk.HBox(False, 0)
addButton = gtk.Button("add checkbox")
addButton.connect("clicked", self.AddCheckButton)
self.vBox = gtk.VBox(False, 0)
self.vBox.pack_start(gtk.CheckButton("CheckButton"), True, True, 1)
hBox.pack_start(self.vBox, True, True, 5)
hBox.pack_end(addButton, False, False, 5)
self.window.add(hBox)
#start gtk
self.window.show_all()
gtk.main()
def AddCheckButton(self, button):
self.vBox.pack_start(gtk.CheckButton("CheckButton"), True, True, 1)
print "adding checkbox..."
def close_application(self, widget):
gtk.main_quit()
# run it
a = DynamicVbox()
A appreciate any help. Thanks in advance.
The new check button is there, but not visible until you call show() on it:
def AddCheckButton(self, button):
button = gtk.CheckButton("CheckButton")
self.vBox.pack_start(button, True, True, 1)
button.show()
print "adding checkbox..."

GTK: Problems redrawing widgets on Gtk.ApplicationWindow

I'm creating a GTK GUI in Python for an already existing program. Imagine the following structure:
program
|----gtk
|-----application.py
|-----mainwindow.py
|-----mainwidgets.py
Of course this is simplified. But the GUI works somewhat like this: application.py is a Gtk.Application that creates instances of the objects in mainwidgets, provides back-end functions which are passed as callbacks to mainwindow.py, which places the widgets in a Gtk.ApplicationWindow.
There are a couple of cases where I'm having trouble. I'll go over two which I think are related.
Let's go for the simpler one first. There's a simple button defined in mainwidget, which is used as a statusbar in my program.
class Statusbar(Gtk.Widget):
def __init__(self, on_button_do):
super(Gtk.Widget, self).__init__()
self.callback = on_button_do
self.button_label_int = 0
self.button = Gtk.Button.new_with_label(str(button_label_int)
def increment_button_label(self):
self.button_label_int += 1
self.button.set_label(str(button_label))
Then, when the mainwindow receives a signal, it is instructed to call
statusbar.increment_button_label(). This crashes almost immediately with a segmentation fault, no more info given about the problem, as soon as that signal is received.
class AppWindow(Gtk.ApplicationWindow):
__gsignals__ = {
"new_log": (GObject.SIGNAL_RUN_FIRST, None, ())
}
def __init__(self, statusbar, sidebar, *args, **kwargs):
super(Gtk.ApplicationWindow, self).__init__(*args, **kwargs)
self.statusbar = statusbar
self.sidebar = sidebar
self.notificationBox = Gtk.Box()
self.notificationBox.pack_start(self.statusbar.get_button(), True, True, 0)
# MAINBOX: THE BIGGER BOX OF ALL THE LITTLE BOXES
self.mainBox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.mainBox.pack_start(self.
self.mainBox.pack_start(self.notificationBox, False, False, 0)
self.add(self.mainBox)
self.show_all()
def do_new_log(self):
"""What should the window do when it gets a new_log signal"""
statusbar.increment_button_label()
Another (related?) problem having to with updating the display of widgets: my sidebar. The sidebar is actually just a Gtk.ListStore and a Gtk.TreeView, nothing fancy. In application.py you'd find code like this:
def do_startup(self):
Gtk.Application.do_startup(self) # deep GTK magic
self.sidebar = Sidebar(self.workspace_manager,
self.changeWorkspace,
CONF.getLastWorkspace())
And then that instance of sidebar is passed to mainwindow.py to be put in a box. Everything works fine for now.
The problem comes when I try to add information to the sidebar. The back-end part of it works, because if I restart the application, I can see the new entry in the sidebar. And the sidebar gets information from this backend:
class Sidebar(Gtk.Widget):
def __init__(self, workspace_manager, callback_to_change_workspace, conf):
super(Gtk.Widget, self).__init__()
self.callback = callback_to_change_workspace
self.ws_manager = workspace_manager
self.lastWorkspace = conf
def createTitle(self):
title = Gtk.Label()
title.set_text("Workspaces")
return title
def workspaceModel(self):
self.workspace_list_info = Gtk.ListStore(str)
for ws in self.ws_manager.getWorkspacesNames():
treeIter = workspace_list_info.append([ws])
if ws == self.lastWorkspace:
self.defaultSelection = treeIter
def workspaceView(self):
self.lst = Gtk.TreeView(self.workspace_list_info)
renderer = Gtk.CellRendererText()
column = Gtk.TreeViewColumn("Workspaces", renderer, text=0)
self.lst.append_column(column)
# select by default the last active workspace
if self.defaultSelection is not None:
self.selectDefault = self.lst.get_selection()
self.selectDefault.select_iter(self.defaultSelection)
self.selection = self.lst.get_selection()
self.selection.connect("changed", self.callback)
What maindindow tries to do is put sidebar.lst into a box. That works fine. The thing is when I add a workspace via a dialog box, I doesn't show up, as I stated above.
Any idea about what could cause these problems? Is this way of organizing my problem not all right for some reason for GTK? I think the code itself is fine: the workspaces are added, after all, and GTK doesn't crash. It just doesn't do it fine. Also the button, at first, is displayed just fine, it even emits its signal. But as soon as I try to change its label, everything explodes.
OK, so this actually had to do with multiple threads and signals. It was solved by overriding the emit signal.
class _IdleObject(GObject.GObject):
"""
Override GObject.GObject to always emit signals in the main thread
by emmitting on an idle handler
"""
def __init__(self):
GObject.GObject.__init__(self)
def emit(self, *args):
GObject.idle_add(GObject.GObject.emit, self, *args)

Global event handlers in wxPython (Phoenix)

I'm having a problem trying to handle focus events in wxPython 3, the phoenix fork.
I'm trying to clear the value in a TextCtrl when it's focused if it still has its default value, and then restore that default value on leaving focus if the user hasn't entered a different value.
This is the handler I have:
def on_enter(self, event):
control = wx.Window.FindFocus()
if control.Id in TEXT_MAPPING.keys():
if control.Value == TEXT_MAPPING[control.Id]:
control.SetValue('')
exit_control = event.GetWindow()
if exit_control.Id in (TEXT_MAPPING.keys()):
if not exit_control.GetValue():
exit_control.SetValue(TEXT_MAPPING[exit_control.Id])
event.Skip()
The handler itself works fine, though it's a little clunky. My issue is that I would like to bind this on a global level, such that whenever any wx.EVT_SET_FOCUS event is fired, I can have it be handled by this function.
I found this:
import wx
class MyApp(wx.App):
def FilterEvent(self, evt):
print evt
return -1
app = MyApp(redirect=False)
app.SetCallFilterEvent(True)
frm = wx.Frame(None, title='Hello')
frm.Show()
app.MainLoop()
but I'm confused on how I would pass the event down to the children of MyApp.
What you can do is bind focus kill and focus select to functions, like this:
self.user_text.Bind(wx.EVT_SET_FOCUS, self.on_focus_username)
self.user_text.Bind(wx.EVT_KILL_FOCUS, self.on_deselect_username)
where self is the panel where the TextCtrl "user_text" is located".
The functions might look like this:
def on_focus_username(self, event):
if self.user_text.GetForegroundColour() != wx.BLACK:
self.user_text.SetForegroundColour(wx.BLACK)
# Clear text when clicked/focused
self.user_text.Clear()
def on_deselect_username(self, event):
if self.user_text.GetLineLength(0) is 0:
self.user_text.SetForegroundColour(wx.Colour(192, 192, 192))
# Put a default message when unclicked/focused away
self.user_text.SetValue("Enter Username")
Hope this helped, if I didn't answer something clearly/correctly let me know.

GtkSourceView Scroll to Line with Gobject Introspection [python]

I have successfully created a python GTK application using Gobject Introspection, and opened a source file in a GTKSourceView Widget.
I am attempting to scroll to place a specific line (line 150) into the center of the screen when the user clicks a button.
I have read how to (programmatically) scroll to a specific line in gtktextview/gtksourceview
and also the documentation surrounding GtkSourceViews, GTKTextView, and the buffer objects for both of these (it is my understanding that the sourceview inherits from the textview)
I have attempted to use the following methods:
-getting an iterator at line 150, and then using the scroll_to_iter() method
- getting an iterator at line 150, making a mark at the iterator, and then using the scroll_to_mark() method
I know the iterators and marks are correct, because i can successfully use the place_cursor(iter) method and it successfully places the marker at the end of line 150, however either scrolling to the mark or the iterator using the given methods does nothing.
The scroll to mark method does not return a value, but the iterator method is returning false.
Can anyone please suggest a way to achieve this?
My testing code is as follows:
from gi.repository import Gtk
from gi.repository import GObject
from gi.repository import GtkSource
class MyApplication (Gtk.Window):
def __init__(self, *args, **kwargs):
Gtk.Window.__init__(self, *args, **kwargs)
self.set_title("SourceView Test")
self.set_size_request(400, 400)
self.connect("destroy", Gtk.main_quit)
self.create_widgets()
self.show_all()
def create_widgets(self):
self.sourceview=GtkSource.View.new()
self.lm = GtkSource.LanguageManager.new()
self.scrolledwindow = Gtk.ScrolledWindow()
vbox = Gtk.VBox()
self.add(vbox)
vbox.pack_start(self.scrolledwindow,True,True,0)
self.scrolledwindow.add_with_viewport(self.sourceview)
self.scrolledwindow.show_all()
button = Gtk.Button("Jump To Line")
button.connect("clicked", self.scroll_to_line_and_mark)
self.open_file_in_srcview("/home/tel0s/source_android/system/core/adb/adb.c")
vbox.pack_start(button, False, False, 5)
def open_file_in_srcview(self,filename,*args,**kwargs):
self.buffer = self.sourceview.get_buffer()
self.filename = filename
self.language = self.lm.guess_language(self.filename,None)
self.sourceview.set_show_line_numbers(True)
if self.language:
self.buffer.set_highlight_syntax(True)
self.buffer.set_language(self.language)
else:
print 'No language found for file "%s"' % self.filename
self.buffer.set_highlight_syntax(False)
txt = open(self.filename).read()
self.buffer.set_text(txt)
self.buffer.place_cursor(self.buffer.get_start_iter())
def scroll_to_line_and_mark(self,*args,**kwargs):
print "setting iterator"
iterator = self.sourceview.get_buffer().get_iter_at_line(150)
print iterator
print "scrolling to iter"
if self.sourceview.scroll_to_iter(iterator,0, False, 0.5, 0.5):
print "done!"
else:
print "scrolling failed!!"
if __name__ == "__main__":
MyApplication()
Gtk.main()
So the issue is the line:
self.scrolledwindow.add_with_viewport(self.sourceview)
According to the Gtk docs for scrolledwindow objects, you should only use add_with_viewport for objects that don't support scrolling. For those that do natively like GtkTextView (and so by inheritance GtkSourceView), you should use GtkContainer.add()...
Hope that helps

Categories