If I set the current folder via the method Gtk.FileChooserWidget.set_current_folder(), the first time I open the file chooser, it opens on the location used as argument for set_current_folder()
But, if I select a file, the I re-open the file-chooser, it opens on the "most_recent_used_files".
I'd like it opens on the last selected file's folder path.
How to do it?
Thank you.
From the docs:
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 File/Save As command and you already have a file saved somewhere.
You may or may not like the reasoning for this behavior. If you're curious about how it came about, see File chooser recent-files in the mailing list and Help the user choose a place to put a new file on the GNOME wiki.
Setting the current folder each time works for me, but it is a little tricky. I'm using Gtk 3.14 and Python 2.7.
You have to get the filename before resetting the directory, or it's lost, and the current directory may be None, so you have to check for that.
This code is tested on Debian jessie and Windows 7.
import os.path as osp
from gi.repository import Gtk
class FileDialog(Gtk.FileChooserDialog):
def __init__(self, parent, title):
Gtk.FileChooserDialog.__init__(self, title, parent)
self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
self.add_button(Gtk.STOCK_OPEN, Gtk.ResponseType.OK)
self.set_current_folder(osp.abspath('.'))
def __call__(self):
resp = self.run()
self.hide()
fname = self.get_filename()
d = self.get_current_folder()
if d:
self.set_current_folder(d)
if resp == Gtk.ResponseType.OK:
return fname
else:
return None
class TheApp(Gtk.Window):
def on_clicked(self, w, dlg):
fname = dlg()
print fname if fname else 'canceled'
def __init__(self):
Gtk.Window.__init__(self)
self.connect('delete_event', Gtk.main_quit)
self.set_resizable(False)
dlg = FileDialog(self, 'Your File Dialog, Sir.')
btn = Gtk.Button.new_with_label('click here')
btn.connect('clicked', self.on_clicked, dlg)
self.add(btn)
btn.show()
if __name__ == '__main__':
app = TheApp()
app.show()
Gtk.main()
Related
I'm learning Python while trying to develop helper tools for Maya using Python and PySide. (Quite ambitious but there is no progress without challenges, right)
Basically, I'm writing a tool that would help me export animation as fbx files to a folder I set in any given location on my PC.
I've finished to write the export process. There should be no issue here, I'm stuck with the UI part of it.
Currently this is how my UI is looking. I want to be able to set the path to the place where I want the script to export the files.
When I select the path and press "Select Folder", I want the path of the selected folder to display in the text line of the UI. And "Remember" it so that when I press Export FBX animation or Export rig it would use that path and would save files there.
But I have no clue how to do that. Can anyone help the clueless me how to make this happen?
I would appreciate any help. Thank you )
Here is my current code:
`
import CreatureAnimBakeProcess
reload(CreatureAnimBakeProcess)
from maya import cmds
import os
from PySide2 import QtWidgets, QtCore, QtGui
class CreatureAnimBakeUI(QtWidgets.QDialog):
def __init__(self):
super(CreatureAnimBakeUI, self).__init__()
self.setWindowTitle('Creature Exporter')
self.library = CreatureAnimBakeProcess.CreatureExport()
self.buildUI()
def buildUI(self):
print 'building ui'
layout = QtWidgets.QVBoxLayout(self)
setPathWidget = QtWidgets.QWidget()
setPathLayout = QtWidgets.QVBoxLayout(setPathWidget)
layout.addWidget(setPathWidget)
self.setPathField = QtWidgets.QLineEdit()
setPathLayout.addWidget(self.setPathField)
setBtn = QtWidgets.QPushButton('Set Export Folder')
setBtn.clicked.connect(self.setPath)
setPathLayout.addWidget(setBtn)
#============================================
btnWidget = QtWidgets.QWidget()
btnLayout = QtWidgets.QVBoxLayout(btnWidget)
layout.addWidget(btnWidget)
ExportFBXBtn = QtWidgets.QPushButton('Export FBX Animation')
ExportFBXBtn.clicked.connect(self.exportFbxAnim)
btnLayout.addWidget(ExportFBXBtn)
ExportRIGBtn = QtWidgets.QPushButton('Export RIG')
ExportRIGBtn.clicked.connect(self.exportRIG)
btnLayout.addWidget(ExportRIGBtn)
return
def getDirectory(self):
directory = os.path.join(cmds.internalVar(userAppDir=True), 'Animation')
if not os.path.exists(directory):
os.mkdir(directory)
return
def setPath(self):
directory = self.getDirectory()
pathName = QtWidgets.QFileDialog.getExistingDirectory(self, directory, "Creature Exporter")
return
def exportFbxAnim(self):
pass
def exportRIG(self):
pass
def showUI():
ui = CreatureAnimBakeUI()
ui.show()
return ui
`
You can use self.setPathField.setText(pathName) to show the value, you can also assign it to self.exportPath = pathName so you can re use it.
I am only showing code snippets where I changed, the remainder of your code needs no changes.
def __init__(self):
super(CreatureAnimBakeUI, self).__init__()
# have it assigned to None
self.exportPath = None # changed here
self.setWindowTitle("Creature Exporter")
self.buildUI()
def setPath(self):
directory = self.getDirectory()
pathName = QtWidgets.QFileDialog.getExistingDirectory(
self, directory, "Creature Exporter"
)
if pathName: # changed here
# "remember the value"
self.exportPath = pathName
# show it in the text line of the UI
self.setPathField.setText(pathName)
return
self.exportPath will be None initially, once you select a folder it will have that value saved. So this if checks if the value is not set and forces the user to set a value, if it is already set that value will be used.
def exportFbxAnim(self): # changed here
if self.exportPath is None:
# call self.setPath if self.exportPath is None
self.setPath()
# self.exportPath should have the value you expect
def exportRIG(self): # changed here
if self.exportPath is None:
# call self.setPath if self.exportPath is None
self.setPath()
# self.exportPath should have the value you expect
I am yet to decide what language and tools to use for my next project. I would love to use python, but I would like to implement ribbon toolbars. Some work has been done in Tk (http://www.ellogon.org/petasis/bibliography/Tcl2010/TkRibbon.pdf), but it looks like it hasn't been implemented in tkinter yet. Is there anything I can do to get this to work?
You need to create a wrapper for this and get a version of the binary you can use. I built this for use with Python 3.4 and copied it to tkribbon1.0-x86_64.zip. You should unzip this in the Python/tcl subdirectory so the version of tcl used by python can load it.
A minimal wrapper looks like this:
from tkinter import Widget
from os import path
class Ribbon(Widget):
def __init__(self, master, kw=None):
self.version = master.tk.call('package','require','tkribbon')
self.library = master.tk.eval('set ::tkribbon::library')
Widget.__init__(self, master, 'tkribbon::ribbon', kw=kw)
def load_resource(self, resource_file, resource_name='APPLICATION_RIBBON'):
"""Load the ribbon definition from resources.
Ribbon markup is compiled using the uicc compiler and the resource included
in a dll. Load from the provided file."""
self.tk.call(self._w, 'load_resources', resource_file)
self.tk.call(self._w, 'load_ui', resource_file, resource_name)
if __name__ == '__main__':
import sys
from tkinter import *
def main():
root = Tk()
r = Ribbon(root)
name = 'APPLICATION_RIBBON'
if len(sys.argv) > 1:
resource = sys.argv[1]
if len(sys.argv) > 2:
name = sys.argv[2]
else:
resource = path.join(r.library, 'libtkribbon1.0.dll')
r.load_resource(resource, name)
t = Text(root)
r.grid(sticky=(N,E,S,W))
t.grid(sticky=(N,E,S,W))
root.grid_columnconfigure(0, weight=1)
root.grid_rowconfigure(1, weight=1)
root.mainloop()
main()
Running this uses the resources built-in to the tkribbon dll and looks like . The complicated bit is going to be getting some Ribbon markup resources into a DLL for loading.
You can use this example to load ribbons from existing applications. For instance, python Ribbon.py c:\Windows\System32\mspaint.exe MSPAINT_RIBBON will load up the ribbon resource from mspaint. The resource name in this case has to be included as the default is APPLICATION_RIBBON. For your own ribbon, using uicc to build a .rc file, then rc /r file.rc to produce a .res file and finally link -dll -out:file.dll file.rc -noentry -machine:AMD64 seems to work to produce a resource only DLL that works with this extension.
Looks like there's no built-in option to disable/remove the InputBox/TextCtrl part in wx.lib.filebrowsebutton.FileBrowseButton, well I did come up with a workaround which is simply set labelText to blank and then size it down to fit only the button itself, this way visually you can tell no difference from a normal button, but I don't think it's nice enough to go with.
So is there a way to fully disable/remove the InputBox part? Or maybe a way to bind normal button with file browser function?
If you don't need a textctrl, then you don't really need wx.lib.FileBrowseButton. You can just have a normal wx.Button that launches a wx.FileDialog instance. In fact, that's all that wx.lib.FileBrowsBbutton does. Here's the relevant source code, the whole thing can be viewed here: https://github.com/wxWidgets/wxPython/blob/master/wx/lib/filebrowsebutton.py
def OnBrowse (self, event = None):
""" Going to browse for file... """
current = self.GetValue()
directory = os.path.split(current)
if os.path.isdir( current):
directory = current
current = ''
elif directory and os.path.isdir( directory[0] ):
current = directory[1]
directory = directory [0]
else:
directory = self.startDirectory
current = ''
dlg = wx.FileDialog(self, self.dialogTitle, directory, current,
self.fileMask, self.fileMode)
if dlg.ShowModal() == wx.ID_OK:
self.SetValue(dlg.GetPath())
dlg.Destroy()
In my PyQt4 application, there is a functionality that allows users to save a avi file.
To this aim, a saveMovie method has been implemented in the main window:
def saveMovie(self):
""" Let the user make a movie out of the current experiment. """
filename = QtGui.QFileDialog.getSaveFileName(self, "Export Movie", "",
'AVI Movie File (*.avi)')
if filename != "":
dialog = QtGui.QProgressDialog('',
QtCore.QString(),
0, 100,
self,
QtCore.Qt.Dialog |
QtCore.Qt.WindowTitleHint)
dialog.setWindowModality(QtCore.Qt.WindowModal)
dialog.setWindowTitle('Exporting Movie')
dialog.setLabelText('Resampling...')
dialog.show()
make_movie(self.appStatus, filename, dialog)
dialog.close()
My idea is to use a QProgressDialog to show how the video encoding work is proceeding.
Nevertheless, after the selection of the filename, the QFileDialog won't disappear and the entire application stays unresponsive until the make_movie function has completed.
What should I do to avoid this?
Lesson learned: if you have some long-running operations to do -- for example, reading or writing a big file, move them to another thread or they will freeze the UI.
Therefore, I created a subclass of QThread, MovieMaker, whose run method encapsulates the functionality previosly implemented by make_movie:
class MovieMaker(QThread):
def __init__(self, uAppStatus, uFilename):
QtCore.QThread.__init__(self, parent=None)
self.appStatus = uAppStatus
self.filename = uFilename
def run(self):
## make the movie and save it on file
Let's move back to the saveMovie method. Here, I replaced the original call to make_movie with the following code:
self.mm = MovieMaker(self.appStatus,
filename)
self.connect(self.mm, QtCore.SIGNAL("Progress(int)"),
self.updateProgressDialog)
self.mm.start()
Note how I defined a new signal, Progress(int).
Such a signal is emitted by the MovieMaker thread to update the QProgressDialog used to show the user how the movie encoding work is progressing.
I want to restart my Python web application, if code gets changed. But there could be a large number of files that could be changed, since files in imported modules could change ...
How to get the actual file names from imported packages / modules?
How can modified Python files be detected efficiently? Is there a library to do that?
Shameless plug. There's also http://github.com/gorakhargosh/watchdog that I'm working on to do exactly this.
HTH.
gamin is another option which is slightly less Linux-specific.
I'm not sure how you would implement the 'reload application' operation in your circumstance; reloading a changed module with the reload built-in probably won't cut it.
But as far as detecting whether or not there was a change, the following would be one way to approach it.
Most python modules have a __file__ attribute.
All loaded modules are stored in sys.modules.
We can walk through sys.modules at some interval, and look for changes on disk for each module in turn
Sometimes __file__ points to a .pyc file instead of a .py file, so you might have to chop off the trailing c. Sometimes a .pyc file exists but a .py doesn't exist; in a robust system you'd have to allow for this.
A proof of concept of code to do this (not robust):
_module_timestamps = {}
_checking = False
def run_checker():
global _checking
_checking = True
while _checking:
for name, module in sys.modules.iteritems():
if hasattr(module, '__file__'):
filename = module.__file__
if filename.endswith('.pyc'):
filename = filename[:-1]
mtime = os.stat(filename).st_mtime
if name not in _module_timestamps:
_module_timestamps[name] = mtime
else:
if mtime > _module_timestamps[name]:
do_reload(name)
else:
'module %r has no file attribute' % (name,)
time.sleep(1)
def do_reload(modname):
print 'I would reload now, because of %r' % (modname,)
check_thread = threading.Thread(target=run_checker)
check_thread.daemon = True
check_thread.start()
try:
while 1:
time.sleep(0.1)
except KeyboardInterrupt:
print '\nexiting...'
Here's an example of how this could be implemented using pyinotify (ie., on Linux).
from importlib import import_module
class RestartingLauncher:
def __init__(self, module_name, start_function, stop_function, path="."):
self._module_name = module_name
self._filename = '%s.py' % module_name
self._start_function = start_function
self._stop_function = stop_function
self._path = path
self._setup()
def _setup(self):
import pyinotify
self._wm = pyinotify.WatchManager()
self._notifier = pyinotify.ThreadedNotifier(
self._wm, self._on_file_modified)
self._notifier.start()
# We monitor the directory (instead of just the file) because
# otherwise inotify gets confused by editors such a Vim.
flags = pyinotify.EventsCodes.OP_FLAGS['IN_MODIFY']
wdd = self._wm.add_watch(self._path, flags)
def _on_file_modified(self, event):
if event.name == self._filename:
print "File modification detected. Restarting application..."
self._reload_request = True
getattr(self._module, self._stop_function)()
def run(self):
self._module = import_module(self._module_name)
self._reload_request = True
while self._reload_request:
self._reload_request = False
reload(self._module)
getattr(self._module, self._start_function)()
print 'Bye!'
self._notifier.stop()
def launch_app(module_name, start_func, stop_func):
try:
import pyinotify
except ImportError:
print 'Pyinotify not found. Launching app anyway...'
m = import_module(self._module_name)
getattr(m, start_func)()
else:
RestartingLauncher(module_name, start_func, stop_func).run()
if __name__ == '__main__':
launch_app('example', 'main', 'force_exit')
The parameters in the launch_app call are the filename (without the ".py"), the function to start execution and a function that somehow stops the execution.
Here's a stupid example of an "app" that could be (re-)launched using the previous code:
run = True
def main():
print 'in...'
while run: pass
print 'out'
def force_exit():
global run
run = False
In a typical application where you'd want to use this, you'd probably have a main loop of some sort. Here's a more real example, for a GLib/GTK+ based application:
from gi.repository import GLib
GLib.threads_init()
loop = GLib.MainLoop()
def main():
print "running..."
loop.run()
def force_exit():
print "stopping..."
loop.quit()
The same concept works for most other loops (Clutter, Qt, etc).
Monitoring several code files (ie. all files that are part of the application) and error resilience (eg. printing exceptions and waiting in an idle loop until the code is fixed, then launching it again) are left as exercises for the reader :).
Note: All code in this answer is released under the ISC License (in addition to Creative Commons).
This is operating system specific. For Linux, there is inotify, see e.g. http://github.com/rvoicilas/inotify-tools/