The SWT-Gui looks very nice.
Is there an easy way to use it in Jython ?
Given that you can use all Java classes from within Jython, it is also possible to use SWT.
For the example, adapted from an SWT snippet, make sure you have the SWT jar on your CLASSPATH:
import org.eclipse.swt as swt
import org.eclipse.swt.widgets as widgets
import org.eclipse.swt.layout as layout
result = None
display = widgets.Display()
shell = widgets.Shell(display)
shell.pack()
shell.open()
dialog = widgets.Shell(shell, swt.SWT.DIALOG_TRIM | swt.SWT.APPLICATION_MODAL)
dialog.setLayout(layout.RowLayout())
ok = widgets.Button(dialog, swt.SWT.PUSH)
ok.setText ("OK")
cancel = widgets.Button(dialog, swt.SWT.PUSH);
cancel.setText("Cancel");
class MyListener(widgets.Listener):
def handleEvent(self, event):
global result
result = event.widget == ok
dialog.close()
listener = MyListener()
ok.addListener(swt.SWT.Selection, listener)
cancel.addListener(swt.SWT.Selection, listener)
dialog.pack()
dialog.open()
while not dialog.isDisposed():
if not display.readAndDispatch():
display.sleep ()
print "Result:", result
display.dispose()
Jython has a few other niceties that makes the code cleaner.
Jython automagically translates getters & setters into public properties so that
ok.setText ("OK")
becomes simply
ok.text = 'OK'
You can then supply them as named arguments to the constructor. Jython also handles creating listener objects for your event handlers:
def handleEvent(self, event):
global result
result = event.widget == ok
dialog.close()
ok = widgets.Button(dialog, swt.SWT.PUSH
text='OK',
widgetSelected=handleEvent)
cancel = widgets.Button(dialog, swt.SWT.PUSH
text='Cancel',
widgetSelected=handleEvent)
Actually, there is no need for a special module. This talk by Sean McGrath contains a simple example of a Jython/SWT GUI.
Slide 11 of the talk begins with:
"""
Simple SWT Example
Sean McGrath
"""
from org.eclipse.swt.events import *
from org.eclipse.swt.graphics import *
from org.eclipse.swt.layout import *
from org.eclipse.swt.widgets import *
from org.eclipse.swt.layout.GridData import *
from org.eclipse.swt import *
It shows that SWT is directly usable from Jython.
The full example is right there at Sean's site.
Related
I am currently working on a GUI using qt designer. I am wondering how I should go about printing strings on the GUI that acts like a logger window. I am using pyqt5.
Adapted from Todd Vanyo's example for PyQt5:
import sys
from PyQt5 import QtWidgets
import logging
# Uncomment below for terminal log messages
# logging.basicConfig(level=logging.DEBUG, format=' %(asctime)s - %(name)s - %(levelname)s - %(message)s')
class QTextEditLogger(logging.Handler):
def __init__(self, parent):
super().__init__()
self.widget = QtWidgets.QPlainTextEdit(parent)
self.widget.setReadOnly(True)
def emit(self, record):
msg = self.format(record)
self.widget.appendPlainText(msg)
class MyDialog(QtWidgets.QDialog, QtWidgets.QPlainTextEdit):
def __init__(self, parent=None):
super().__init__(parent)
logTextBox = QTextEditLogger(self)
# You can format what is printed to text box
logTextBox.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
logging.getLogger().addHandler(logTextBox)
# You can control the logging level
logging.getLogger().setLevel(logging.DEBUG)
self._button = QtWidgets.QPushButton(self)
self._button.setText('Test Me')
layout = QtWidgets.QVBoxLayout()
# Add the new logging box widget to the layout
layout.addWidget(logTextBox.widget)
layout.addWidget(self._button)
self.setLayout(layout)
# Connect signal to slot
self._button.clicked.connect(self.test)
def test(self):
logging.debug('damn, a bug')
logging.info('something to remember')
logging.warning('that\'s not right')
logging.error('foobar')
app = QtWidgets.QApplication(sys.argv)
dlg = MyDialog()
dlg.show()
dlg.raise_()
sys.exit(app.exec_())
Threading issues
Note: as of late 2022, this is still the highest ranked answer. The OP doesn't seem to be active anymore, so I took the liberty of editing it because the lack of explanation of the implications of its usage is a common cause of closely related questions.
Be aware that the above code will only work for single threaded programs. If you are sure that your program doesn't use threading, then it's fine. Be aware, though: you have to be completely sure about that, also considering external modules. Qt included.
Don't underestimate this aspect: even some Qt classes that are thread safe are actually not single threaded; for instance, QFileSystemModel is a thread safe object, but it does use threading for file system crawling. If it faces a problem for any reason (ie. access permissions), it will output some debug messages from those threads, and you'll probably still get issues.
Possible results of underestimated threading issues while accessing UI elements include:
unexpected behavior;
drawing artifacts;
lots of "unrelated" debug messages;
possible fatal crash of the program;
So, as a rule of thumb, the above has to be considered as unsafe and should not be used, since it attempts to access the QPlainTextEdit widget even from external threads: widgets are not thread-safe, and can only be accessed from the main thread.
Since there is no immediate way to know if the logging source is in the same thread or not without pointlessly affecting performance, it's better to always use a thread safe solution in any case (see the other answers that consider this aspect).
If you are using the Python logging module to can easily create a custom logging handler that passes the log messages through to a QPlainTextEdit instance (as described by Christopher).
To do this you first subclass logging.Handler. In this __init__ we create the QPlainTextEdit that will contain the logs. The key bit here is that the handle will be receiving messages via the emit() function. So we overload this function and pass the message text into the QPlainTextEdit.
import logging
class QPlainTextEditLogger(logging.Handler):
def __init__(self, parent):
super(QPlainTextEditLogger, self).__init__()
self.widget = QPlainTextEdit(parent)
self.widget.setReadOnly(True)
def emit(self, record):
msg = self.format(record)
self.widget.appendPlainText(msg)
def write(self, m):
pass
Create an object from this class, passing it the parent for the QPlainTextEdit (e.g. the main window, or a layout). You can then add this handler for the current logger.
# Set up logging to use your widget as a handler
log_handler = QPlainTextEditLogger(<parent widget>)
logging.getLogger().addHandler(log_handler)
Here's a complete working example based on mfitzp's answer:
import sys
from PyQt4 import QtCore, QtGui
import logging
# Uncomment below for terminal log messages
# logging.basicConfig(level=logging.DEBUG, format=' %(asctime)s - %(name)s - %(levelname)s - %(message)s')
class QPlainTextEditLogger(logging.Handler):
def __init__(self, parent):
super().__init__()
self.widget = QtGui.QPlainTextEdit(parent)
self.widget.setReadOnly(True)
def emit(self, record):
msg = self.format(record)
self.widget.appendPlainText(msg)
class MyDialog(QtGui.QDialog, QPlainTextEditLogger):
def __init__(self, parent=None):
super().__init__(parent)
logTextBox = QPlainTextEditLogger(self)
# You can format what is printed to text box
logTextBox.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
logging.getLogger().addHandler(logTextBox)
# You can control the logging level
logging.getLogger().setLevel(logging.DEBUG)
self._button = QtGui.QPushButton(self)
self._button.setText('Test Me')
layout = QtGui.QVBoxLayout()
# Add the new logging box widget to the layout
layout.addWidget(logTextBox.widget)
layout.addWidget(self._button)
self.setLayout(layout)
# Connect signal to slot
self._button.clicked.connect(self.test)
def test(self):
logging.debug('damn, a bug')
logging.info('something to remember')
logging.warning('that\'s not right')
logging.error('foobar')
if (__name__ == '__main__'):
app = None
if (not QtGui.QApplication.instance()):
app = QtGui.QApplication([])
dlg = MyDialog()
dlg.show()
dlg.raise_()
if (app):
app.exec_()
Thread-safe version
class QTextEditLogger(logging.Handler, QtCore.QObject):
appendPlainText = QtCore.pyqtSignal(str)
def __init__(self, parent):
super().__init__()
QtCore.QObject.__init__(self)
self.widget = QtWidgets.QPlainTextEdit(parent)
self.widget.setReadOnly(True)
self.appendPlainText.connect(self.widget.appendPlainText)
def emit(self, record):
msg = self.format(record)
self.appendPlainText.emit(msg)
Usage
logTextBox = QTextEditLogger(self)
# log to text box
logTextBox.setFormatter(
logging.Formatter(
'%(asctime)s %(levelname)s %(module)s %(funcName)s %(message)s'))
logging.getLogger().addHandler(logTextBox)
logging.getLogger().setLevel(logging.DEBUG)
# log to file
fh = logging.FileHandler('my-log.log')
fh.setLevel(logging.DEBUG)
fh.setFormatter(
logging.Formatter(
'%(asctime)s %(levelname)s %(module)s %(funcName)s %(message)s'))
logging.getLogger().addHandler(fh)
Alex's answer should be ok in a single thread scenario, but if you are logging in another thread (QThread) you may get the following warning:
QObject::connect: Cannot queue arguments of type 'QTextCursor'
(Make sure 'QTextCursor' is registered using qRegisterMetaType().)
This is because you are modifying the GUI (self.widget.appendPlainText(msg)) from a thread other than the main thread without using the Qt Signal/Slot mechanism.
Here is my solution:
# my_logger.py
import logging
from PyQt5.QtCore import pyqtSignal, QObject
class Handler(QObject, logging.Handler):
new_record = pyqtSignal(object)
def __init__(self, parent):
super().__init__(parent)
super(logging.Handler).__init__()
formatter = Formatter('%(asctime)s|%(levelname)s|%(message)s|', '%d/%m/%Y %H:%M:%S')
self.setFormatter(formatter)
def emit(self, record):
msg = self.format(record)
self.new_record.emit(msg) # <---- emit signal here
class Formatter(logging.Formatter):
def formatException(self, ei):
result = super(Formatter, self).formatException(ei)
return result
def format(self, record):
s = super(Formatter, self).format(record)
if record.exc_text:
s = s.replace('\n', '')
return s
# gui.py
... # GUI code
...
def setup_logger(self)
handler = Handler(self)
log_text_box = QPlainTextEdit(self)
self.main_layout.addWidget(log_text_box)
logging.getLogger().addHandler(handler)
logging.getLogger().setLevel(logging.INFO)
handler.new_record.connect(log_text_box.appendPlainText) # <---- connect QPlainTextEdit.appendPlainText slot
...
Sounds like you'll want to use a QPlainTextEdit widget set to read-only.
Consider changing the background color to gray to give the user a hint that it is not editable. It is also up to you if you want it to be scrollable or the text selectable.
This answer can get you started subclassing QPlainTextEdit to scroll with output, save to a file, whatever.
Sifferman’s answer appears to be the most elegant to me. Regardless, I've tried all in this post. They all work, but notice that if you try, e.g., to write a test and create a log entry on it you might get a nasty error like
RuntimeError: wrapped C/C++ object of type QPlainTextEdit has been deleted
After loosing my mind for a couple hours, I noticed that it's quite important to delete the handler manually when closing the window,
def closeEvent(self, event):
...
root_logger = logging.getLogger()
root_logger.removeHandler(self.logger)
...
super().closeEvent(event)
I'm running Jupyter 5.0.0 notebook with Python 3.5.3 on Windows 10. The following example code fails to run:
from concurrent.futures import as_completed, ProcessPoolExecutor
import time
import numpy as np
def do_work(idx1, idx2):
time.sleep(0.2)
return np.mean([idx1, idx2])
with ProcessPoolExecutor(max_workers=4) as executor:
futures = set()
for idx in range(32):
future = winprocess.submit(
executor, do_work, idx, idx * 2
)
futures.add(future)
for future in as_completed(futures):
print(future.result())
... and throws BrokenProcessPool: A process in the process pool was terminated abruptly while the future was running or pending.
The code works perfectly fine on Ubuntu 14.04.
I've understand that Windows doesn't have os.fork, thus multiprocessing is handled differently, and doesn't always play nice with interactive mode and Jupyter.
What are some workarounds to make ProcessPoolExecutor work in this case?
There are some similar questions, but they relate to multiprocessing.Pool:
multiprocessing.Pool in jupyter notebook works on linux but not windows
Closer inspection shows that a Jupyter notebook can run external python modules which is parallelized using ProcessPoolExecutor. So, a solution is to do the parallelizable part of your code in a module and call it from the Jupyter notebook.
That said, this can be generalized as a utility. The following can be stored as a module, say, winprocess.py and imported by jupyter.
import inspect
import types
def execute_source(callback_imports, callback_name, callback_source, args):
for callback_import in callback_imports:
exec(callback_import, globals())
exec('import time' + "\n" + callback_source)
callback = locals()[callback_name]
return callback(*args)
def submit(executor, callback, *args):
callback_source = inspect.getsource(callback)
callback_imports = list(imports(callback.__globals__))
callback_name = callback.__name__
future = executor.submit(
execute_source,
callback_imports, callback_name, callback_source, args
)
return future
def imports(callback_globals):
for name, val in list(callback_globals.items()):
if isinstance(val, types.ModuleType) and val.__name__ != 'builtins' and val.__name__ != __name__:
import_line = 'import ' + val.__name__
if val.__name__ != name:
import_line += ' as ' + name
yield import_line
Here is how you would use this:
from concurrent.futures import as_completed, ProcessPoolExecutor
import time
import numpy as np
import winprocess
def do_work(idx1, idx2):
time.sleep(0.2)
return np.mean([idx1, idx2])
with ProcessPoolExecutor(max_workers=4) as executor:
futures = set()
for idx in range(32):
future = winprocess.submit(
executor, do_work, idx, idx * 2
)
futures.add(future)
for future in as_completed(futures):
print(future.result())
Notice that executor has been changed with winprocess and the original executor is passed to the submit function as a parameter.
What happens here is that the notebook function code and imports are serialized and passed to the module for execution. The code is not executed until it is safely in a new process, thus does not trip up with trying to make a new process based on the jupyter notebook itself.
Imports are handled in such a way as to maintain aliases. The import magic can be removed if you make sure to import everything needed for the function being executed inside the function itself.
Also, this solution only works if you pass all necessary variables as arguments to the function. The function should be static so to speak, but I think that's a requirement of ProcessPoolExecutor as well. Finally, make sure you don't execute other functions defined elsewhere in the notebook. Only external modules will be imported, thus other notebook functions won't be included.
I have the basic PBS Controller and EngineSet Launchers working with my PBS/torque cluster. But now I'd like to pass in additional options through the config file and/or the command line to be rendered in the batch_template file using the {} template formatter syntax.
As an example, I want to run:
ipcluster start -n 10 --pmem=10gb
And then have, in my template:
#PBS -l pmem={pmem}
I guess that I have to subclass the PBSControllerLauncher & PBSEngineSetLauncher classes? This is mentioned in the documentation, but I can't find a clear example of how to do it.
Simply inheriting from these classes and adding pmem = Unicode(...) as a class attribute doesn't make pmem available in the template's context when it calls write_batch_script.
I can access the config options and insert it into the context, as shown below. This code works, but is pretty awkward and doesn't seem like the right way to do it:
from IPython.parallel.apps.launcher import PBSControllerLauncher, PBSEngineSetLauncher, BatchClusterAppMixin, ipcontroller_cmd_argv, ipengine_cmd_argv
from IPython.utils.traitlets import (
Any, Integer, CFloat, List, Unicode, Dict, Instance, HasTraits, CRegExp
)
import pipes
class MyPBSLauncherExtension(object):
def update_context(self):
cfg = self.config
ctx = self.context
cls = type(self).__name__
for c in ('extra_pbs_directives', 'extra_modules'):
if cls in cfg:
if c in cfg[cls]:
ctx[c] = cfg[cls][c]
continue
if c in cfg:
ctx[c] = cfg[c]
else:
ctx[c] = getattr(self, c)
class MyPBSControllerLauncher(PBSControllerLauncher, MyPBSLauncherExtension):
extra_pbs_directives = Unicode(u'', config=True, help='')
extra_modules = Unicode(u'', config=True, help='')
def write_batch_script(self, n):
self.update_context()
super(MyPBSControllerLauncher, self).write_batch_script(n)
class MyPBSEngineSetLauncher(PBSEngineSetLauncher, MyPBSLauncherExtension):
extra_pbs_directives = Unicode(u'', config=True, help='')
extra_modules = Unicode(u'', config=True, help='')
def write_batch_script(self, n):
self.update_context()
super(MyPBSEngineSetLauncher, self).write_batch_script(n)
Would very much appreciate a simple example that adds an additional option to a launcher subclass.
I'm really puzzled by this, but the answer is probably quite simple and just can't see it:
I have a series of python modules that work fine from within the python interpreter, but nothing happens when running from a GUI situation. I've tried creating a .desktop file, adding shebangs, changing permissions to 777 and renaming to .pyw for all the modules. A single test module works fine on its own, so I know that it's not a typo error.
If I click the main module .pyw file and click 'Run' from the system dialogue nothing happens at all. Similarly the .py file (and the .desktop via menu)... nothing. Here is the start of my code:#
#!/usr/bin/python3
import tkinter as tk, imp, sys
root = tk.Tk()
msg = tk.messagebox
sdg = tk.simpledialog
import capitaliser_cfg as cfg, fileio as io
imp.reload(cfg) ; imp.reload(io)
### GO AND GET COUNTY LIST ####
# Nb: attach to config for simplicity
cfg.counties = io.getfilelist("counties.txt", "London")
if not type(cfg.counties)==list:
k = msg.showerror(cfg.version, cfg.counties)
root.destroy()
root.mainloop()
### GO AND GET DICTIONARY ####
cfg.tempdict = [[],[],[]]
cfg.spelldict = io.getdictionary("addressdict.txt","roda","Road")
if not type(cfg.spelldict)==dict:
k = msg.showerror(cfg.version, cfg.spelldict)
root.destroy()
root.mainloop()
import thinbutton as tb, labelradio as lr, fieldblock as fb, bigbutton as bb
import textblock as tx, padding as pd, widget_tools as wt
import capitaliser_mth as mth
import capitaliser_bnd as bnd
imp.reload(tb) ; imp.reload(lr) ; imp.reload(fb) ; imp.reload(bb)
imp.reload(tx) ; imp.reload(pd) ; imp.reload(wt) ;
imp.reload(mth)
imp.reload(bnd)
If I put k = msg.showerror("xxxx","yyyy") after the line sdg = tk.simpledialog, still nothing happens which leads me to believe that tkinter is not loading for some reason.
Any ideas anyone ?
For Python 2 try:
import tkMessageBox
import tkSimpleDialog
msg = tkMessageBox
sdg = tkSimpleDialog
or simpler:
import tkMessageBox as msg
import tkSimpleDialog as sdk
For Python 3 try:
from tkinter import messagebox
from tkinter import simpledialog
msg = messagebox
sdg = simpledialog
or simpler:
from tkinter import messagebox as msg
from tkinter import simpledialog as sdg
I am currently writing a wrapper for a small console program I wrote.
The c program needs a password string as input and because I intend to use it through dmenu and such, I'd like to use a little gtk entry box to enter that string.
However, I have to fork after I get the input (because I'm also handling clipboard stuff which needs deletion after some time) and the window simply won't close until the child process exits.
from subprocess import Popen, PIPE
from gi.repository import Gtk
import sys
import os
import time
import getpass
HELP_MSG = "foobar [options] <profile>"
class EntryDialog(Gtk.Dialog):
def run(self):
result = super(EntryDialog, self).run()
if result == Gtk.ResponseType.OK:
text = self.entry.get_text()
else:
text = None
return text
def __init__(self):
super(EntryDialog, self).__init__()
entry = Gtk.Entry()
entry.set_visibility(False)
entry.connect("activate",
lambda ent, dlg, resp:
dlg.response(resp),
self,
Gtk.ResponseType.OK)
self.vbox.pack_end(entry, True, True, 0)
self.vbox.show_all()
self.entry = entry
def get_pwd():
if sys.stdin.isatty():
return getpass.getpass()
else:
prompt = EntryDialog()
prompt.connect("destroy", Gtk.main_quit)
passwd = prompt.run()
prompt.destroy()
return passwd
The thought is, that it should close when I hit enter, but I'm pretty sure I'm doing something entirely wrong.
The script basically continues like this:
profile = argv[0]
pwd = get_pwd()
if pwd is None:
print(HELP_MSG)
sys.exit()
out = doStuff()
text_to_clipboard(out)
# now fork and sleep!
if os.fork():
sys.exit()
time.sleep(10)
clear_clipboard()
sys.exit(0)
I dropped the python wrapper and wrote it directly in c. However, for anyone having the same problem, the (untested) solution would be to add a function
def quit():
self.emit("destroy")
where 'self' is the dialog box - and connect that to the "activate" signal,
entry.connect("activate", quit)
so that the dialog widget emits the destroy signal as soon as the user hits Return and thus Gtk.main_quit gets called.
In c the content can be extracted nicely by specifying a GtkEntryBuffer and calling it's
gtk_entry_buffer_get_text()
I didn't find it right now, but there is probably an equivalent for pygtk available.