Open multiple files from file managers - python

I've built a (Linux) GUI application that can be launched from a terminal and accepts an undefined number of files as arguments. The app reads sys.argv and lists the name of these files in a QListWidget.
The code is something like:
import sys
from PyQt4.QtGui import QApplication, QMainWindow, QCoreApplication
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
# parse command line arguments
for i in QCoreApplication.argv()[1:]:
...
def main():
app = QApplication(sys.argv)
...
What I want to do is to be able to select multiple files from a file manager and open them with my app through the "Open with..." option provided by file managers. How this can be achieved?
With the current code, when I try it only one of the selected files is shown on the QListWidget.
Edit:
It finally seems that it depends to the file manager.
I tried with a few file managers and...
pcmanfm: It only opens one of the selected files.
spacefm: Works properly!
dolphin: It opens each file to a different instance of my program. If
I select 3 files it will open my app 3 times, one for each file.
nautilus: I didn't manage to open any files with it. My program is not listed in the suggested applications and I didn't find any way to do it.

There's not really enough information to give a definite answer, but...
First, have you checked that a print sys.argv at the top of the code looks like you were expecting?
If so, does it work if you change the line...
for i in QCoreApplication.argv()[1:]:
...to...
for i in sys.argv[1:]:
For debugging purposes, you might also like to include the line...
assert QCoreApplication.argv()[1:] == sys.argv[1:]
...just before you start the for-loop.

Use a QFileDialog: Documentation

Related

PyQt5 Calling a static method inside a class' method messes up the execution order of the method's code [duplicate]

This question already has an answer here:
QLabel is not updated unless the mainWindow is unfocused
(1 answer)
Closed 1 year ago.
I've been building a fairly simple program using PyQt5 for the GUI for the past few days and I have the following problem which I can't seem to find a solution for.
Here's an oversimplified version of my code (kept the absolute basics to keep things short):
def run_blueprint(file):
# doing stuff to the file and returning the path to the output file
return full_path
class Window(QMainWindow, Ui_MainWindow):
# some variables here
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)
self.connectSignalsSlots()
def connectSignalsSlots(self):
self.pushButton.clicked.connect(self.processFiles)
def processFiles(self):
self.toggleElements()
for i, file in enumerate(self.selected_files_directories):
temp_full_path = run_blueprint(file)
self.listWidget_3.insertItem(i, temp_full_path)
self.toggleElements()
def toggleElements(self):
self.pushButton_2.setEnabled(not self.pushButton_2.isEnabled())
self.listWidget.setEnabled(not self.listWidget.isEnabled())
if __name__ == "__main__":
app = QApplication(sys.argv)
win = Window()
win.show()
sys.exit(app.exec())
Let's assume that I have selected an item from the listWidget and also have a list of file paths stored in the selected_files_directories[] variable.
If I run the code as is, when it's time for processFiles(self) to run I get some weird behavior. For some reason the for loop is ran first, then the first toggleElements() and after that the second toggleElements(). This results in the two elements that I want to temporarily disable until the for loop is over staying enabled the whole time.
However if I don't call the run_blueprint(file) method at all, but instead run any other code inside the for loop or even completely discard the loop, those two elements are disabled first, then the rest of the code runs and finally they are enabled again.
I believe the problem has something to do with me calling a static method outside the class. Do you have any ideas on how to solve this issue? Thanks in advance!
Edit: Below is the simplified version of the run_blueprint() function.
The basic functionality of this program is as follows: I select a template file (.docx) and input file(s) (.pdf). Then using the docxtpl and pdfplumber libraries, for each of the selected input files I get specific text pieces from them and place them inside the template document and save that as a .docx file.
In the following code assume that template_path and bp_path show the path to the template file and blueprint file respectively.
def run_blueprint(file):
doc = DocxTemplate(template_path) # docxtpl method
global lns
lns = export_data(file) # function I made that uses pdfplumber to get all the text from a pdf file
global context
context = {}
with open(bp_path, 'r', encoding='utf8') as f:
blueprint = f.read() # blueprint contains python code that populates the context variable. I do it this way to easily change between blueprints
exec(blueprint, globals(), locals()) # I know it's not a good practice to use exec but it's intended for personal use
doc.render(context) # docxtpl method
output_path = "D:\\Desktop" # could be any path
doc.save(output_path) # docxtpl method
return output_path
The blueprint code usually contains API calls that take a few milliseconds to send a response. Apart from that it's mostly regex used to locate the text pieces I'm looking for.
This is an issue with the canvas being redrawn. Add a repaint call after the toggleElements call.
def processFiles(self):
self.toggleElements()
self.repaint()
for i, file in enumerate(self.selected_files_directories):
temp_full_path = run_blueprint(file)
self.listWidget_3.insertItem(i, temp_full_path)
self.toggleElements()
For more context based off the suggestions of the comments by #musicamante and to help clarify:
There isn't an issue with the canvas being repainted. The issue is that the function blocks the main thread. A better solution, given the context, would be to offload the blocking task to a QThread or QProcess to unblock the main thread.

Preventing file actions in QFileDialog, such as copying, viewing, deleting, etc

We are looking at deploying a PyQt application on an Azure server, and the application works well enough, albeit a little slow to respond to user actions.
We have a problem, however, and that is that the QFileDialog allows pretty much any explore action: copy a file from the virtual machine to the user's local drive, open a file within 'Program Files (x86)' in Notepad, etc.
Approaches already considered:
As the python application has to have read and write permissions to
run under 'Program Files (x86)', we can't use file permissions to
control access.
We can turn the Python into an inscrutable .exe, but this could
still be copied using the context menus in the file dialog.
We could use the file filters and then hide them, so you can only
see (and mess with) the relevant files, but the user could still
copy entire directories.
The only thing we can think of is to create our own file dialog from scratch, but that's very tedious. Are there any 'out of the box' solutions?
The QFileDialog class already has this functionality:
dialog = QtGui.QFileDialog()
dialog.setOption(QtGui.QFileDialog.ReadOnly, True)
dialog.exec_()
This only seems to work with Qt's built-in file-dialog, though. If you use the static functions to open a native file-dialog, the ReadOnly option seems to be ignored (I've only tested this on Linux, though).
looking at exemple of qtreeview they show a file explorer so i think it's actually not a big task to implement a simple file system explorer. it's specialy easy thanks to QFileSystemModel http://doc.qt.io/qt-5/model-view-programming.html#using-models-and-views
Here's what I actually did, based on #ekhumoro's advice:
from PyQt4 import QtGui
import guidata
import re
class _DirectoryFilterProxyModel(QtGui.QSortFilterProxyModel):
""" A basic filter to be applied to the file items to be displayed.
Based on C++ example at:
https://stackoverflow.com/questions/2101100/qfiledialog-filtering-folders. """
def __init__(self, ignore_directories=[], *args, **kw):
""" Constructor
:param ignore_directories: A list of directories to exclude. These
can be regular expressions or straight names. """
QtGui.QSortFilterProxyModel.__init__(self, *args, **kw)
self.ignore_directories = ignore_directories
def filterAcceptsRow(self, sourceRow, sourceParent):
fileModel = self.sourceModel()
index0 = fileModel.index(sourceRow, 0, sourceParent)
if fileModel:
if fileModel.isDir(index0):
for directory in self.ignore_directories:
if re.match(directory, fileModel.fileName(index0)):
return False
return True
else: # For files
return True
else:
return False
And instantiated:
app = guidata.qapplication()
dialog = QtGui.QFileDialog()
proxyModel = _DirectoryFilterProxyModel(ignore_directories=["Program Files", "Program Files (x86)", "Windows"])
dialog.setProxyModel(proxyModel)
dialog.setOption(QtGui.QFileDialog.ReadOnly, True)
dialog.setOption(QtGui.QFileDialog.HideNameFilterDetails, True)
dialog.exec_()
My thanks to #serge_gubenko and #Gayan on the page qfiledialog - Filtering Folders? for providing the C++ implementation, from which I derived the above.

Pickling a dict inside a PyQt app

I'm attempting to dump a pickle file in my PyQt app only its seems to be completely ignoring the statement.
import cPickle as pickle
class MyActions(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
#Create blank dictionary and save it.
self.employee_dict = {}
pickle.dump(self.employee_dict, open("pickle file", 'wb'))
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
QtCore.QObject.connect(self.ui.pushButton, QtCore.SIGNAL('clicked()'), self.emp_display )
QtCore.QObject.connect(self.ui.pushButton_2, QtCore.SIGNAL('clicked()'), self.admin_display)
am i doing something wrong here or is pickle just not compatible with dumping dictionaries in side a PyQt gui.
Even though your question doesn't have enough information to go on, I'm willing to bet I know the answer.
You're creating a file called pickle file in whatever the current directory happens to be.
Then you're going and looking for the pickle file right next to the script, which is in general not going to be the current directory.
In the special case where you're running a script from the command line as ./foo.py or python ./foo.py, of course, the two happen to be the same. But when you double-click a PyQt app, it's… well, that depends on what platform you're on, and a variety of Explorer/Finder/Nautilus/etc. settings (either global, or per-app), but it's generally not going to be the same place the script is.
If you just want to force it to write the file next to the script (which is usually a bad idea), you can save the script directory at startup, like this:
import os
import sys
scriptdir = os.path.dirname(sys.argv[0])
Then, instead of writing to 'pickle file', you can write to os.path.join(scriptdir, 'pickle file').
However, a better solution is to use a platform-appropriate "app data" directory or "home" or "documents" directory (depending on whether you want the file to be visible to novice users); Qt has nice wrappers that make it easy to do that in the platform-appropriate way with platform-independent code.

How to display an image and prompt for a short string in Python from command line

(I edited the whole question to be more clear)
Hello,
I have never had any affairs with Python GUI libraries. I know there are plenty and well documented, but as I need only one single snippet, I would dislike to dive deep into documentations to seek for a way how to do it. If I am going to write a GUI program, I surely would do that, but this is needed only as a few lines for my ad hoc script.
What would be the easiest and the most straightforward way for me (GUI noob) to write in Python following piece of code? Less lines = more happiness.
Grab a JPEG picture by filename.
Display it's thumbnail.
Below the thumbnail display a textfield so the user can type in a caption.
Wait until user hits ENTER key on his/her keyboard. In that case, close and return the input.
...or wait until user hits DELETE key. In that case, close and return an information about the decision (to delete the picture).
Dependencies or Linux-only solutions are okay. I need to run this on Xubuntu machine. Any code snippets, please? I believe this is a matter of 5 minutes for someone skilled in Python GUI field. I would need to study loads of library docs. Thank you!
Below is a minimal python script that more or less fits the spec.
It requires python2 and pyqt4 packages to be installed, and it won't work with python3 (although it could quite easily be adapted to do so if necessary).
If the user types in a valid caption and presses enter, the script will return with status code 0 and print the caption to stdout; otherwise, if the user enters an invalid caption (empty or whitespace only), or simply closes the dialog without doing anything, the script will return with status code 1 and print nothing.
example bash usage:
$ CAPTION=$(python imgviewer.py image.jpg)
$ [ $? -eq 0 ] && echo $CAPTION
imgviewer.py:
import sys, os
from PyQt4 import QtGui, QtCore
class Dialog(QtGui.QDialog):
def __init__(self, path):
QtGui.QDialog.__init__(self)
self.viewer = QtGui.QLabel(self)
self.viewer.setMinimumSize(QtCore.QSize(400, 400))
self.viewer.setScaledContents(True)
self.viewer.setPixmap(QtGui.QPixmap(path))
self.editor = QtGui.QLineEdit(self)
self.editor.returnPressed.connect(self.handleReturnPressed)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.viewer)
layout.addWidget(self.editor)
def handleReturnPressed(self):
if self.editor.text().simplified().isEmpty():
self.reject()
else:
self.accept()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
args = app.arguments()[1:]
if len(args) == 1:
dialog = Dialog(args[0])
if dialog.exec_() == QtGui.QDialog.Accepted:
print dialog.editor.text().simplified().toLocal8Bit().data()
sys.exit(0)
else:
print 'ERROR: wrong number of arguments'
sys.exit(1)
There are several good GUI libraries for Python. The "standard" library that comes built-in with python is tkinter:http://wiki.python.org/moin/TkInter. Some says that wxPython is much more powerful and straightforward: http://www.wxpython.org/.
I think that you can start with wxPython, they have many many tutorials and examples you can dig into (just run the DEMO).
They have an example called "ImageBrowser" which might be a very good starting point.
Regarding the communication between the different apps, you can use "pipes" and "redirections" to communicate. But if everything is written in python, I think this is the wrong way to go, you can show the image form within your python script and get the result internally.

Basic cocoa application using dock in Python, but not Xcode and all that extras

It seems that if I want to create a very basic Cocoa application with a dock icon and the like, I would have to use Xcode and the GUI builder (w/ PyObjC).
The application I am intending to write is largely concerned with algorithms and basic IO - and thus, not mostly related to Apple specific stuff.
Basically the application is supposed to run periodically (say, every 3 minutes) .. pull some information via AppleScript and write HTML files to a particular directory. I would like to add a Dock icon for this application .. mainly to showing the "status" of the process (for example, if there is an error .. the dock icon would have a red flag on it). Another advantage of the dock icon is that I can make it run on startup.
Additional bonus for defining the dock right-click menu in a simple way (eg: using Python lists of callables).
Can I achieve this without using Xcode or GUI builders but simply using Emacs and Python?
Install the latest py2app, then make a new directory -- cd to it -- in it make a HelloWorld.py file such as:
# generic Python imports
import datetime
import os
import sched
import sys
import tempfile
import threading
import time
# need PyObjC on sys.path...:
for d in sys.path:
if 'Extras' in d:
sys.path.append(d + '/PyObjC')
break
# objc-related imports
import objc
from Foundation import *
from AppKit import *
from PyObjCTools import AppHelper
# all stuff related to the repeating-action
thesched = sched.scheduler(time.time, time.sleep)
def tick(n, writer):
writer(n)
thesched.enter(20.0, 10, tick, (n+1, writer))
fd, name = tempfile.mkstemp('.txt', 'hello', '/tmp');
print 'writing %r' % name
f = os.fdopen(fd, 'w')
f.write(datetime.datetime.now().isoformat())
f.write('\n')
f.close()
def schedule(writer):
pool = NSAutoreleasePool.alloc().init()
thesched.enter(0.0, 10, tick, (1, writer))
thesched.run()
# normally you'd want pool.drain() here, but since this function never
# ends until end of program (thesched.run never returns since each tick
# schedules a new one) that pool.drain would never execute here;-).
# objc-related stuff
class TheDelegate(NSObject):
statusbar = None
state = 'idle'
def applicationDidFinishLaunching_(self, notification):
statusbar = NSStatusBar.systemStatusBar()
self.statusitem = statusbar.statusItemWithLength_(
NSVariableStatusItemLength)
self.statusitem.setHighlightMode_(1)
self.statusitem.setToolTip_('Example')
self.statusitem.setTitle_('Example')
self.menu = NSMenu.alloc().init()
menuitem = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_(
'Quit', 'terminate:', '')
self.menu.addItem_(menuitem)
self.statusitem.setMenu_(self.menu)
def writer(self, s):
self.badge.setBadgeLabel_(str(s))
if __name__ == "__main__":
# prepare and set our delegate
app = NSApplication.sharedApplication()
delegate = TheDelegate.alloc().init()
app.setDelegate_(delegate)
delegate.badge = app.dockTile()
delegate.writer(0)
# on a separate thread, run the scheduler
t = threading.Thread(target=schedule, args=(delegate.writer,))
t.setDaemon(1)
t.start()
# let her rip!-)
AppHelper.runEventLoop()
Of course, in your real code, you'll be performing your own periodic actions every 3 minutes (rather than writing a temp file every 20 seconds as I'm doing here), doing your own status updates (rather than just showing a counter of the number of files written so far), etc, etc, but I hope this example shows you a viable starting point.
Then in Terminal.App cd to the directory containing this source file, py2applet --make-setup HelloWorld.py, python setup.py py2app -A -p PyObjC.
You now have in subdirectory dist a directory HelloWorld.app; open dist and drag the icon to the Dock, and you're all set (on your own machine -- distributing to other machines may not work due to the -A flag, but I had trouble building without it, probably due to mis-installed egg files laying around this machine;-). No doubt you'll want to customize your icon &c.
This doesn't do the "extra credit" thingy you asked for -- it's already a lot of code and I decided to stop here (the extra credit thingy may warrant a new question). Also, I'm not quite sure that all the incantations I'm performing here are actually necessary or useful; the docs are pretty latitant for making a pyobjc .app without Xcode, as you require, so I hacked this together from bits and pieces of example code found on the net plus a substantial amount of trial and error. Still, I hope it helps!-)
PyObjC, which is included with Mac OS X 10.5 and 10.6, is pretty close to what you're looking for.
Chuck is correct about PyObjC.
You should then read about this NSApplication method to change your icon.
-(void)setApplicationIconImage:(NSImage *)anImage;
For the dock menu, implement the following in an application delegate. You can build an NSMenu programmatically to avoid using InterfaceBuilder.
-(NSMenu *)applicationDockMenu:(NSApplication *)sender;

Categories