GTK+: How can I add a Gtk.CheckButton to a Gtk.FileChooserDialog? - python

I want to present a folder chooser to users, and allow them to specify whether that folder should be processed recursively. I tried
do_recursion = False
def enable_recurse(widget, data=None):
nonlocal do_recursion
do_recursion = widget.get_active()
choose_file_dialog = Gtk.FileChooserDialog(use_header_bar=use_header_bar,
title=_(da_title), # _( invokes GLib.dgettext
action=Gtk.FileChooserAction.SELECT_FOLDER)
choose_file_dialog.add_button("_Cancel", Gtk.ResponseType.CANCEL)
choose_file_dialog.add_button("_OK", Gtk.ResponseType.OK)
check_box_1 = Gtk.CheckButton("_RECURSE")
check_box_1.connect("toggled", enable_recurse)
choose_file_dialog.add(check_box_1)
But that fails, and generates the warning:
Gtk-WARNING **: 14:03:31.139: Attempting to add a widget with type GtkCheckButton to a GtkFileChooserDialog, but as a GtkBin subclass a GtkFileChooserDialog can only contain one widget at a time; it already contains a widget of type GtkBox
What is a correct way to do this?

As noted above, an answer is to use set_extra_widget instead of add
check_box_1 = Gtk.CheckButton(label="Recurse source directory")
check_box_1.connect("toggled", enable_recurse)
choose_file_dialog.set_extra_widget(check_box_1)
But I do not like the placement of the checkbox in the lower left corner, so I hope someone has a better answer.

Related

Panel + Param: FileInput widget and #param.depends interaction

I can't seem to figure out the syntax for triggering a function upon someone using a FileInput widget in a Parameterized class.
I understand that FileInput isn't a param itself, but I looked at the code for it and the value attribute is a generic param.Parameter, so I thought this would work. I also tried just depending on file (#param.depends('file')).
class MyFile(param.Parameterized):
file = pn.widgets.FileInput() # should be a param.?
file_data = None
#param.depends('file.value')
def process_file(self):
print('processing file')
self.file_data = self.file.value
my_file = MyFile()
Then after using the file widget, I would expect my_file.file_data to have the same contents of self.file.value.
panel_output
Appreciate any input or if anyone can point me to appropriate docs. Thanks!
https://github.com/pyviz/panel/issues/711
You are right, in this case your 'file' variable needs to be a param, not a panel widget.
All possible options there are for setting available params are here:
https://param.pyviz.org/Reference_Manual/param.html
So in your case I used param.FileSelector():
import param
import panel as pn
pn.extension()
class MyFile(param.Parameterized):
file = param.FileSelector() # changed this into a param
file_data = None
#param.depends('file', watch=True) # removed .value and added watch=True
def process_file(self):
print('processing file')
self.file_data = self.file # removed .value since this is a param so it's not needed
my_file = MyFile()
This FileSelector is however a box to type the filename yourself. This question is related to this and gives some more explanation: Get a different (non default) widget when using param in parameterized class (holoviz param panel) So you need to change this FileSelector still to the FileInput widget, by overwriting it like this:
pn.Param(
my_file.param['file'],
widgets={'file': pn.widgets.FileInput}
)
Please note that I also added watch=True. This makes sure that changes get picked up, when your 'file' param has changes. There's a bit more explanation of this in the following question: How do i automatically update a dropdown selection widget when another selection widget is changed? (Python panel pyviz)
Can you let me know if this helped?

How can I make a tkinter text widget unselectable?

I want to make my tkinter Text to be only an output and not an input. Through some research I've found that text.config(state="disabled") disables user input, but it still allows for selecting text, which I do not want.
How can I get my Text widget to be unselectable and unwritable?
The simplest way is to replace the default text bindings that support selection so that they do nothing. There are a couple ways to do this: using binding tags you can remove all default bindings, or you can remove the bindings to only a subset of default bindings.
Removing all default bindings
All bindings on widgets -- including the default bindings -- are associated with binding tags (also called "bindtags"). The binding tag for the the text widget is "Text", and all default bindings for the text widget are associated with this tag. If you remove that binding tag, you remove all Text-specific bindings.
The default binding tags for any widget is a tuple of the string representation of the widget, the internal widget class (in this case, "Text"), the internal name of the toplevel window (in this case, root), and the special tag "all".
In the following example we change the binding tags so that "Text" is not included, effectively removing all default bindings on the text widget:
import tkinter as tk
root = tk.Tk()
text = tk.Text(root)
text.bindtags((str(text), str(root), "all"))
Removing specific bindings
If you prefer to keep some of the default bindings, you can replace just the ones that you don't want. You do that by creating your own bindings, and having those bindings return the string "break". This special return value tells tkinter to stop processing the event any further.
For example, to prevent a double-click from selecting the word under the cursor you could do this:
text.bind("<Double-1>", lambda event: "break")
The downside to this approach is that you have to figure out what all of the bindings are that are related to the selection mechanism. On the other hand, it gives you complete control over what each key or button press does.
A read-only, unselectable text widget.
class Textarea(tkinter.Text):
def __init__(self, master, **kw):
super().__init__(master, **kw)
# disable text alteration
self.configure(state="disabled")
# untag any selection from beginning to end
def unselect(event):
self.tag_remove("sel", "1.0", "end")
# catch different ways selections could be made and unselect before copying or cutting
good = ["<ButtonRelease-1>", "<Leave>", "<Control-c>", "<Control-C>", "<Control-x>", "<Control-X>"]
better = good + ["<Shift-Left>", "<Shift-Up>", "<Shift-Right>", "<Shift-Down>", "<Shift-Home>", "<Shift-End>", "<Shift-Next>", "<Shift-Prior>"]
excessive = better + ["<Shift-KP_1>", "<Shift-KP_2>", "<Shift-KP_3>", "<Shift-KP_4>", "<Shift-KP_6>", "<Shift-KP_7>", "<Shift-KP_8>", "<Shift-KP_9>"]
for sequence in better:
self.bind(sequence, unselect, add="+")
# remove the appearance of selection
self.configure(selectforeground=self.cget("foreground"), selectbackground=self.cget("background"))
# disallow export of selection in case anything gets through
self.configure(exportselection=False)
Tested on python 3.8.2
I believe you will have to replace it with another widget that such as a Label or LabelFrame to accomplish this. As well you could use a from tkinter import messagebox and have the text you want pop up in another window (like an info window or error message window). I think that as far as the Text widget goes, setting the state to disabled is the best you can do for your purposes unfortunately and users will be able to copy that text despite being unable to edit it.
Here is the simplest method to prevent text from being selected/highlighted when you just want the Text widget to be an ordinary log that is disabled and unselectable.
When I had the issue I figured I just needed to set some Text configuration property (highlightbackground, highlightcolor or selectbackground) to "Black". Nothing worked. The Text widget employs tags that can be used to mark up the Text within the control. Configuration for user defined tags as well as the special tag "sel" have a number of settings including foreground (color) and background (color).
tag_config("sel", background="black")
Too easy right? That doesn't work either.
Turns out that the highlight is actually a bitmap overlaid on the text. This is controlled by the bgstipple (bitmap) configuration for the tag. The documentation indicates that there are a number of system bitmaps (shades of grey) that can be used however it is also possible to specify your own. The bitmap needs to be an xbm and it's easy to create your own as it's a text file.
Put the following in a file named transparent.xbm.
#define trans_width 2
#define trans_height 2
static unsigned char trans_bits[] = {
0x00, 0x00
};
Here it is...
class TextLog(tk.Text):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.tag_config("sel", bgstipple="#transparent.xbm",
foreground="white")
self.config(state="disabled")
def write_log(self, text="", clear=False)
self.configure(state='normal')
if clear is True:
self.delete("1.0","end")
self.insert(tk.END, text)
self.see(tk.END)
self.config(state="disabled")
Depending on where the .xbm is relative to the module using the TextLog you may need to prefix it with a path "#path/to/transparent.xbm"

Trying to populate a window dynamically in Maya using separate py files to essentially build modules

So I'm trying build a window in Maya, that will have contents that will be populated dynamically. My folder structure is this:
/scripts/modularMenu/ <-- which contains:
init.py
modMenu.py
and a /modules/ folder
in the modules folder I have:
modList.py
mod1.py
mod2.py
mod3.py etc. etc.
In modMenu.py I tell Maya to draw the window, but also run the function that populates it based on the contents of the modules folder, with the goal being to create new modules that are, if tagged correctly, populated in the window.
import maya.cmds as cmds
import sys, os.path
from functools import partial
import modules.modList as mList
def modMenu():
if (cmds.window('modMenu', exists=True)):
cmds.deleteUI('modMenu')
else:
window = cmds.window( 'modMenu', title="Mod Menu v1.1", iconName='mRig', widthHeight=(400, 800))
cmds.scrollLayout(width=400, cr=True)
cmds.columnLayout(adj=True )
#This all needs to be generated Dynamically.
mList.populateWindow()
cmds.showWindow( window )
In modList.py I have a list of categories and a function to populate the window.
typeList = ['Type One', 'Type Two', Type Three']
def populateWindow():
for type in typeList:
cmds.frameLayout(label = type, collapsable = True, borderStyle = 'etchedIn')
cmds.text(label = type, al = 'center', height = 15)
#Need to then go through each module and import the rest of the form here, each form will have to have a tag to determine if it
#needs to go in this Category or not. Perhaps the equivalent of...
#for each mod.py in /modules folder if their tag == type then add
cmds.setParent( '..' )
What I'm trying to figure out next is one, how to safely import the contents of each separate mod1.py, mod2.py, etc etc into this modList.py file and two how to tag each separate mod.py file so its placed within the menu system correctly. Ideally I'd like to include one identical function in each mod.py file and a string that correct tags it, that I could call in the modList.py file, but I'm not sure how to correctly import from those mod files en masse to successfully call that function. Any help would be welcome.
On one level, this is pretty simple. You can always add gui elements to a Maya layout if you have a string reference to it, using the setParent() command to tell Maya where the new stuff goes.
In a case like this you just need to pass the shared layout to a bunch of functions --- it doesn't matter where they come froms -- and have each of them call 'setParent` to make the layout active and add to it. Here's an example of how it would go, using separate functions instead of separate modules -- it would not make a difference if these different functions had different module origins.
def button_section(parent):
cmds.columnLayout(adj=True)
cmds.frameLayout(label='buttons')
cmds.columnLayout(adj=True)
cmds.rowLayout(nc=2, cw = (200,200))
cmds.button(label = 'red', backgroundColor=(1,0.5,0.5), width=100)
cmds.button(label = 'blue', backgroundColor =(0.5, 0.5, 1), width=100)
def text_section(parent):
cmds.separator()
cmds.text(label = 'time:')
cmds.text(label = 'It is currently ' + str(datetime.datetime.now()))
cmds.text(label = 'main layout is ' + parent)
cmds.separator()
def object_section(parent):
cmds.columnLayout(adj=True)
cmds.frameLayout(label = 'scene')
cmds.columnLayout(adj=True, rs = 12, columnAttach = ('both', 8) )
for object in cmds.ls(type='transform'):
select_the_thing = lambda b: cmds.select(object)
cmds.button(label = object, c = select_the_thing)
def create_window(*sections):
window = cmds.window(title = 'example')
main_layout = cmds.columnLayout(adj=True)
for each_section in sections:
cmds.setParent(main_layout)
each_section(main_layout)
cmds.setParent(main_layout)
cmds.columnLayout(adj=1, columnAttach = ('both', 8))
cmds.separator()
cmds.text(label = 'here is a footer')
cmds.showWindow(window)
create_window(button_section, text_section, object_section)
If you're not familiar with the syntax the create_window function with a * takes any number of arguments. In this case it's just taking the three individual section functions. You could, however write it to just take a list of functions. In any case the logic is the same -- just setParent back to the main layout and you'll be able to add new stuff to the layout.
In this example I passed the name of the main layout into each of the different layout functions. This is useful so you can do things like get the width of a layout element that owns you, or work your way up to a higher level recursively.
In general the thing you'll have to watch out for here is designing this so that the different sections really are independent of each other. Things will get complicated fast if a button in section A needs to know the state of a checkbox in section B. However this shows you the basics of how to compose layouts in Maya.
I'd be very careful about trying to populate the menu based on the contents of your module folder -- if you delete a module but don't remember to delete the pyc file that is produced by it, you can end up with a phantom section of UI that you don't expect. It would be better to just organize the code as conventional modules and then have a simple script that asked for the modules explicitly. Then you can know exactly what to expect on a local install.
1 - you will have to use exec() or "import mod, mod.doIt()", but what is "safely" will rely on your checks
2 - Im not sure to understand. Do you want to reorder mod by number ?
if not, I supposed you can do a json to store the order or maybe store some metadata

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)

gtk: trouble modifying TreeView model on CellRendererCombo 'changed' signal

I have a treeview with a CellRendererCombo in a given column. I use the following code to set up the column:
crc = gtk.CellRendererCombo()
crc.set_property('model', comboModel)
crc.set_property('text-column', 0)
crc.set_property('editable', True)
crc.set_property('has_entry', False)
cl = gtk.TreeViewColumn(ctitle, crc, text=i)
def changed(cell, path, newiter):
treeViewModel[path][0] = "HAH"
crc.connect("changed", changed)
treeView.append_column(cl)
treeView is a TreeView, treeViewModel is its model, and comboModel is the model for the combo entry containing just two strings.
If I run the code, then the combo works as expected, except that the first time I select an entry I get the following errors:
c:\python25\lib\site-packages\twisted\internet\gtk2reactor.py:255: Warning: inva
lid unclassed pointer in cast to `GObject'
gtk.main()
c:\python25\lib\site-packages\twisted\internet\gtk2reactor.py:255: Warning: g_ob
ject_notify: assertion `G_IS_OBJECT (object)' failed
gtk.main()
On the second time I get:
c:\python25\lib\site-packages\twisted\internet\gtk2reactor.py:255: Warning: inva
lid uninstantiatable type `<invalid>' in cast to `GObject'
gtk.main()
and on the third time the program crashes. If I change the connect line to:
crc.connect("edited", changed)
...then the code works fine. However, the value only changes after clicking out of the combo box, and I'd rather have it change every time an object is selected. How can I do the latter?
EDIT: I just noticed this in the API docs for pygtk:
Note that as soon as you change the model displayed in the tree view, the tree view will immediately cease the editing operating. This means that you most probably want to refrain from changing the model until the combo cell renderer emits the edited or editing_canceled signal.
It doesn't mention that the code would crash, though. In any case, I'd like it that after clicking an entry in the combobox, editing stops, without having to press ENTER or click somewhere else. How can I accomplish that?
Ending editing of a CellRendererCombo immediately after an item is selected is a two stage process.
In the first stage, you must capture the combo itself, since it is not accessible later. To capture the combo, connect to the editing-started CellRenderer signal. You may define the connection in Glade or create it manually in code.
In the second stage, emit a focus-out-event in a changed signal handler for CellRendererCombo.
Here is your original code modified to demonstrate:
comboEditable = None
crc = gtk.CellRendererCombo()
crc.set_property('model', comboModel)
crc.set_property('text-column', 0)
crc.set_property('editable', True)
crc.set_property('has_entry', False)
cl = gtk.TreeViewColumn(ctitle, crc, text=i)
def changed(cell, path, newiter):
treeViewModel[path][0] = "HAH"
e = gtk.gdk.Event(gtk.gdk.FOCUS_CHANGE)
e.window = treeView.window
e.send_event = True
e.in_ = False
comboEditable.emit('focus-out-event', e)
def started(cell, editable, path):
# Or to make life more predictable, use a class and set self.comboEditable
global comboEditable
comboEditable = editable
crc.connect('changed', changed)
crc.connect('editing-started', started)
treeView.append_column(cl)
Note that in more recent versions of GTK+, you don't ordinarily modify the TreeModel in the changed signal handler. You should use the edited signal handler.
Here is the final version:
comboEditable = None
crc = gtk.CellRendererCombo()
crc.set_property('model', comboModel)
crc.set_property('text-column', 0)
crc.set_property('editable', True)
crc.set_property('has_entry', False)
cl = gtk.TreeViewColumn(ctitle, crc, text=i)
def changed(cell, path, newiter):
e = gtk.gdk.Event(gtk.gdk.FOCUS_CHANGE)
e.window = treeView.window
e.send_event = True
e.in_ = False
comboEditable.emit('focus-out-event', e)
def started(cell, editable, path):
# Or to make life more predictable, use a class and set self.comboEditable
global comboEditable
comboEditable = editable
def edited(cell, path, newtext):
treeViewModel[path][columnNumber] = newText
crc.connect('changed', changed)
crc.connect('editing-started', started)
crc.connect('edited', edited)
treeView.append_column(cl)
i'm not sure, but i guess the fastest way to get an answer is to search the pygtk mailing list, and if you cant find a similar post, try posting it to the mailing list.
pygtk mailing list

Categories

Resources