PyGTK: Allow usage of Tab in AccelGroup - python

When using gtk.AccelGroup any combination with Tab charater is invalid. Now I do understand that UI navigation is done using this key but in some special cases I need to override that behavior. Is there a way to make AccelGroup allow usage of this key?
For example:
group = gtk.AccelGroup()
group.connect(gtk.gdk.keyval_from_name('Tab'), gtk.gdk.CONTROL_MASK, 0, callback)

You can easily get key names and values with this :
#!/usr/bin/env python
import gtk
import gtk
def catch_button(window, event, label):
keyval = event.keyval
name = gtk.gdk.keyval_name(keyval)
mod = gtk.accelerator_get_label(keyval, event.state)
label.set_markup('<span size="xx-large">%s\n%d</span>'% (mod, keyval))
window = gtk.Window()
window.set_size_request(640,480)
label = gtk.Label()
label.set_use_markup(True)
window.connect('key-press-event',catch_button, label)
window.connect('destroy', gtk.main_quit)
window.add(label)
window.show_all()
gtk.main()
But I found that the keynames returned were locale-dependent, of no great use for me. The keyval can probably be used.
Cheers,
Louis

This below is one way to do it. Although if you don't wish for the program to listen for every keypress as you stated above, I should say that I've never run across a way of tying Tab to an AccelGroup. I've tried various things myself, but to no avail.
widget.connect("key-press-event",self.on_key_pressed)
def on_key_pressed(self,widget,event,*args):
if event.keyval == gtk.keysyms.Tab:
do_something()

Related

How do I make a GTK 2 list with multiple selection?

In a GTK 3 release, the ListBox widget added supported for multiple elements being selected from the list:
I'd like to achieve the same effect with GTK 2. I'm considering using a ScrolledWindow with a VBox of CheckButton's. I fear it's not going to look very good though; like this, but with a scrollbar:
Does anyone know a good way to emulate the functionality in the first image using GTK 2?
It turns out there was a method of doing just this buried in the documentation! In fact, you should find it all the way back to GTK 2.0, but the selection constant might have had a different name (SELECTION_MULTI).
The widget looks like this:
The color scheme is inherited from my GNOME theme, so don't mind the window styling too much. This widget works with both Ctrl and Shift keys. It doesn't automatically have multiple selection just clicking on the different items.
Here's the (Python 2) MWE I made for this:
import gtk
class UI(gtk.Window):
def __init__(self):
gtk.Window.__init__(self)
self.set_position(gtk.WIN_POS_CENTER)
self.set_default_size(250, 150)
store = gtk.ListStore(str)
for i in range(7):
# The list notation here is odd, but required.
store.append(["Item %d" % i])
tree_view = gtk.TreeView()
tree_view.set_model(store)
# The view can support a whole tree, but we want just a list.
renderer = gtk.CellRendererText()
column = gtk.TreeViewColumn("Multiple Selection", renderer, text=0)
tree_view.append_column(column)
# This is the part that enables multiple selection.
tree_view.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
scrolled_tree = gtk.ScrolledWindow()
scrolled_tree.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
scrolled_tree.add_with_viewport(tree_view)
self.add(scrolled_tree)
def main():
win = UI()
win.connect("destroy", gtk.main_quit)
win.show_all()
gtk.main()
if __name__ == "__main__":
main()

How to switch frames in tkinter using keyboard events

I have been using the following example to create a tkinter GUI which can switch between different frames.
Switch between two frames in tkinter
I would like to add a keyboard short cut to switch between the frames. E.g pressing '1' on the keyboard switches to page one in the example. I'm not sure how to use the existing structure to add in this new feature. I've read the effbot.org documentation on events and bindings but I'm confused how their examples can be applied to this particular case. Any help would be greatly appreciated, thanks.
One can use bind method to attach keyboard events to call methods such as show_frame(given that it selects the pages in a list: "StartPage", "PageOne", "PageTwo":
def on_key_release(event):
key_mapping = {'0':"StartPage", '1':"PageOne", '2':"PageTwo"}
key_released = event.keysym
if key_released in key_mapping:
app.show_frame(key_mapping[key_released])
if __name__ == "__main__":
app = SampleApp()
app.bind('<KeyRelease>', on_key_release)
app.mainloop()

How can I make this Tkinter text widget read only? [duplicate]

It doesn't look like it has that attribute, but it'd be really useful to me.
You have to change the state of the Text widget from NORMAL to DISABLED after entering text.insert() or text.bind() :
text.config(state=DISABLED)
text = Text(app, state='disabled', width=44, height=5)
Before and after inserting, change the state, otherwise it won't update
text.configure(state='normal')
text.insert('end', 'Some Text')
text.configure(state='disabled')
Very easy solution is just to bind any key press to a function that returns "break" like so:
import Tkinter
root = Tkinter.Tk()
readonly = Tkinter.Text(root)
readonly.bind("<Key>", lambda e: "break")
The tcl wiki describes this problem in detail, and lists three possible solutions:
The Disable/Enable trick described in other answers
Replace the bindings for the insert/delete events
Same as (2), but wrap it up in a separate widget.
(2) or (3) would be preferable, however, the solution isn't obvious. However, a worked solution is available on the unpythonic wiki:
from Tkinter import Text
from idlelib.WidgetRedirector import WidgetRedirector
class ReadOnlyText(Text):
def __init__(self, *args, **kwargs):
Text.__init__(self, *args, **kwargs)
self.redirector = WidgetRedirector(self)
self.insert = self.redirector.register("insert", lambda *args, **kw: "break")
self.delete = self.redirector.register("delete", lambda *args, **kw: "break")
If your use case is really simple, nbro's text.bind('<1>', lambda event: text.focus_set()) code solves the interactivity problem that Craig McQueen sees on OS X but that others don't see on Windows and Linux.
On the other hand, if your readonly data has any contextual structure, at some point you'll probably end up using Tkinter.Text.insert(position, text, taglist) to add it to your readonly Text box window under a tag. You'll do this because you want parts of the data to stand out based on context. Text that's been marked up with tags can be emphasized by calling .Text.tag_config() to change the font or colors, etc. Similarly, text that's been marked up with tags can have interactive bindings attached using .Text.tag_bind(). There's a good example of using these functions here. If a mark_for_paste() function is nice, a mark_for_paste() function that understands the context of your data is probably nicer.
This is how I did it. Making the state disabled at the end disallows the user to edit the text box but making the state normal before the text box is edited is necessary for text to be inserted.
from tkinter import *
text=Text(root)
text.pack()
text.config(state="normal")
text.insert(END, "Text goes here")
text.config(state="disabled")
from Tkinter import *
root = Tk()
text = Text(root)
text.insert(END,"Some Text")
text.configure(state='disabled')
Use this code in windows if you want to disable user edit and allow Ctrl+C for copy on screen text:
def txtEvent(event):
if(event.state==12 and event.keysym=='c' ):
return
else:
return "break"
txt.bind("<Key>", lambda e: txtEvent(e))
If selecting text is not something you need disabling the state is the simplest way to go. In order to support copying you can use an external entity - a Button - to do the job. Whenever the user presses the button the contents of Text will be copied to clipboard. Tk has an in-build support of handling the clipboard (see here) so emulating the behaviour of Ctrl-C is an easy task. If you are building let's say a console where log messages are written you can go further and add an Entry where the user can specify the number of log messages he wants to copy.
Many mentioned you can't copy from the text widget when the state is disabled. For me on Ubuntu Python 3.8.5 the copying issue turned out to be caused by the widget not having focus on Ubuntu (works on Windows).
I have been using the solution with setting the state to disabled and then switching the state, when I need to edit it programmatically using 1) text.config(state=tkinter.NORMAL) 2) editing the text and 3) text.config(state=tkinter.DISABLED).
On windows I was able to copy text from the widget normally, but on Ubuntu it would look like I had selected the text, but I wasn't able to copy it.
After some testing it turned out, that I could copy it as long as the text widget had focus. On Windows the text widget seems to get focus, when you click it regardless of the state, but on Ubuntu clicking the text widget doesn't focus it.
So I fixed this problem by binding the text.focus_set() to the mouse click event "<Button>":
import tkinter
root = tkinter.Tk()
text0 = tkinter.Text(root, state=tkinter.DISABLED)
text0.config(state=tkinter.NORMAL)
text0.insert(1.0, 'You can not copy or edit this text.')
text0.config(state=tkinter.DISABLED)
text0.pack()
text1 = tkinter.Text(root, state=tkinter.DISABLED)
text1.config(state=tkinter.NORMAL)
text1.insert(1.0, 'You can copy, but not edit this text.')
text1.config(state=tkinter.DISABLED)
text1.bind("<Button>", lambda event: text1.focus_set())
text1.pack()
For me at least, that turned out to be a simple but effective solution, hope someone else finds it useful.
Disabling the Text widget is not ideal, since you would then need to re-enable it in order to update it. An easier way is to catch the mouse button and any keystrokes. So:
textWidget.bind("<Button-1>", lambda e: "break")
textWidget.bind("<Key>", lambda e: "break")
seems to do the trick. This is how I disabled my "line numbers" Text widget in a text editor. The first line is the more powerful one. I'm not sure the second is needed, but it makes me feel better having it there. :)
This can also be done in Frames
from tkinter import *
root = Tk()
area = Frame(root)
T = (area, height=5, width=502)
T.pack()
T.insert(1.0, "lorem ipsum")
T.config(state=DISABLED)
area.pack()
root.mainloop()
You could use a Label instead. A Label can be edited programmatically and cannot be edited by the user.

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.

How to use the same widget twice in pygtk?

This doesn't work well:
image_log = gtk.Image()
image_log.set_from_file("test.png")
self.out_button = gtk.Button()
self.out_button.add(image_log)
self.err_button = gtk.Button()
self.err_button.add(image_log)
another_box.pack_start(self.out_button, False)
another_box.pack_start(self.err_button, False)
The problem is, image_log is used twice and GTK doesn't like it. Is there some .copy() method? Or should I just use plain vanilla deepcopy?
EDIT: Looks like there is no default way to clone objects in GTK. Factory will do the trick in this case.
GTK warning:
app/gui.py:248: GtkWarning: gtk_box_pack: assertion `child->parent == NULL' failed
hbox_errlog.pack_start(image_log)
You could use a factory function to reduce code duplication
def make_image_from_file(fname):
im = gtk.Image()
im.set_from_file(fname)
return im
self.out_button.set_image(make_image_from_file(..))
Revisiting
There is a much more natural way. You will like it. In PyGTK 2.12+:
gtk.image_new_from_file(filename)
I had something in the back of my mind telling me this, but I didn't look it up.
http://www.pygtk.org/docs/pygtk/class-gtkimage.html#function-gtk--image-new-from-file
Use
def clone_widget(widget):
widget2=widget.__class__()
for prop in dir(widget):
if prop.startswith("set_") and prop not in ["set_buffer"]:
prop_value=None
try:
prop_value=getattr(widget, prop.replace("set_","get_") )()
except:
try:
prop_value=getattr(widget, prop.replace("set_","") )
except:
continue
if prop_value == None:
continue
try:
getattr(widget2, prop)( prop_value )
except:
pass
return widget2
All this try ... except blocks are there because not all properties could be copied by using
set_prop(get_prop). I haven't tested this for all properties and widgets yet, but it worked well for gtkEntry. Maybe this is slow, but it's nice the use :)
Why not
image_log = gtk.Image()
image_log.set_from_file("test.png")
image_logb = gtk.Image()
image_logb.set_from_file("test.png")
self.out_button = gtk.Button()
self.out_button.add(image_log)
self.err_button = gtk.Button()
self.err_button.add(image_logb)
another_box.pack_start(self.out_button, False)
another_box.pack_start(self.err_button, False)
It is only an extra 2 lines of code, and maybe more efficient than cloning/copying the first image object.
That way you can treat out_button and err_button independently. But it should make sense to use the same gtk.Image() object for both buttons ... it is just an image.
Edit
To avoid duplication (seems like overkill though) you could write a factory for gtk.Image() objects from the same image.
def gtkimage_factory(num_objs, image_file):
i=0
imglist = []
while i<num_objs:
img_ob = gtk.Image()
img_ob.set_from_file(image_file)
imglist.append( img_ob )
i+=1
return imglist
Or something along those lines, you get the idea. But a factory seems like overkill unless you are producing loads of these things and need them independently parented in GTK.
Then...
image_list = gtkimg_factory(2, "test.png")
self.out_button = gtk.Button()
self.out_button.add(image_list[0])
self.err_button = gtk.Button()
self.err_button.add(image_list[1])
another_box.pack_start(self.out_button, False)
another_box.pack_start(self.err_button, False)
Maybe it is something to do with GTK resource management?
all if you want to use the collection of widgets arranged in some fashion, again and again, let's say a box with one entry box and a label(can be complex as hell if you want) and want to use it multiple time in your application like depending upon condition how many similar tabs are needed but obviously working with different data that use composites with glade.
With pygi(gi_composites) python library you can make your own widgets and use them multiple times.
[https://github.com/virtuald/pygi-composite-templates][1]

Categories

Resources