PyGtk - Activating a combo box - python

If I have a combo box in pyGTK and would like to set a list of strings and then on clicking on one activate a command how would I do it?
At the moment I have:
self.combo_key = gtk.Combo()
self.combo_key.set_popdown_strings(self.keys)
self.combo_key.entry.set_text(db.keys()[0])
self.combo_key.entry.connect("activate", self.key_sel)
But "activate" only calls after selection, and then by pressing enter. I'm also getting a deprecation warning for gtk.Combo() but cannot find any help on using gtk.ComboBoxEntry()
Any help guys?

Try using a gtk.ComboBox instead of gtk.Combo, since the latter is deprecated in favor of the former. To initialise, you can you code like:
liststore = gtk.ListStore(gobject.TYPE_STRING)
for key in self.keys:
liststore.append((key,))
combobox = gtk.ComboBox(liststore)
cell = gtk.CellRendererText()
combobox.pack_start(cell, True)
combobox.add_attribute(cell, 'text', 0)
Now you connect to the changed signal of the combobox and use its get_active() method to ask for the item that was selected.
As you might guess from this explanation, the ComboBox isn't exactly made for this purpose. You probably want to use gtk.Menu.

Related

Trying to check multiple qt radio buttons with python

I need to check multiple radio buttons from a qt ui with python.
Up to now we are using something similar to:
if main.ui.radioButton_1.isChecked():
responses["q1"] = "1"
elif main.ui.radioButton_2.isChecked():
responses["q1"] = "2"
elif main.ui.radioButton_3.isChecked():
responses["q1"] = "3"
if main.ui.radioButton_4.isChecked():
responses["q2"] = "1"
elif main.ui.radioButton_5.isChecked():
responses["q2"] = "2"
elif main.ui.radioButton_6.isChecked():
responses["q2"] = "3"
...
Since there are very many buttons and many different categories (q1, q2, ...) I was thinking of optimizing it a bit. So this is what I hoped would work (adopted from How to get the checked radiobutton from a groupbox in pyqt):
for i, button in enumerate(["main.ui.radioButton_" + str(1) for i in range(1, 8)]):
if button.isChecked():
responses["q1"] = str(i - 1)
I get why this doesn't work but writing it I hoped it would.
So I tried to iterate through the buttons using something similar to (Is there a way to loop through and execute all of the functions in a Python class?):
for idx, name, val in enumerate(main.ui.__dict__.iteritems()):
and then use some modulo 3 and such to assign the results. But that doesn't work either. Not sure if it's because i used __ dict __ or something else. The error I got was:
TypeError: 'QLabel' object is not iterable
Now some people could say that implicit is better that explicit and also because of readability the if elif chain is good the way it is but there are 400+ lines of that. Also after reading this post, Most efficient way of making an if-elif-elif-else statement when the else is done the most?, I thought there must be a better and more efficient way of doing this (see examples 3.py and 4.py of the of the accepted answer). Because I need to check the Boolean value of main.ui.radioButton_1.isChecked() and then assign thevalue according to the Buttons group (q1, q2,...), I haven't managed to implement the solution using dictionaries as described in the post.
Am I stuck with the if elif chain or is there a way to not only reduce the LOC but also make the code more efficient (faster)?
It looks like you have used Qt Designer to create your ui, so I would suggest putting each set of radio buttons in a QButtonGroup. This will give you a simple, ready-made API for getting the checked button in a group without having to query each button individually.
In Qt Designer, buttons can be added to a button-group by selecting them, and then choosing Assign to button group > New button group from the context menu. The button IDs (which you will need to use later) are assigned in the order the buttons are selected. So use Ctrl+Click to select each button of a group in the correct order. The IDs start at 1 for each group and just increase by one for each button that is added to that group.
When a new button-group is added, it will appear in the Object Inspector. This will allow you to select it and give it a more meaningful name.
Once you've created all the groups, you can get the checked button of a group like this:
responses["q1"] = str(main.ui.groupQ1.checkedId())
responses["q2"] = str(main.ui.groupQ2.checkedId())
# etc...
This could be simplified even further to process all the groups in a loop:
for index in range(1, 10):
key = 'q%d' % index
group = 'groupQ%d' % index
responses[key] = str(getattr(main.ui, group).checkedId())
Another way to do it is using signals. If you had lots of radio button in an application, I suspect this kind of approach would be noticeably faster. For example:
import sys
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class MoodExample(QGroupBox):
def __init__(self):
super(MoodExample, self).__init__()
# Create an array of radio buttons
moods = [QRadioButton("Happy"), QRadioButton("Sad"), QRadioButton("Angry")]
# Set a radio button to be checked by default
moods[0].setChecked(True)
# Radio buttons usually are in a vertical layout
button_layout = QVBoxLayout()
# Create a button group for radio buttons
self.mood_button_group = QButtonGroup()
for i in xrange(len(moods)):
# Add each radio button to the button layout
button_layout.addWidget(moods[i])
# Add each radio button to the button group & give it an ID of i
self.mood_button_group.addButton(moods[i], i)
# Connect each radio button to a method to run when it's clicked
self.connect(moods[i], SIGNAL("clicked()"), self.radio_button_clicked)
# Set the layout of the group box to the button layout
self.setLayout(button_layout)
#Print out the ID & text of the checked radio button
def radio_button_clicked(self):
print(self.mood_button_group.checkedId())
print(self.mood_button_group.checkedButton().text())
app = QApplication(sys.argv)
mood_example = MoodExample()
mood_example.show()
sys.exit(app.exec_())
I found more information at:
http://codeprogress.com/python/libraries/pyqt/showPyQTExample.php?index=387&key=QButtonGroupClick
http://www.pythonschool.net/pyqt/radio-button-widget/

PywinAuto and the disappearing control Identifier

I'm using pywinauto (the latest from the new github) to automate logging in to another program. The "Sign in" window has a bunch of buttons, and two fields, one for user name and one for password.
My problem is that the user name and password 'edit' control identifiers have the same Access name: ['1', '0', 'Edit']. There is no "Edit2".
When I use
sign_in.print_control_identifiers()
It still only shows the one edit property. How do I access this other edit control?
update with pictures with the demo company file:
Here is the LOGIN window: http://imgur.com/VwS9w0b
Here is the mouse hovering over password: http://imgur.com/6HWQVlZ
Password field clicked, its called edit1 as well! http://imgur.com/GUnTVrK
Swapy output : http://imgur.com/LJB99y1
A solution I found was to simulate a "tab" key
sign_on.TypeKeys("{TAB}")
But this is not a great solution because if another window were to take focus at the time of the TAB then the script would sent the tab to that window.
I'm not sure which version of Pywinauto you are using. There is a revived one on GitHub (https://github.com/pywinauto/pywinauto). You can access controls as elements of a dictionary:
sign_in['0']
sign_in['1']
Highlighting a GUI element can also help to understand what element you refer to:
sign_in['0'].DrawOutline() # green by default
sign_in['1'].DrawOutline('red') # acceptable keywords: 'red','blue','green'
sign_in['Edit'].DrawOutline(0xff0000) # blue
Update the answer with an example of walking through all control's children and highlight them. This way you can see if you have access to the "password" field.
import time
def drawContours(ctl):
for c in ctl.Children():
drawContours(c)
time.sleep(1)
c.DrawOutline()
ctl.DrawOutline()
drawCountours(sign_in)
Edit0 and Edit1 refers to the same first edit box. It's expected behavior (by design). Edit2 refers to the second edit box, Edit3 to the third one etc. If you get print_control_identifiers() output, you usually see something like that ("Find" dialog in Notepad, for example):
Edit - '' (L152, T160, R323, B180)
'Edit' 'Edit0' 'Edit1' 'Fi&nd what:Edit' ()
Edit - '' (L152, T188, R323, B208)
'Edit2' 'Re&place with:Edit' ()
So possible names for best_match search algorithm are listed for each control. These names are trying to be unique (not overlapping with other controls), but one control has several best names. It's normal situation. sign_in['Edit2'] is probably what you need.
If you disagree with such approach, you may raise design discussion here: https://github.com/pywinauto/pywinauto/issues

How to disable input to a Text widget but allow programmatic input?

How would i go about locking a Text widget so that the user can only select and copy text out of it, but i would still be able to insert text into the Text from a function or similar?
Have you tried simply disabling the text widget?
text_widget.configure(state="disabled")
On some platforms, you also need to add a binding on <1> to give the focus to the widget, otherwise the highlighting for copy doesn't appear:
text_widget.bind("<1>", lambda event: text_widget.focus_set())
If you disable the widget, to insert programatically you simply need to
Change the state of the widget to NORMAL
Insert the text, and then
Change the state back to DISABLED
As long as you don't call update in the middle of that then there's no way for the user to be able to enter anything interactively.
Sorry I'm late to the party but I found this page looking for the same solution as you.
I found that if you "disable" the Text widget by default and then "normal" it at the beginning of a function that gives it input and "disable" it again at the end of the function.
def __init__():
self.output_box = Text(fourth_frame, width=160, height=25, background="black", foreground="white")
self.output_box.configure(state="disabled")
def somefunction():
self.output_box.configure(state="normal")
(some function goes here)
self.output_box.configure(state="disable")
I stumbled upon the state="normal"/state="disabled" solution as well, however then you are unable to select and copy text out of it. Finally I found the solution below from: Is there a way to make the Tkinter text widget read only?, and this solution allows you to select and copy text as well as follow hyperlinks.
import Tkinter
root = Tkinter.Tk()
readonly = Tkinter.Text(root)
readonly.bind("<Key>", lambda e: "break")

How do I add items to a gtk.ComboBox created through glade at runtime?

I'm using Glade 3 to create a GtkBuilder file for a PyGTK app I'm working on. It's for managing bandwidth, so I have a gtk.ComboBox for selecting the network interface to track.
How do I add strings to the ComboBox at runtime? This is what I have so far:
self.tracked_interface = builder.get_object("tracked_interface")
self.iface_list_store = gtk.ListStore(gobject.TYPE_STRING)
self.iface_list_store.append(["hello, "])
self.iface_list_store.append(["world."])
self.tracked_interface.set_model(self.iface_list_store)
self.tracked_interface.set_active(0)
But the ComboBox remains empty. I tried RTFM'ing, but just came away more confused, if anything.
Cheers.
Or you could just create and insert the combo box yourself using gtk.combo_box_new_text(). Then you'll be able to use gtk shortcuts to append, insert, prepend and remove text.
combo = gtk.combo_box_new_text()
combo.append_text('hello')
combo.append_text('world')
combo.set_active(0)
box = builder.get_object('some-box')
box.pack_start(combo, False, False)
Hey, I actually get to answer my own question!
You have to add gtk.CellRendererText into there for it to actually render:
self.iface_list_store = gtk.ListStore(gobject.TYPE_STRING)
self.iface_list_store.append(["hello, "])
self.iface_list_store.append(["world."])
self.tracked_interface.set_model(self.iface_list_store)
self.tracked_interface.set_active(0)
# And here's the new stuff:
cell = gtk.CellRendererText()
self.tracked_interface.pack_start(cell, True)
self.tracked_interface.add_attribute(cell, "text", 0)
Retrieved from, of course, the PyGTK FAQ.
Corrected example thanks to Joe McBride

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