PyGTK: adding text over widgets - python

I'm developing a GTK app, and would like to print some messages over existing widgets rather than displaying them in the status bar, kind of like the way Mendeley does it when no document is selected:
(as opposed to what is displayed in the right pane when you select a document:)
Should I dynamically create a panel, label, ... with the appropriate message and destroy it when needed, or is there a simpler / better way?

You don't need to destroy the label, even nothing forces you to do so, neither create it dynamically. You could create it when you need it or glade could do it for you. This is a minimal example but, as you notice, both labels are created only once.
import gtk
labels = []
def changeLabel(widget):
l = p.get_children()[1]
p.remove(l)
nl = labels[l is l1]
p.add2(nl)
w = gtk.Window()
w.connect('destroy', lambda w: gtk.main_quit())
p = gtk.HPaned()
w.add(p)
b = gtk.Button('change label')
b.connect('clicked', changeLabel)
p.add1(b)
l1 = gtk.Label('hello world')
l1.show()
p.add2(l1)
l2 = gtk.Label('ciao mondo')
l2.show()
labels = [l1, l2]
which = 0
w.show_all()
gtk.main()

Related

One checkbox controls other checkboxes in Maya with Python

I am making a UI in Maya with Python. My goal is to make the master checkbox be able to control other checkboxes so that when the master checkbox is checked, all the other checkboxes are checked and vice versa. The function is as same as the Game Exporter/Animation Clips section in Maya. You can add multiple clips and use the master checkbox to control all clips. Does anyone know how to do that with Python?
The example (image) of what I want to achieve:
I've tried to use changeCommand, onCommand, and offCommand to edit other checkboxes, but it didn't work.
The code is like this (clipOn is multiple checkboxes, and masterCheckbox is the master checkbox that can control others):
clipOn = cmds.checkBox(label = '')
masterCheckbox = cmds.checkBox(label = '', onCommand = lambda x: cmds.checkBox(clipOn, editable = True, onCommand = True))
It's late and this is untested, but something like the following should work. Maya's 'onCommand'/ 'offCommand' parameters require a string or command to be returned. I used enumerate with list comprehension so that it should return a string for each value as it loops through and edits the checkboxes (rather than a single list). Of course you can always just call a regular function, which is arguably an easier and more straightforward approach.
master = cmds.checkBox(label='All')
check1 = cmds.checkBox(label='One')
check2 = cmds.checkBox(label='Two')
check3 = cmds.checkBox(label='Three')
checkAll = lambda state: [cmds.checkBox(c, edit=1, value=state) for i, c in enumerate([check1,check2,check3])][i]
cmds.checkBox(master, edit=1, onCommand=checkAll(True), offCommand=checkAll(False))
I would have thought:
checkAll = lambda state: cmds.checkBox([check1, check2, check3], edit=1, value=state)
would have worked. But it appears the checkBox command doesn't allow you to pass in multiple objects.
By the way, if you want to edit the 'onCommand' cmds.checkBox(clipOn, editable = True, onCommand = True)), you would pass a new command in not a bool value. Lastly, there is a 'checkBoxGrp' MEL command that allows creating and editing groups of checkboxes, however ultimately, I don't think it does anything different then what you can accomplish with the standard 'checkBox' command.
I would advise something like this :
from functools import partial
clipList = []
def addClip(*args):
newClip = cmds.checkBox(label='clip', p=mainLayout)
global clipList
clipList.append(newClip)
def setAllClips(ckb_master, *args):
value = cmds.checkBox(ckb_master, q=True, v=True)
if clipList:
for clip in clipList:
cmds.checkBox(clip, e=True, v=value)
mainLayout = cmds.columnLayout()
master = cmds.checkBox(label='All', p=mainLayout, cc=partial(setAllClips, master))
addClip()

Maya window adding a row dynamically with python

I've been working on this for a while and I can't find any information about adding a row to a window. I seen it done with pyside2 and qt, witch would work but the users are using multiple versions of Maya (2016 = pyside, 2017=pyside2).
I want it like adding a widget in in pyside. I done it where adding a row is a function like add row 1, add row 2, and add row 3 but the script get to long. I need to parent to rowColumnLayout and make that unique in order to delete that later. Also I have to query the textfield in each row. Maybe a for loop that adds a number to the row? I really don't know but this is what I have so far:
from maya import cmds
def row( ):
global fed
global info
item=cmds.optionMenu(mygroup, q=True, sl=True)
if item == 1:
cam=cmds.optionMenu(mygroup, q=True, v=True)
fed=cmds.rowColumnLayout(nc = 1)
cmds.rowLayout(nc=7)
cmds.text(l= cam )
cmds.text(l=u'Frame Range ')
start = cmds.textField('textField3')
cmds.text(l=u' to ')
finish = cmds.textField('textField2')
cmds.button(l=u'render',c='renderTedd()')
cmds.button(l=u'delete',c='deleteRow()')
cmds.setParent (fed)
def deleteRow ():
cmds.deleteUI(fed, layout=True)
if item == 2:
print item
global red
cam1=cmds.optionMenu(mygroup, q=True, v=True)
red = cmds.rowColumnLayout()
cmds.rowLayout(nc=7)
cmds.text(l= cam1 )
cmds.text(l=u'Frame Range ')
start = cmds.textField('textField3')
cmds.text(l=u' to ')
finish = cmds.textField('textField2')
cmds.button(l=u'render',c='renderTedd()')
cmds.button(l=u'delete',c='deleteRow2()')
cmds.setParent (red)
def deleteRow2 ():
cmds.deleteUI(red, control=True)
def cameraInfo():
info=cmds.optionMenu(mygroup, q=True, sl=True)
print info
def deleteRow ():
cmds.deleteUI(fed, control=True)
def getCamera():
layers=pm.ls(type="renderLayer")
for layer in layers:
pm.editRenderLayerGlobals(currentRenderLayer=layer)
cameras=pm.ls(type='camera')
for cam in cameras:
if pm.getAttr(str(cam) + ".renderable"):
relatives=pm.listRelatives(cam, parent=1)
cam=relatives[0]
cmds.menuItem(p=mygroup,label=str (cam) )
window = cmds.window()
cmds.rowColumnLayout(nr=10)
mygroup = cmds.optionMenu( label='Colors', changeCommand='cameraInfo()' )
getCamera()
cmds.button(l=u'create camera',aop=1,c='row ()')
cmds.showWindow( window )
This is totally doable with cmds. The trick is just to structure the code so that the buttons in each row know and can operate on the widgets in that row; once that works you can add rows all day long.
To make it work you want to do two things:
Don't use the string form of callbacks. It's never a good idea, for reasons detailed here
Do use closures to make sure your callbacks are referring to the right widgets. Done right you can do what you want without the overhead of a class.
Basically, this adds up to making a function which generates both the gui items for the row and also generates the callback functions -- the creator function will 'remember' the widgets and the callbacks it creates will have access to the widgets. Here's a minimal example:
def row_test():
window = cmds.window(title='lotsa rows')
column = cmds.columnLayout()
def add_row(cameraname) :
cmds.setParent(column)
this_row = cmds.rowLayout(nc=6, cw6 = (72, 72, 72, 72, 48, 48) )
cmds.text(l= cameraname )
cmds.text(l=u'Frame Range')
start = cmds.intField()
finish = cmds.intField()
# note: buttons always fire a useless
# argument; the _ here just ignores
# that in both of these callback functions
def do_delete(_):
cmds.deleteUI(this_row)
def do_render(_):
startframe = cmds.intField(start, q=True, v=True)
endframe = cmds.intField(finish, q=True, v=True)
print "rendering ", cameraname, "frames", startframe, endframe
cmds.button(l=u'render',c=do_render)
cmds.button(l=u'delete',c=do_delete)
for cam in cmds.ls(type='camera'):
add_row(cam)
cmds.showWindow(window)
row_test()
By defining the callback functions inside of add_row(), they have access to the widgets which get stored as start and finish. Even though start and finish will be created over and over each time the function runs, the values they store are captured by the closures and are still available when you click a button. They also inherit the value of cameraname so the rendering script can get that information as well.
At the risk of self-advertising: if you need to do serious GUI work using cmds you should check out mGui -- a python module that makes working with cmds gui less painful for complex projects.

Populate window in Maya with icons and dynamically adjust depending on window size

I want to create a window in maya, that gets populated with icons from a specific path. I know how to do that, but I also want the icons to adjust dynamically as I change the size of the window.
For example, let's say I have this:
enter image description here
and I want when I resize to get this:
enter image description here
here is a bit of the code I have :
import maya.cmds as cmds
import os
from os import listdir
def UI(*args):
if cmds.window("Test", exists = True):
cmds.deleteUI("Test")
testwindow = cmds.window("Test", t="Test Window", sizeable = 1)
cmds.scrollLayout('srcoll', p = "Test")
cmds.rowColumnLayout("ColLayout", p = "Test", nc = 3)#I imagine the "nc" command is probably useless here, I am just leaving it for testing purposes
cmds.showWindow("Test")
customPath = "C:\Users\$username$\Desktop"
customPathItems = listdir(customPath)
def popUI(*args):
for item in customPathItems:
if item.endswith("_icon.png"):
cmds.iconTextButton(l = item, p = "ColLayout", i = customPath + "/" + item, w = 128, h = 128)
def buildUI(*args):
UI()
popUI()
buildUI()
Any help would be appreciated
What you need is called a Flow layout, where the items inside the layout automatically adjust themselves when the widget is resized.
Here's an example from Qt's documentation that you can fully convert over to Python:
https://doc.qt.io/qt-4.8/qt-layouts-flowlayout-flowlayout-cpp.html
You can also google pyqt flow layout for ones already written in Python.

Creating a simple file browser using python and gtkTreeView

I am trying to create a simple file browser using python and GTK3. Inspired by an another question here I was able to make a small working example
#!/usr/bin/python
import os
from gi.repository import Gtk
window = Gtk.Window()
window.connect("delete-event", Gtk.main_quit)
filesystemTreeStore = Gtk.TreeStore(str)
parents = {}
for (path, dirs, files) in os.walk("/home"):
for subdir in dirs:
parents[os.path.join(path, subdir)] = filesystemTreeStore.append(parents.get(path, None), [subdir])
for item in files:
filesystemTreeStore.append(parents.get(path, None), [item])
filesystemTreeView = Gtk.TreeView(filesystemTreeStore)
renderer = Gtk.CellRendererText()
filesystemColumn = Gtk.TreeViewColumn("Title", renderer, text=0)
filesystemTreeView.append_column(filesystemColumn)
window.add(filesystemTreeView)
window.show_all()
Gtk.main()
The code works, but the result feels not much effective. I was able to read and display the whole linux filesystem, but it took a very long time. One reason could be the usage of os.walk.
Another thing is, that such code does not allow opening empty directories.
For this reason I would like to display only the content of the parent directory for which the listing is made and expand the tree gradually as the user is exploring the tree structure.
I was not able to find a solution for this yet using Python and GTK3. There is a similar solution but for Tkinter
i was able to come with a solution. There could be a better solution, but I am quite happy that it is working as I expected. I append "dummy" nodes to make the folders expandable, even if the are ampty. Had to deal with adding and removing the tree content on expanding/collapsing the treeView.
Here is my solution:
#!/usr/bin/python
import os, stat
from gi.repository import Gtk
from gi.repository.GdkPixbuf import Pixbuf
def populateFileSystemTreeStore(treeStore, path, parent=None):
itemCounter = 0
# iterate over the items in the path
for item in os.listdir(path):
# Get the absolute path of the item
itemFullname = os.path.join(path, item)
# Extract metadata from the item
itemMetaData = os.stat(itemFullname)
# Determine if the item is a folder
itemIsFolder = stat.S_ISDIR(itemMetaData.st_mode)
# Generate an icon from the default icon theme
itemIcon = Gtk.IconTheme.get_default().load_icon("folder" if itemIsFolder else "empty", 22, 0)
# Append the item to the TreeStore
currentIter = treeStore.append(parent, [item, itemIcon, itemFullname])
# add dummy if current item was a folder
if itemIsFolder: treeStore.append(currentIter, [None, None, None])
#increment the item counter
itemCounter += 1
# add the dummy node back if nothing was inserted before
if itemCounter < 1: treeStore.append(parent, [None, None, None])
def onRowExpanded(treeView, treeIter, treePath):
# get the associated model
treeStore = treeView.get_model()
# get the full path of the position
newPath = treeStore.get_value(treeIter, 2)
# populate the subtree on curent position
populateFileSystemTreeStore(treeStore, newPath, treeIter)
# remove the first child (dummy node)
treeStore.remove(treeStore.iter_children(treeIter))
def onRowCollapsed(treeView, treeIter, treePath):
# get the associated model
treeStore = treeView.get_model()
# get the iterator of the first child
currentChildIter = treeStore.iter_children(treeIter)
# loop as long as some childern exist
while currentChildIter:
# remove the first child
treeStore.remove(currentChildIter)
# refresh the iterator of the next child
currentChildIter = treeStore.iter_children(treeIter)
# append dummy node
treeStore.append(treeIter, [None, None, None])
window = Gtk.Window()
window.connect("delete-event", Gtk.main_quit)
# initialize the filesystem treestore
fileSystemTreeStore = Gtk.TreeStore(str, Pixbuf, str)
# populate the tree store
populateFileSystemTreeStore(fileSystemTreeStore, '/home')
# initialize the TreeView
fileSystemTreeView = Gtk.TreeView(fileSystemTreeStore)
# Create a TreeViewColumn
treeViewCol = Gtk.TreeViewColumn("File")
# Create a column cell to display text
colCellText = Gtk.CellRendererText()
# Create a column cell to display an image
colCellImg = Gtk.CellRendererPixbuf()
# Add the cells to the column
treeViewCol.pack_start(colCellImg, False)
treeViewCol.pack_start(colCellText, True)
# Bind the text cell to column 0 of the tree's model
treeViewCol.add_attribute(colCellText, "text", 0)
# Bind the image cell to column 1 of the tree's model
treeViewCol.add_attribute(colCellImg, "pixbuf", 1)
# Append the columns to the TreeView
fileSystemTreeView.append_column(treeViewCol)
# add "on expand" callback
fileSystemTreeView.connect("row-expanded", onRowExpanded)
# add "on collapse" callback
fileSystemTreeView.connect("row-collapsed", onRowCollapsed)
scrollView = Gtk.ScrolledWindow()
scrollView.add(fileSystemTreeView)
# append the scrollView to the window (this)
window.add(scrollView)
window.connect("delete-event", Gtk.main_quit)
window.show_all()
Gtk.main()
What you need is commonly called lazy loading, which is currently not supported by/on the ideas page of GtkTreeStore but you can still create your own YourTreeStoreLazy which implements the GtkTreeModel interface. This was done a couple of times in the past but I can not seem to find any reasonable code examples. Have a look at this post and its comments(link gone)wayback archive copy for some ideas on how to approach the implementation of getters.

Group Gtk.RadioToolButtons?

I've been trying for days to find a way to group RadioToolButtons in pygobject without success. There is no *.RadioToolButton.join_group(*) method like RadioButtons.
Here is what I've been trying:
## Toolbar
self.mainWindow.mainBox.mainToolbar = Gtk.Toolbar()
self.mainWindow.mainBox.mainToolbar.get_style_context().add_class(Gtk.STYLE_CLASS_PRIMARY_TOOLBAR)
self.mainWindow.mainBox.mainToolbar.set_style(Gtk.ToolbarStyle.BOTH)
self.mainWindow.mainBox.mainToolbar.radioGroup = list() # *.radioGroup = [] Does not work either.
## Left toolbar separator
self.mainWindow.mainBox.mainToolbar.leftSeparator = Gtk.SeparatorToolItem(draw = False)
self.mainWindow.mainBox.mainToolbar.leftSeparator.set_expand(True)
## Overview toggle button
self.mainWindow.mainBox.mainToolbar.overviewRadio = Gtk.RadioToolButton(Gtk.STOCK_HOME)
self.mainWindow.mainBox.mainToolbar.overviewRadio.set_group(self.mainWindow.mainBox.mainToolbar.radioGroup)
self.mainWindow.mainBox.mainToolbar.overviewRadio.set_is_important(True)
self.mainWindow.mainBox.mainToolbar.overviewRadio.set_label("Overview")
self.mainWindow.mainBox.mainToolbar.overviewRadio.connect("clicked", self.on_overviewRadio_clicked)
self.mainWindow.mainBox.mainToolbar.overviewRadio.set_border_width(4)
## Basic settings toggle button
self.mainWindow.mainBox.mainToolbar.basicRadio = RadioToolButton(Gtk.STOCK_PROPERTIES)
self.mainWindow.mainBox.mainToolbar.basicRadio.set_group(self.mainWindow.mainBox.mainToolbar.radioGroup)
self.mainWindow.mainBox.mainToolbar.basicRadio.set_is_important(True)
self.mainWindow.mainBox.mainToolbar.basicRadio.set_label("Basic")
self.mainWindow.mainBox.mainToolbar.basicRadio.connect("clicked", self.on_basicRadio_clicked)
self.mainWindow.mainBox.mainToolbar.basicRadio.set_border_width(4)
## Right toolbar separator
self.mainWindow.mainBox.mainToolbar.rightSeparator = Gtk.SeparatorToolItem(
draw = False)
self.mainWindow.mainBox.mainToolbar.rightSeparator.set_expand(True)
(Not all of my code - *.show_all() is not the issue)
Here is what I get:
What am I doing wrong? How can I group these two buttons?
Create the second radio button so it's in the first radio button's group using:
Gtk.RadioToolButton.new_with_stock_from_widget(first_button, Gtk.STOCK_PROPERTIES)
PS. Looks like the UI task you are trying to accomplish might be better done with a Notebook?

Categories

Resources