What are the steps to convert from using libglade to GtkBuilder? (Python) - python

I have a small project that uses libglade and use the following to load the xml file:
self.gladefile = "sdm.glade"
self.wTree = gtk.glade.XML(self.gladefile)
self.window = self.wTree.get_widget("MainWindow")
if (self.window):
self.window.connect("destroy", gtk.main_quit)
dic = { "on_button1_clicked" : self.button1_clicked,
"on_MainWindow_destroy" : gtk.main_quit}
self.wTree.signal_autoconnect(dic)
After converting my project in glade, what structural changes do I need to make?
I'm on Ubuntu 9.04.

You need to use gtk.Builder instead. This class can load any number of UI files, so you need to add them manually, either as files or as strings:
self.uifile = "sdm.ui"
self.wTree = gtk.Builder()
self.wTree.add_from_file(self.uifile)
Instead of get_widget, just use get_object on the builder class:
self.window = self.wTree.get_object("MainWindow")
if self.window:
self.window.connect("destroy", gtk.main_quit)
To connect the signals, just use connect_signals, which also takes a dictionary:
dic = { "on_button1_clicked" : self.button1_clicked,
"on_MainWindow_destroy" : gtk.main_quit}
self.wTree.connect_signals(dic)
It used to be the case (at least in GTK+ 2.12, not sure if it's still the same) that you could call connect_signals only once, any signals which are not connected during the first invocation will never be connected. This was different in glade, so be careful if you relied on that feature before.

Torsten's answer is correct, but a little incomplete, so in the spirit of http://xkcd.com/979/ here is the procedure I recently settled on after much trial-and-error:
Open yada.glade in Glade interface designer. Go to edit->project and change the project type to GtkBuilder and make sure it targets the latest version (2.24 as of this writing). Save the file, being sure that it saves in GtkBuilder format, and change the name from yada.glade to yada.ui
Open yada.py and change the following code:
gladefile = relativize_filename(os.path.join("glade", "yada.glade"))
self.wTree = gtk.glade.XML(gladefile, self.windowname)
to:
uifile = relativize_filename(os.path.join("glade", "yada.ui"))
self.wTree = gtk.Builder()
self.wTree.add_from_file(uifile)
Similarly change all instances of self.wTree.get_widget(...) to self.wTree.get_object(...)
Change self.wTree.signal_autoconnect(dic) to self.wTree.connect_signals(dic)
If your code depends on the name assigned the widget in the interface designer, change widget.get_name() to gtk.Buildable.get_name(widget). widget.get_name() now just returns the widget type. EDIT: You also need to change widget.set_name('my_widget') to gtk.Buildable.set_name(widget, 'my_widget').
Delete import gtk.glade
I found numerous unused signals defined in the yada.ui xml file, I had to open the xml file and manually delete them to eliminate the warnings they caused.

Related

How to set initial directory in GTK3 FileChooser in Python?

EDIT: Solution found and shown in code below.
Python noob here. I usually like to find answers myself, but I'm striking out on this one.
I simply want to set the directory the user starts in when FileChooser opens (rather than have the Python working directory).
This seems like such a simple thing and a common need, I am surprised and frustrated that I could not find anything in Glade (which I used to create the FileChooser), nor in any online GTK documentation.
So... how can this be done?
As mentioned I built the xml file with Glade and loaded it with gtk.builder.
Thanks much for any help
CODE WITH (A) SOLUTION
def GetFile():
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk
chosenfile = None
class Handler:
def on_destroy(self, *args):
Gtk.main_quit()
def on_OK_clicked(self, *args):
nonlocal chosenfile
chosenfile = window.get_filename()
Gtk.main_quit()
def on_cancel_clicked(self, *args):
nonlocal chosenfile
chosenfile = "CANCELLED"
Gtk.main_quit()
builder = Gtk.Builder()
builder.add_from_file("mod_FileChoose.glade")
builder.connect_signals(Handler())
window = builder.get_object("FileSelector")
#HERE IS THE LINE IN QUESTION
Gtk.FileChooser.set_current_folder(window, "/home/")
window.show_all()
Gtk.main()
return chosenfile
I should add that in GTK API source where I found this, it states:
"In general, you should not use this function. See the section on setting up a file chooser dialog[gtkfilechooserdialog-setting-up] for the rationale behind this."
So far I have not found the setting-up section with rationale for not using or better approach.
Source: gtk API ref
It seems to be okay to use the set_current_folder method as to the mentioned "setup section" that can be found here:
Note that old versions of the file chooser’s documentation suggested using gtk_file_chooser_set_current_folder() in various situations, with the intention of letting the application suggest a reasonable default folder. This is no longer considered to be a good policy, as now the file chooser is able to make good suggestions on its own. In general, you should only cause the file chooser to show a specific folder when it is appropriate to use gtk_file_chooser_set_filename(), i.e. when you are doing a Save As command and you already have a file saved somewhere.
Addtional info: to look up docs, I use the DevHelp tool on Linux. Most Gtk docs (for python) for it can be found on this github repo. The offical online docs for Gtk3 should be these.

application window title with file path for multiple main windows

I have an application with two main windows. I want both of then to have the standard title which contains the file name and application name. But this works strange because both files show the file name but only the second window shows the application name. The first shows just "x.py" while the second "y.py - My App". Anybody has an idea why is that and how to solve it? Is this a bug or is it expected behaviour?
from qtpy.QtWidgets import QApplication, QMainWindow
app = QApplication([])
app.setApplicationDisplayName("My App")
wnd1 = QMainWindow()
wnd2 = QMainWindow()
wnd1.setWindowFilePath("x.py") # in most cases it shows only "x.py" - this is wrong
wnd2.setWindowFilePath("y.py") # correctly shows "y.py - My App"
wnd1.show()
wnd2.show()
app.exec_()
Tested on Ubuntu 16.04., PyQt 5.8.2.
UPDATE: So I also discovered it behaves non-deterministically. Sometimes both application titles appear correctly. Sometimes only one. This seems like a bug.
As a workaround for this likely bug I am going to override the setWindowFilePath() for my main window classes. This will give me another benefit such as showing the full file path instead of just file name and also indicate that the file is unnamed if it is a new file which has not yet been saved or loaded, which is what I want anyway. It also works well with changing window-modified state. I know I am sacrificing the 100 % 'native' look but... I can live with it.
def setWindowFilePath(self, filePath):
super(MainWindow, self).setWindowFilePath(filePath)
if not filePath:
filePath = "unnamed"
self.setWindowTitle("{}[*] - {}".format(filePath, qApp.applicationDisplayName()))
Maybe somebody will find a better solution.

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)

QUiLoader not loading layouts from .ui file

I have a plugin for an application which provides the ability to use PyQt4 to create widgets which they can use to make their own tools for the application. The QApplication is maintained by a C++ plugin and an 'anchor' widget is parented to the host application's window handle. Then the user can create their own widgets and use this 'anchor' to be the parent widget. This works very well for PyQt4. Recently, I have been attempting to provide support for PySide and this too is working well right up until I need to create widgets from .ui files. It seems that when I use QUiLoader to load my .ui file the my resulting widget doesn't look the same as in PyQt4. It looks as if it is missing or skipping layouts and other properties described in the .ui file, such as the title. Thing is when I log the return value of that load function I get what seems right...
class ExampleUiFile(QDialog):
def __init__(self, parent, uifilepath):
QDialog.__init__(self, parent)
# load ui file
loader = QUiLoader(self)
file = QtCore.QFile(uifilepath)
file.open(QtCore.QFile.ReadOnly)
self.ui = loader.load(file, self)
file.close()
for k,v in vars(self.ui).items():
print("%s : %s" % (k,v))
# connect to the createCube function
self.ui.uiCreateCube.clicked.connect(self.createCube)
def createCube(self):
print(self.ui.uiCubeName.text())
Output..
horizontalLayout : <PySide.QtGui.QHBoxLayout object at 0x0000000023997308>
uiCubeName : <PySide.QtGui.QLineEdit object at 0x0000000023338508>
verticalLayout :<PySide.QtGui.QVBoxLayout object at 0x0000000023997548>
gridLayout : <PySide.QtGui.QGridLayout object at 0x0000000023997E08>
uiCubeLength : <PySide.QtGui.QDoubleSpinBox object at 0x0000000023338808>
uiCreateCube : <PySide.QtGui.QPushButton object at 0x0000000023338988>
So in an attempt to fix this I went digging, here and elsewhere, and found examples of sub-classing QUiLoader. I was able to copy a simple class which inherits QUiLoader which does some extra work to return the initial base class. This guy seemed to have worked and my dialog looks correct, the correct title is shown, layout, and resizing works. Unfortunately, the garbage collector removes all the widgets that my loader class created and I get message about my object being deleted...
class UiLoader(QUiLoader):
def __init__(self, baseinstance):
super(UiLoader, self).__init__(baseinstance)
self._baseinstance = baseinstance
def createWidget(self, classname, parent=None, name=""):
widget = super(UiLoader, self).createWidget(
classname, parent, name)
if parent is None:
return self._baseinstance
else:
setattr(self._baseinstance, name, widget)
return widget
Output using new sub-classed loader...
Internal C++ object (PySide.QtGui.QLineEdit) already deleted.
I have done a lot of digging on this issue and some bug in PySide in the past seemed to be the culprit, but I am using PySide 1.1.2 which has it fixed. I even built PySide from source with 1.1.3dev and still the same. I should add that I wasn't able to reproduce this sub-classing issue outside of the host application. I was able to make a simple python/PySide example that worked as expected.
Where do I go from here? I get a good looking ui without the functionality or I get an ugly ui with the functionality. I would prefer not to sub-class the QUiLoader since I am not doing anything fancy with custom widgets or anything.

Stock Icons not shown on buttons

self.button = gtk.Button(stock=gtk.STOCK_DELETE)
Only Shows:
Delete
The Python equivalent for setting the property without having to change any system config files is:
settings = gtk.settings_get_default()
settings.props.gtk_button_images = True
This should follow a call to window.show() and, obviously, precede the gtk.main() loop.
This is a recent change in GTK - the developers wanted icons not to appear on buttons. On Linux, this can be changed by editing the gconf key
/desktop/gnome/interface/buttons_have_icons
On windows, I think (I haven't actually tried this) that you need to set a value in your gtkrc file (for me it's in C:\Program Files\Gtk+\etc\gtkrc) and use a theme that supports icons (I think the default one doesn't).
You can also add gtk-button-images = 1 to your ~/.gtkrc-2.0 file after setting the theme which may over ride the option from gconf.
EDIT in answer to your comment:
Just like this answer, but in Python: In Gtk, how do I make a Button with just a stock icon?
For python, it's just
image = gtk.Image()
# (from http://www.pygtk.org/docs/pygtk/gtk-stock-items.html)
image.set_from_stock(gtk.STOCK_**)
button = gtk.Button()
button.set_image(image)
button.set_label("")
I had to do this to get it to work from Python without changing my config file. When I called set_image(), no image was being displayed.
image = gtk.Image()
image.set_from_stock(gtk.STOCK_**, gtk.ICON_SIZE_BUTTON)
button = gtk.Button()
button.add(image)
button.show()
If you work with pygobject, the new syntax is:
image.set_from_stock(gtk.STOCK_**, Gtk.IconSize.BUTTON)
I had the same issue in GTKmm on Windows. The "MS-Windows" theme disables images on stock buttons and the theme has priority over settings in gtkrc (so putting gtk-button-images = true in gtkrc didn't help). What I did is to modify the GTK settings runtime, and the images appeared as expected. :) Here's the code in C++:
Glib::RefPtr<Gtk::Settings> settings = Gtk::Settings::get_default();
/* force using icons on stock buttons: */
settings->property_gtk_button_images() = true;
It should be placed after the first window is constructed.
in Gtk3 gtk.STOCK method has been deprecated from v3.10.
Deprecated since version 3.10: Use Gtk.Button.new_with_label ()
instead.
In the case it doesn't help since it points to the custom label solution (new_with_label) If you want to use STOCK stuff you still can do so with new methods Gtk.Button.new_from_icon_name(icon_name, size) and Gtk.Button.new_with_mnemonic(label) which will create new buttons with stock icon and label respectively.
Example new button with a "stock" icon:
button = Gtk.Button.new_from_icon_name ("edit-paste", Gtk.IconSize.SMALL_TOOLBAR)
Example new button with a "stock" label:
button = Gtk.Button.new_with_mnemonic("_Open")
NOTE: on serious code creating a constant variable instead of using the string straight is a better option :)
References:
Gtk.Button
static new_with_mnemonic(label)
new_from_icon_name(icon_name, size)
Freedesktops Naming Convention
You can show explicitly the button image, justly, Gtk+ developers do not recommend doing this because it's overrides the Gtk+ user configuration.
So...
button.get_image().show()

Categories

Resources