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
Related
I'm trying to call the init function of the screen I'm changing my screen index to
For an example, i have this code:
from PyQt5 import QtWidgets as qtw
from PyQt5 import QtGui as qtg
from sys import argv as sysArgv
from sys import exit as sysExit
arialLarge = qtg.QFont("Arial", 18)
class MainWindow(qtw.QWidget):
def __init__(self):
super().__init__()
# Current screen label;
mainWindowLabel = qtw.QLabel("This is the main window", self)
mainWindowLabel.setFont(arialLarge)
mainWindowLabel.move(20, 40)
# Button for going to the HelloWindow screen;
gotoHelloWindowButton = qtw.QPushButton("Go to hello window", self, clicked=lambda: appStack.setCurrentIndex(appStack.currentIndex()+1))
gotoHelloWindowButton.move(100, 100)
class HelloWindow(qtw.QWidget):
def __init__(self):
super().__init__()
# EG: print hello world when I visit this page
print("hello world")
# Current screen label;
helloWindowLabel = qtw.QLabel("This is the hello window", self)
helloWindowLabel.setFont(arialLarge)
helloWindowLabel.move(20, 40)
# Button for going to the MainWindow screen;
gotoMainWindowButton = qtw.QPushButton("Go to main window", self, clicked=lambda: appStack.setCurrentIndex(appStack.currentIndex()-1))
gotoMainWindowButton.move(100, 100)
if __name__ == "__main__":
app = qtw.QApplication(sysArgv)
appStack = qtw.QStackedWidget()
appStack.addWidget(MainWindow())
appStack.setFixedSize(300, 300)
appStack.show()
appStack.addWidget(HelloWindow())
sysExit(app.exec())
If im visiting the HelloWindow from the MainWindow, how can i run the init function of the HelloWindow screen so I can run whatever code I want in there?
I need to be able to do this as on the app im working on as on the mainpage i have dynamically created buttons that all have functions parameters with different indexes to my server, and i need to be able to fetch the data from server based off the clicked button's data index so on the other page I can view the desired data.
The __init__ of a python class is what is called when an instance is created (using SomeClass()), so you should not try (or even think) to call it again, as it could create serious problems and bugs that are hard to track.
I strongly suggest you to read the documentation about classes in Python, as you cannot ignore that aspect in object oriented programming.
If you need to call something everytime the index is changed, then you should better subclass QStackedWidget and control everything from there.
A good solution is to create a standardized function that will be called everytime the page is presented, and ensure that the stack widget correctly calls it.
class FirstPage(QtWidgets.QWidget):
def __init__(self):
super().__init__(self)
# ...
self.nextButton = QtWidgets.QPushButton('Next')
self.doSomething()
def doSomething(self):
...
class SecondPage(QtWidgets.QWidget):
def __init__(self):
super().__init__(self)
# ...
self.prevButton = QtWidgets.QPushButton('Previous')
self.doSomething()
def doSomething(self):
...
class Stack(QtWidgets.QStackedWidget):
def __init__(self):
super().__init__(self)
self.first = FirstPage()
self.first.nextButton.clicked.connect(self.goNext)
self.addWidget(self.first)
self.second = SecondPage()
self.second.prevButton.clicked.connect(self.goPrev)
self.currentChanged.connect(self.initCurrent)
def goNext(self):
self.setCurrentIndex(1)
def goPrev(self):
self.setCurrentIndex(0)
def initCurrent()
if self.currentWidget():
self.currentWidget().doSomething()
if __name__ == "__main__":
app = qtw.QApplication(sysArgv)
appStack = Stack()
appStack.setFixedSize(300, 300)
appStack.show()
sysExit(app.exec())
Note that adding a QMainWindow to a parent is not a good idea, as Qt main windows are intended to be used as top level windows; also note that using fixed geometries (positions and sizes) is often considered bad practice, and you should use layout managers instead.
Overview: I have a fairly large GUI program, built in PyQt5/PySide2 that performs a multitude of operations. Some processes are very quick and a few can take up to around a minute to complete. I had my program set up to display a sort of 'Please Wait...' dialog box for any process that took more than a second or so. I did this using the threading module in conjunction with the signals built into PyQt5/PySide2. While it worked fine, I found that I could not also run threading using concurrent.futures, because these threading modules did not function well together. concurrent.futures did not handle the signals and the threading was generally much slower when processing a multitude of functions back-to-back. So I take my pick when it is appropriate to use either one.
Problem: However, I started experiencing instances where, though the dialog box would appear, it would not display the text within the box, which usually was a message along the lines of "PLEASE WAIT, PROCESSING REQUEST". Essentially, the process of displaying the text was/is being held up until the underlying process finishes. After the process finishes, as long as the window isn't closed, it would then show the text.
Obstacle: In addition to the above described situation, I have re-written parts of the signal and dialog display classes, which on the surface, appear to function as expected and I have included the full code of a basic example. However, when I apply those exact methods to my larger program, it closes itself when it first begins to display the dialog box.
Question: I am wondering if I am missing a basic element or concept in this below example, which when applied to a much bigger program, possibly creates some issues for me. I am looking for the potential red flags in this example.
EDIT: When you run this example, click on the OK button to test the dialog box and threading. The 'main' box will disappear and only the dialog box should be displayed. After the 3 seconds are up, the dialog box disappears and another box appears. This closely resembles the actual functionality of my larger program. Essentially, when you start up you 'login' to the program, so the start menu disappears and then the actual program initializes and loads up. As you can see with this example, the box will display briefly then disappears and this is what happens in my program. A user logs in, but then within a second of logging in, the program closes. I have tried variations on how to get the window to load. The one listed below actually displays it at least, but other methods I've used will just result in a QApplication::exec: Must be called from the main thread error. I've tried a few other methods and listed them below, though obviously none of them work.
import sys
from PySide2 import QtWidgets, QtCore
import PySide2
import time
import threading
def startThread(functionName, *args, **kwargs):
startThread.t = threading.Thread(target=functionName)
startThread.t.daemon = True
startThread.t.start()
class UserInput(object):
def setupUi(self, get_user_input=None):
# Basic shape
self.width = 175
get_user_input.setObjectName("get_user_input")
get_user_input.resize(175, self.width)
# Grid layout for the buttons
self.buttonLayoutGrid = QtWidgets.QWidget(get_user_input)
self.buttonLayoutGrid.setGeometry(QtCore.QRect(10, 115, 155, 50))
self.buttonLayoutGrid.setObjectName("buttonLayoutGrid")
self.buttonLayout = QtWidgets.QGridLayout(self.buttonLayoutGrid)
self.buttonLayout.setContentsMargins(0, 0, 0, 0)
self.buttonLayout.setObjectName("buttonLayout")
self.buttonLayout.setAlignment(PySide2.QtCore.Qt.AlignLeft|PySide2.QtCore.Qt.AlignVCenter)
# Buttons
self.buttonOK = QtWidgets.QPushButton(self.buttonLayoutGrid)
self.buttonOK.setObjectName("buttonOK")
self.buttonOK.setText("OK")
class FakeBox(PySide2.QtWidgets.QDialog):
def __init__(self):
super(FakeBox, self).__init__()
self.setupUi(self)
self.buttonProcessCompleted.clicked.connect(self.close)
def setupUi(self, box_details):
box_details.setObjectName("box_details")
box_details.resize(500, 89)
self.labelProcessStatus = QtWidgets.QLabel(box_details)
self.labelProcessStatus.setGeometry(QtCore.QRect(10, 0, 221, 51))
self.labelProcessStatus.setAlignment(QtCore.Qt.AlignCenter)
self.labelProcessStatus.setWordWrap(True)
self.labelProcessStatus.setObjectName("labelProcessStatus")
self.buttonProcessCompleted = QtWidgets.QPushButton(box_details)
self.buttonProcessCompleted.setEnabled(False)
self.buttonProcessCompleted.setGeometry(QtCore.QRect(60, 60, 111, 23))
self.buttonProcessCompleted.setObjectName("buttonProcessCompleted")
QtCore.QMetaObject.connectSlotsByName(box_details)
class FUNCTION_RUN(PySide2.QtWidgets.QDialog):
display_dialog_window = PySide2.QtCore.Signal(str)
display_process_complete = PySide2.QtCore.Signal(str)
process_complete_no_msg = PySide2.QtCore.Signal()
def __init__(self):
super(FUNCTION_RUN, self).__init__()
self.setupUi(self)
self.buttonProcessCompleted.clicked.connect(self.close)
def setupUi(self, functionRunning):
functionRunning.setObjectName("functionRunning")
functionRunning.resize(234, 89)
self.labelProcessStatus = QtWidgets.QLabel(functionRunning)
self.labelProcessStatus.setGeometry(QtCore.QRect(10, 0, 221, 51))
self.labelProcessStatus.setAlignment(QtCore.Qt.AlignCenter)
self.labelProcessStatus.setWordWrap(True)
self.labelProcessStatus.setObjectName("labelProcessStatus")
self.buttonProcessCompleted = QtWidgets.QPushButton(functionRunning)
self.buttonProcessCompleted.setEnabled(False)
self.buttonProcessCompleted.setGeometry(QtCore.QRect(60, 60, 111, 23))
self.buttonProcessCompleted.setObjectName("buttonProcessCompleted")
QtCore.QMetaObject.connectSlotsByName(functionRunning)
def show_msg(self, msg_text=None):
self.setWindowTitle("RUNNING")
self.labelProcessStatus.setText(msg_text)
self.setModal(False)
self.show()
def process_complete(self, msg_text=None):
self.setWindowTitle("FINISHED")
self.labelProcessStatus.setText(msg_text)
self.buttonProcessCompleted.setText('OK')
self.buttonProcessCompleted.setEnabled(True)
self.setModal(False)
self.show()
def process_finished(self):
self.hide()
class UserInputPrompt(PySide2.QtWidgets.QDialog, UserInput):
def __init__(self):
super(UserInputPrompt, self).__init__()
self.setupUi(self)
self.buttonOK.clicked.connect(self.scoreMass)
def some_more_text(self):
print('Some more...')
def scoreMass(self):
startThread(MASTER.UI.display_msg)
def display_msg(self):
def dialog():
MASTER.UI.hide()
m = ' Processing things...'
MASTER.processing_window.display_dialog_window.emit(m)
MASTER.UI.some_more_text()
time.sleep(3)
MASTER.Second_UI.show()
MASTER.processing_window.process_complete_no_msg.emit()
dialog()
class MASTER(object):
def __init__(self):
super(MASTER, self).__init__()
MASTER.UI = UserInputPrompt()
MASTER.Second_UI = FakeBox()
MASTER.processing_window = FUNCTION_RUN()
MASTER.processing_window.display_dialog_window.connect(MASTER.processing_window.show_msg)
MASTER.processing_window.display_process_complete.connect(MASTER.processing_window.process_complete)
MASTER.processing_window.process_complete_no_msg.connect(MASTER.processing_window.process_finished)
MASTER.UI.show()
app.exec_()
def main():
MASTER()
if __name__ == '__main__':
global app
app = PySide2.QtWidgets.QApplication(sys.argv)
main()
The line where you have MASTER.Second_UI.show() Is probably where you're getting held up. You created an instance in your main thread, which is good, but you'll need to create a signal in that class that you can emit the show() method. Make the FakeBox class look like this:
class FakeBox(PySide2.QtWidgets.QDialog):
show_new_prompt = PySide2.QtCore.Signal()
def __init__(self):
super(FakeBox, self).__init__()
self.setupUi(self)
self.buttonProcessCompleted.clicked.connect(self.close)
And then in your MASTER class look like this:
class MASTER(object):
def __init__(self):
super(MASTER, self).__init__()
MASTER.UI = UserInputPrompt()
MASTER.Second_UI = FakeBox()
MASTER.Second_UI.show_new_prompt.connect(MASTER.Second_UI.show)
# Keeping everything after this line
And then lastly, in your display_msg() function, change it to this:
def display_msg(self):
def dialog():
MASTER.UI.hide()
m = ' Processing things...'
MASTER.processing_window.display_dialog_window.emit(m)
MASTER.UI.some_more_text()
time.sleep(3)
MASTER.processing_window.process_complete_no_msg.emit()
MASTER.Second_UI.show_new_prompt.emit()
dialog()
This should follow the progression as you described and will keep the last window displayed at the end.
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)
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()
I have a glade gui, and I want to insert another object using a glade file as well.
When I do it as bellow (this is essentially what I am doing) the whole app hangs and the self.show() and maxes out the CPU at 100%. If I replace the first line of one's init() with self.builder = gtk.Builder() then the app runs, I can set widgets, ie: set contents of entry's, set and change the values of comboboxes. But I cant respond to signals, button clicks never call the handler.
In the real code the object two is set as a page in a note book, and I have multiple other pages, the gtk.main() is in the object that owns the notebook. All these work as expected, it's just the object one that fails.
Any clues? I have tried calling self.builder.connect_signals() for every widget but it still fails to notice them.
class one(gtk.VBox):
def __init__(self, builder):
gtk.VBox.__init__(self)
self.builder = builder # if this is self.builder = gtk.Builder() app runs but widget signals go missing.
self.builder.add_from_file("ui_for_one.glade")
self.show() # Endless loop here?
class two(object): # This is the page in a notebook.
def __init__(self):
self.builder = gtk.Builder()
self.builder.add_from_file("ui_for_two.glade")
self.some_container = self.builder.get_object("some_container")
self.one = one(self.builder)
self.some_container.pack_start(self.one, False, False)
Is there a good reason for using the same gtk.Builder object in two classes?
This might be the cause of your problem. In your one class, you load a glade file but you never do anything with its widgets. Something like this should work:
class one(gtk.VBox):
def __init__(self):
gtk.VBox.__init__(self)
self.builder = gtk.Builder()
self.builder.add_from_file("ui_for_one.glade")
some_widget = self.builder.get_object("some_widget")
self.add(some_widget)
self.builder.connect_signals(self)
# No reason to call self.show() here, that should be done manually.
#Your callback functions here
class two(object): # This is the page in a notebook.
def __init__(self):
self.builder = gtk.Builder()
self.builder.add_from_file("ui_for_two.glade")
self.some_container = self.builder.get_object("some_container")
self.one = one()
self.some_container.pack_start(self.one, False, False)
self.some_container.show_all() #recursively show some_container and all its child widgets
self.builder.connect_signals(self)
For more info, check out these Glade tutorials.