How to hide a Gtk+ FileChooserDialog in Python 3.4? - python

I have a program set up so that it displays a FileChooserDialog all by itself (no main Gtk window, just the dialog).
The problem I'm having is that the dialog doesn't disappear, even after the user has selected the file and the program has seemingly continued executing.
Here's a snippet that showcases this issue:
from gi.repository import Gtk
class FileChooser():
def __init__(self):
global path
dia = Gtk.FileChooserDialog("Please choose a file", None,
Gtk.FileChooserAction.OPEN,
(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
Gtk.STOCK_OPEN, Gtk.ResponseType.OK))
self.add_filters(dia)
response = dia.run()
if response == Gtk.ResponseType.OK:
print("Open clicked")
print("File selected: " + dia.get_filename())
path = dia.get_filename()
elif response == Gtk.ResponseType.CANCEL:
print("Cancel clicked")
dia.destroy()
def add_filters(self, dia):
filter_any = Gtk.FileFilter()
filter_any.set_name("Any files")
filter_any.add_pattern("*")
dia.add_filter(filter_any)
dialog = FileChooser()
print(path)
input()
quit()
The dialog only disappears when the program exits with the quit() function call.
I've also tried dia.hide(), but that doesn't work either - the dialog is still visible while code continues running.
What would the proper way to make the dialog disappear?
EDIT: I've since learned that it's discouraged to make a Gtk dialog without a parent window. However, I don't want to deal with having to have the user close a window that has nothing in it and simply stands as the parent for the dialog.
Is there a way to make an invisible parent window and then quit the Gtk main loop when the dialog disappears?

You can set up a window first by doing:
def __init__ (self):
[.. snip ..]
w = Gtk.Window ()
dia = Gtk.FileChooserDialog("Please choose a file", w,
Gtk.FileChooserAction.OPEN,
(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
Gtk.STOCK_OPEN, Gtk.ResponseType.OK))
also, set a default value for path in case the user cancels:
path = ''
Then, at the end of your script:
print (path)
while Gtk.events_pending ():
Gtk.main_iteration ()
print ("done")
to collect and handle all events.

Related

Issues when attaching and detaching external app from QDockWidget

Consider this little piece of code:
import subprocess
import win32gui
import win32con
import time
import sys
from PyQt5.Qt import * # noqa
class Mcve(QMainWindow):
def __init__(self, path_exe):
super().__init__()
menu = self.menuBar()
attach_action = QAction('Attach', self)
attach_action.triggered.connect(self.attach)
menu.addAction(attach_action)
detach_action = QAction('Detach', self)
detach_action.triggered.connect(self.detach)
menu.addAction(detach_action)
self.dock = QDockWidget("Attach window", self)
self.addDockWidget(Qt.RightDockWidgetArea, self.dock)
p = subprocess.Popen(path_exe)
time.sleep(0.5) # Give enough time so FindWindowEx won't return 0
self.hwnd = win32gui.FindWindowEx(0, 0, "CalcFrame", None)
if self.hwnd == 0:
raise Exception("Process not found")
def detach(self):
try:
self._window.setParent(None)
# win32gui.SetWindowLong(self.hwnd, win32con.GWL_EXSTYLE, self._style)
self._window.show()
self.dock.setWidget(None)
self._widget = None
self._window = None
except Exception as e:
import traceback
traceback.print_exc()
def attach(self):
# self._style = win32gui.GetWindowLong(self.hwnd, win32con.GWL_EXSTYLE)
self._window = QWindow.fromWinId(self.hwnd)
self._widget = self.createWindowContainer(self._window)
self.dock.setWidget(self._widget)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = Mcve("C:\\Windows\\system32\\calc.exe")
w.show()
sys.exit(app.exec_())
The goal here is to fix the code so the window attaching/detaching into a QDockWidget will be made properly. Right now, the code has 2 important issues.
Issue1
Style of the original window is screwed up:
a) Before attaching (the calculator has a menu bar)
b) When attached (the calculator menu bar is gone)
c) When detached (the menu bar hasn't been restored properly)
I've already tried using flags/setFlags qt functions or getWindowLong/setWindowLong but I haven't had luck with all my attempts
Issue2
If you have attached and detached the calculator to the mainwindow, and then you decide to close the mainwindow, you definitely want everything (pyqt process) to be closed and cleaned properly. Right now, that won't be the case, why?
In fact, when you've attached/detached the calculator to the mainwindow, the python process will hold and you'll need to force the termination of the process manually (i.e. ctrl+break conemu, ctrl+c cmd prompt)... which indicates the code is not doing things correctly when parenting/deparenting
Additional notes:
http://doc.qt.io/qt-5/qwindow.html#fromWinId
http://doc.qt.io/qt-5/qwidget.html#createWindowContainer
In the above minimal code I'm spawning calc.exe as a child process but you can assume calc.exe is an existing non-child process spawned by let's say explorer.exe
I found part of the issue wrt to closing. So when you are creating the self._window in the attach function and you close the MainWindow, that other window (thread) is sitting around still. So if you add a self._window = None in the __init__ function and add a __del__ function as below, that part is fixed. Still not sure about the lack of menu. I'd also recommend holding onto the subprocess handle with self.__p instead of just letting that go. Include that in the __del__ as well.
def __del__(self):
self.__p.terminate()
if self._window:
print('terminating window')
self._window.close
Probably better yet would be to include a closeEvent
def closeEvent(self, event):
print('Closing time')
self.__p.terminate()
if self._window is not None:
print('terminating window')
self._window.close

pyGtk3 Do dialogues have to have a parent? If so, how do you call them from a script?

I can have a script that calls a window, but when I try to raise a dialogue with parent = None, I get:
Gtk-Message: GtkDialog mapped without a transient parent. This is discouraged.
What parent can I map this this to? It seems I can map it to a dummy parent, but will this cause things to break and people to die?
Code from http://python-gtk-3-tutorial.readthedocs.io/en/latest/dialogs.html#messagedialog where it is called from a parent window... but I want to be able to pop this up as I'm running through a terminal script.
Edit: Some poking around (and also provide by an answer below) yielded that using Gtk.Window() as the first argument below (instead of none) does get rid of the message...
def on_question():
dialog = Gtk.MessageDialog(None, 0, Gtk.MessageType.QUESTION,
Gtk.ButtonsType.YES_NO, "This is an QUESTION MessageDialog")
dialog.format_secondary_text(
"And this is the secondary text that explains things.")
response = dialog.run()
if response == Gtk.ResponseType.YES:
print("QUESTION dialog closed by clicking YES button")
elif response == Gtk.ResponseType.NO:
print("QUESTION dialog closed by clicking NO button")
dialog.destroy()
I will made the above comment an answer.
You may have a w = Gtk.Window() somewhere in your code (it may be inside function body) and pass that w to on_question:
def on_question(parent=None):
dialog = Gtk.MessageDialog(parent, 0, Gtk.MessageType.QUESTION,
Gtk.ButtonsType.YES_NO, "This is an QUESTION MessageDialog")
....
w = Gtk.Window()
on_question(w)
or
def on_question():
dialog = Gtk.MessageDialog(Gtk.Window(), 0, Gtk.MessageType.QUESTION,
Gtk.ButtonsType.YES_NO, "This is an QUESTION MessageDialog")
The Gtk-message it's gone, if that is the only problem.

Why gtk.Dialog disappears directly when entry received no focus

I was searching for an solution to hide gtk dialog immediately after get the response. But now I am wondering why it disappears, but only if I don't click into the entry field:
import gtk, time
def get_info():
entry = gtk.Entry()
entry.set_text("Hello")
dialog = gtk.Dialog(title = "Title",
parent = None,
flags = gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
dialog.vbox.pack_start(entry)
dialog.show_all()
response = dialog.run()
if response == gtk.RESPONSE_ACCEPT:
info = entry.get_text().strip()
dialog.destroy()
return info
else:
exit()
info = get_info()
time.sleep(4)
print info
If I just press "OK" the dialog disappears and after 4 seconds the info is printed.
If I click into the entry field and then press "OK" the dialog doesn't disappear until the program ends.
Why is this so?
edit:
Same problem if I make this with a main loop:
#!/usr/bin/env python
# -*- coding: utf8 -*-
import gtk, time
class EntryTest:
def get_info(self):
entry = gtk.Entry()
entry.set_text("Hello")
dialog = gtk.Dialog(title = "Title",
parent = None,
flags = gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
dialog.vbox.pack_start(entry)
dialog.show_all()
response = dialog.run()
if response == gtk.RESPONSE_ACCEPT:
info = entry.get_text().strip()
dialog.destroy()
return info
else:
exit()
def main(self):
gtk.main()
if __name__ == "__main__":
base = EntryTest()
info = base.get_info()
time.sleep(4)
print info
You don't have a main loop running. This usually means Gtk+ doesn't do anything -- windows wouldn't be shown in the first place -- but dialog.run() is special in that it happens to run its own short-lived mainloop so it looks like things are working. After dialog.run() exits you really don't have a mainloop running so Gtk+ cannot do anything.
If you do this in a real app where gtk.main() is running, it should just work.
Example main loop use (EntryTest can stay as it is, but you will need an additional import glib):
def quit ():
print "now quitting"
gtk.main_quit()
return False
if __name__ == "__main__":
base = EntryTest()
print base.get_info()
glib.timeout_add_seconds (3, quit)
gtk.main()
It's worth noting that the main loop is not running when the dialog is visible but only afterwards (because I was lazy). You could start the get_info() code within the main loop as well with e.g. glib.idle_add() but the point is the same: GTK+ usually needs the main loop to be running.

How to make filename/path from Gtk+ (Python 3.4) FileChooserDialog accessible globally

I've got some code that uses the Gtk+ FileChooserDialog in Python 3.4 to allow a user to select a file.
Then, it's supposed to close the dialog (obviously) and continue executing the code that follows the user choosing a file. However, what happens is that the user selects their file, and the code continues, but the dialog doesn't disappear like it should.
I had this issue previously, and we figured out the reason why it was happening then and resolved it, but now it's happening again and although I know what's causing, I don't know how to actually resolve it.
Here's my code:
from gi.repository import Gtk
class FileChooser():
def __init__(self):
global path
dia = Gtk.FileChooserDialog("Please choose a file", None,
Gtk.FileChooserAction.OPEN,
(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
Gtk.STOCK_OPEN, Gtk.ResponseType.OK))
self.add_filters(dia)
response = dia.run()
if response == Gtk.ResponseType.OK:
print("Open clicked")
print("File selected: " + dia.get_filename())
path = dia.get_filename()
elif response == Gtk.ResponseType.CANCEL:
print("Cancel clicked")
dia.destroy()
def add_filters(self, dia):
filter_any = Gtk.FileFilter()
filter_any.set_name("Any files")
filter_any.add_pattern("*")
dia.add_filter(filter_any)
filter_text = Gtk.FileFilter()
filter_text.set_name('Text files')
filter_text.add_mime_type('text/plain')
dia.add_filter(filter_text)
filter_py = Gtk.FileFilter()
filter_py.set_name('Python files')
filter_py.add_mime_type('text/x-python')
dia.add_filter(filter_py)
filter_img = Gtk.FileFilter()
filter_img.set_name('Image')
filter_img.add_mime_type('image/*')
dia.add_filter(filter_img)
dialog = FileChooser()
# path variable will be used after this point
The issue here is that, for reasons unknown to me, if I have the global path declaration in the FileChooser() class' __init__() function, the dialog won't disappear.
If I remove that global path declaration, the dialog goes away, but I get a NameError: name 'path' is not defined later in the program when I try to access the path variable!
I have also tried making path global right at the start of the program, but I still get the NameError.
What can I do to make this variable accessible later in my program, while still making the dialog disappear?
Consider the path variable as an instance to FileChooser(). It provides a logical end to have path accessed by the dialog that is representing the FileChooser().
class FileChooser():
def __init__(self):
#Stores your path
self.path = None
dia = Gtk.FileChooserDialog("Please choose a file", None,
Gtk.FileChooserAction.OPEN,
(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
Gtk.STOCK_OPEN, Gtk.ResponseType.OK))
self.add_filters(dia)
response = dia.run()
if response == Gtk.ResponseType.OK:
print("Open clicked")
print("File selected: " + dia.get_filename())
self.path = dia.get_filename()
elif response == Gtk.ResponseType.CANCEL:
print("Cancel clicked")
dia.destroy()
When you create the object.
dialog = FileChooser()
You can access it as follows:
dialog.path

Error when "Cancel" while opening a file in PyQt4

I have a simple PyQt4 GUI where I have the user open a txt file that will display in the GUI's QPlainTextEdit widget.
Here's the pseudo code:
class mainWindow(QtGui.QWidget):
def __init__(self):
super(mainWindow, self).__init__()
self.layout = QtGui.QVBoxLayout()
self.plain = QtGui.QPlainTextEdit()
self.openButton = QtGui.QPushButton("OPEN")
self.layout.addWidget(self.plain)
self.layout.addWidget(self.openButton)
self.openButton.clicked.connect(self.openFile)
def openFile(self):
openFileName = QtGui.QFileDialog.getOpenFileName(None, "Open File","/some/dir/","TXT(*.txt);;AllFiles(*.*)")
openFile = open(openFileName,'r').read()
self.plainTextEdit.appendPlainText(openFile)
So I click the "OPEN" button and the QFileDialog pops up, but if I hit the "CANCEL" button within the QFileDialog, I get this error:
IOError: [Errno 2] No such file or directory: PyQt4.QtCore.QString(u'')
I know as the programmer that this error can be easily ignored and does not impact the code's operation whatsoever, but the users do not know that. Is there a way to eliminate this error from printing in terminal?
Yes: simply check the return value from getOpenFileName. This method returns an empty QString if the user clicks Cancel. As empty QStrings are considered false, you can check to see if the user chose a file instead of clicking Cancel by putting the last two lines of your openFile method in an if openFileName: statement:
def openFile(self):
openFileName = QtGui.QFileDialog.getOpenFileName(None, "Open File","/some/dir/","TXT(*.txt);;AllFiles(*.*)")
if openFileName:
openFile = open(openFileName,'r').read()
self.plainTextEdit.appendPlainText(openFile)
as one above said, you should debug and check what kind of answer "openFilename" returns.
in Pyqt5, when you press cancel or 'x' to close the window, it returns a tuple
so to get around the issue:
openFileName = QFileDialog.getOpenFileName(self)
if openFileName != ('', ''):
with open(openFileName [0], 'r') as f:
file_text = f.read()
self.text.setText(file_text)

Categories

Resources