Skip to content

Commit f23b6d1

Browse files
author
mgotz
committed
added support for PyQt5 and Python 3
1 parent a71a63b commit f23b6d1

File tree

7 files changed

+237
-88
lines changed

7 files changed

+237
-88
lines changed

README.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
This is a collection of tools to work with PyQt4 GUIs in python.
1+
This is a collection of tools to work with PyQt4 and PyQt5 GUIs in python.
22
There are functions to load and save all the fields in an GUI, to make a simple matplotlib window and most interestingly a GUI handler for the python logger.
33
Credit for the logger goes mostly to Andreas Schuhman, from whom I shameless copied most of it.
44

5-
Obviously depends on PyQt4, which, however, can not be specified as a dependency as it does not exist on PyPi.
5+
Obviously depends on PyQt4 or PyQt5. However, PyQt4 can not be specified as a dependency as it does not exist on PyPi.

mg/pyguitools/Log.py

Lines changed: 80 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,44 @@
88
from MyGui import Log
99
Log.run()
1010
"""
11-
12-
from PyQt4 import QtCore, QtGui
11+
#get compatible to python3
12+
from __future__ import absolute_import, division, print_function
1313

1414
import os
15+
16+
#enable compatibility to both pyqt4 and pyqt5
17+
_modname = os.environ.setdefault('QT_API', 'pyqt')
18+
assert _modname in ('pyqt', 'pyqt5')
19+
20+
if os.environ['QT_API'].startswith('pyqt'):
21+
try:
22+
if os.environ['QT_API'] == 'pyqt5':
23+
from PyQt5.QtWidgets import (QApplication, QDockWidget, QFileDialog,
24+
QMainWindow)
25+
26+
### import ui created with qtdesigner
27+
### create python file with:
28+
### pyuic5 Log.ui > ui_Log_qt5.py
29+
from ui_Log_qt5 import Ui_DockWidget
30+
31+
from PyQt5 import QtCore
32+
33+
else:
34+
from PyQt4.QtGui import (QApplication, QDockWidget, QFileDialog,
35+
QMainWindow)
36+
37+
### import ui created with qtdesigner
38+
### create python file with:
39+
### pyuic4 Log.ui > ui_Log_qt4.py
40+
from ui_Log_qt4 import Ui_DockWidget
41+
42+
from PyQt4 import QtCore
43+
except ImportError as e:
44+
print (e)
45+
raise ImportError("GUI Logger requires PyQt4 or PyQt5. "
46+
"QT_API: {!s}".format(os.environ['QT_API']))
47+
48+
1549
import logging
1650

1751

@@ -79,7 +113,7 @@ def emit(self, record):
79113
self.logSig.emit_log(text)
80114
### otherwise just print log message
81115
else:
82-
print text
116+
print(text)
83117

84118

85119
class LogFormat(logging.Formatter):
@@ -99,59 +133,40 @@ class LogFormat(logging.Formatter):
99133
# err_fmt = "[ERROR] %(module)s:%(funcName)s >> %(msg)s"
100134
#My Formattes (prints just the messages):
101135
#
102-
dbg_fmt = "[DEBUG]%(asctime)s %(msg)s"
103-
info_fmt = "[INFO]%(asctime)s %(msg)s"
104-
warn_fmt = "[WARNING]%(asctime)s %(msg)s"
105-
err_fmt = "[ERROR]%(asctime)s %(msg)s"
106-
crit_fmt = "[CRITICAL]%(asctime)s %(msg)s"
136+
level_translation = {"10: ":"[DEBUG] ",
137+
"20: ":"[INFO] ",
138+
"30: ":"[WARNING] ",
139+
"40: ":"ERROR ",
140+
"50: ":"CRITICAL "}
107141
datefmt = ""
108142

109-
def __init__(self, fmt="%(levelno)s: %(msg)s",datefmt=None,infoString=True):
143+
def __init__(self, fmt="%(levelno)s: %(asctime)s %(msg)s", datefmt=None,
144+
infoString=True):
110145
logging.Formatter.__init__(self, fmt,datefmt)
111146
self.datefmt = datefmt
147+
112148

113149
if not infoString:
114-
self.info_fmt = "%(asctime)s %(msg)s"
150+
self.level_tranlsation["20: "] = ""
151+
# self.info_fmt = "%(asctime)s %(msg)s"
115152

116153
def format(self, record):
117-
118-
# Save the original format configured by the user
119-
# when the logger formatter was instantiated
120-
format_orig = self._fmt
121-
122-
# Replace the original format with one customized by logging level
123-
if record.levelno == 10: # DEBUG
124-
self._fmt = self.dbg_fmt
125-
126-
elif record.levelno == 20: # INFO
127-
self._fmt = self.info_fmt
128-
129-
elif record.levelno == 30: # WARNING
130-
self._fmt = self.warn_fmt
131-
132-
elif record.levelno == 40: # ERROR
133-
self._fmt = self.err_fmt
134-
135-
elif record.levelno == 50: # CRITICAL
136-
self._fmt = self.crit_fmt
154+
# Call the base class formatter to do the grunt work
155+
result = logging.Formatter.format(self, record)
156+
157+
#replace the numeric logging levels with strings
158+
result = self.level_translation[result[0:4]] + result[4:]
159+
137160

138161

139-
# Call the original formatter class to do the grunt work
140-
result = logging.Formatter.format(self, record)
141162

142-
# Restore the original format configured by the user
143-
self._fmt = format_orig
144163

145164
return result
146165

147166

148167

149-
### import ui created with qtdesigner
150-
### create python file with:
151-
### pyuic4 Log.ui > ui_Log.py
152-
from ui_Log import Ui_DockWidget
153168

154-
class QtDockLog(QtGui.QDockWidget):
169+
class QtDockLog(QDockWidget):
155170
"""
156171
Log Dock Widget to use in a PyQt GUI
157172
Will output all the logging.info(), logging.debug() etc. info
@@ -181,7 +196,7 @@ def __init__(self,datefmt=None,infoString = True):
181196
if false info messages are not prefixed with the log level string
182197
"""
183198

184-
QtGui.QDockWidget.__init__(self)
199+
QDockWidget.__init__(self)
185200

186201
# Set up the user interface from Designer.
187202
self.ui = Ui_DockWidget()
@@ -213,12 +228,16 @@ def __init__(self,datefmt=None,infoString = True):
213228
lambda: self.setLevel(self.ui.comboBox.currentText()))
214229
self.ui.pushButtonSave.clicked.connect(self.saveLog)
215230

216-
217231
def saveLog(self):
218232
"""
219233
Saves the shown log to file 'filename'
220234
"""
221-
savePath =QtGui.QFileDialog.getSaveFileName(self,'select save file')
235+
savePath =QFileDialog.getSaveFileName(self,caption='select save file',
236+
filter="Text files (*.txt);;All files (*)")
237+
#in pyqt5 a tuple is returned, unpack it
238+
if os.environ['QT_API'] == 'pyqt5':
239+
savePath, _ = savePath
240+
222241
if savePath != '':
223242
text = str(self.ui.textBrowser.toPlainText()) # get log text
224243
f = open(savePath, 'w') # open file
@@ -236,29 +255,28 @@ def setLevel(self, level):
236255
# clear the text window
237256
self.ui.textBrowser.clear()
238257
# open the log file
239-
f = open(self.filename, 'r')
240-
# go through lines in log file
241-
for line in f:
242-
line = line.strip() # delete end of line
243-
try:
244-
level_line = line.split('[')[1].split(']')[0] # get level of line
245-
except IndexError:
246-
level_line = "INFO" #default to info
247-
248-
level_line_int = eval("logging.%s" %(level_line))
249-
# if number of line level is greater equal to number of
250-
# the global level, append to text in color
251-
if level_line_int >= level_int:
252-
text = "<font color='%s'> %s </font>" \
253-
% ( self.handler.COLORS[level_line], line)
254-
self.ui.textBrowser.append(text)
255-
# close file
256-
f.close()
258+
with open(self.filename, 'r') as f:
259+
# go through lines in log file
260+
for line in f:
261+
line = line.strip() # delete end of line
262+
try:
263+
level_line = line.split('[')[1].split(']')[0] # get level of line
264+
except IndexError:
265+
level_line = "INFO" #default to info
266+
267+
level_line_int = eval("logging.%s" %(level_line))
268+
# if number of line level is greater equal to number of
269+
# the global level, append to text in color
270+
if level_line_int >= level_int:
271+
text = "<font color='%s'> %s </font>" \
272+
% ( self.handler.COLORS[level_line], line)
273+
self.ui.textBrowser.append(text)
257274

258275
cmd = "self.handler.setLevel(logging.%s)" % (level)
259276
eval(cmd)
260277
### print new level
261278
self.logger.debug("Logging level set to %s." %(level))
279+
262280

263281
### pyQt SLOT used to receive emitted SIGNAL from LogHandler()
264282
### pass self to LogHandler to make this work
@@ -271,8 +289,8 @@ def printLog(self, text):
271289
def run():
272290
import sys
273291

274-
app = QtGui.QApplication(sys.argv)
275-
win = QtGui.QMainWindow()
292+
app = QApplication(sys.argv)
293+
win = QMainWindow()
276294
win.addDockWidget(QtCore.Qt.TopDockWidgetArea, QtDockLog())
277295
win.show()
278296
sys.exit(app.exec_())

mg/pyguitools/gui_save_and_load.py

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,28 @@
44
functions to save and restore pyqt gui contents
55
"""
66

7-
from PyQt4 import QtGui
7+
#get compatible to python3
8+
from __future__ import absolute_import, division, print_function
9+
from builtins import str #use instead of pyhton2 unicode()
10+
11+
import os
12+
13+
#enable compatibility to both pyqt4 and pyqt5
14+
_modname = os.environ.setdefault('QT_API', 'pyqt')
15+
assert _modname in ('pyqt', 'pyqt5')
16+
17+
if os.environ['QT_API'].startswith('pyqt'):
18+
try:
19+
if os.environ['QT_API'] == 'pyqt5':
20+
from PyQt5.QtWidgets import (QComboBox, QLineEdit, QCheckBox,
21+
QSpinBox, QDoubleSpinBox)
22+
else:
23+
from PyQt4.QtGui import (QComboBox, QLineEdit, QCheckBox,
24+
QSpinBox, QDoubleSpinBox)
25+
except ImportError:
26+
raise ImportError("plot_window requires PyQt4 or PyQt5. "
27+
"QT_API: {!s}".format(os.environ['QT_API']))
28+
829
import inspect
930

1031
def gui_save(ui, settings):
@@ -25,20 +46,20 @@ def gui_save(ui, settings):
2546
#get all the children of the ui
2647
for name, obj in inspect.getmembers(ui):
2748
#different fields need slightly different things to be saved
28-
if isinstance(obj, QtGui.QComboBox):
49+
if isinstance(obj, QComboBox):
2950
name = obj.objectName() # get combobox name
3051
index = obj.currentIndex() # get current index from combobox
3152
text = obj.itemText(index) # get the text for current index
3253
settings.setValue(name, text) # save combobox selection to settings
33-
if isinstance(obj, QtGui.QLineEdit):
54+
if isinstance(obj, QLineEdit):
3455
name = obj.objectName()
3556
value = str(obj.text())
3657
settings.setValue(name, value)
37-
if isinstance(obj, QtGui.QCheckBox):
58+
if isinstance(obj, QCheckBox):
3859
name = obj.objectName()
3960
state = obj.checkState()
4061
settings.setValue(name, state)
41-
if isinstance(obj, QtGui.QSpinBox) or isinstance(obj, QtGui.QDoubleSpinBox):
62+
if isinstance(obj, QSpinBox) or isinstance(obj, QDoubleSpinBox):
4263
name = obj.objectName()
4364
value = obj.value()
4465
settings.setValue(name,value)
@@ -60,9 +81,9 @@ def gui_restore(ui, settings):
6081

6182
#iterate over the child items and try to load something for each item
6283
for name, obj in inspect.getmembers(ui):
63-
if isinstance(obj, QtGui.QComboBox):
84+
if isinstance(obj, QComboBox):
6485
name = obj.objectName()
65-
value = unicode(settings.value(name))
86+
value = str(settings.value(name))
6687

6788
if value == "":
6889
continue
@@ -76,16 +97,16 @@ def gui_restore(ui, settings):
7697
else:
7798
obj.setCurrentIndex(index) # set the correct index otherwise
7899

79-
if isinstance(obj, QtGui.QLineEdit):
100+
if isinstance(obj, QLineEdit):
80101
name = obj.objectName()
81102
try:
82103
value = settings.value(name,type=str) # get stored value from registry
83104
except TypeError:
84105
value = None
85106
if value != None:
86-
obj.setText(unicode(value)) # restore lineEditFile
107+
obj.setText(str(value)) # restore lineEditFile
87108

88-
if isinstance(obj, QtGui.QCheckBox):
109+
if isinstance(obj, QCheckBox):
89110
name = obj.objectName()
90111
try:
91112
value = settings.value(name,type=bool) # get stored value from registry
@@ -94,7 +115,7 @@ def gui_restore(ui, settings):
94115
if value != None:
95116
obj.setChecked(value) # restore checkbox
96117

97-
if isinstance(obj, QtGui.QSpinBox):
118+
if isinstance(obj, QSpinBox):
98119
name = obj.objectName()
99120
try:
100121
value = settings.value(name,type=int)
@@ -103,7 +124,7 @@ def gui_restore(ui, settings):
103124
if value != None:
104125
obj.setValue(value)
105126

106-
if isinstance(obj, QtGui.QDoubleSpinBox):
127+
if isinstance(obj, QDoubleSpinBox):
107128
name = obj.objectName()
108129
try:
109130
value = settings.value(name,type=float)

0 commit comments

Comments
 (0)