diff --git a/DESCRIPTION.rst b/DESCRIPTION.rst index 5e12356..7e5f8e6 100644 --- a/DESCRIPTION.rst +++ b/DESCRIPTION.rst @@ -1,9 +1,6 @@ pyILPER -======================= +======= -This python application provides virtual HP-IL devices for -the PIL-Box of Jean-Francois Garnier. Code was taken from -ILPER 1.4.5 for Windows (Copyright (c) 2008-2013 J-F Garnier, -Visual C++ version by Christoph Gießelink 2013. The -Terminal emulator widget was taken from pyqterm console -widget by Henning Schroeder. +pyILPER emulates virtual peripheral devices for HP-IL (Hewlett-Packard Interface Loop). + +Please visit the project page at https://github.com/bug400/pyilper for further information. diff --git a/INSTALL.md b/INSTALL.md index eb89553..949023d 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -10,6 +10,7 @@ Index * [Setup](#setup) * [Operation](#operation) * [Installation without the ANACONDA platform](#installation-without-the-anaconda-platform) +* [Installation with Qt6 and PIP](#installation-with-qt6-and-pip) * [Installation of beta or development versions](#installation-of-beta-or-development-versions) General @@ -17,11 +18,10 @@ General pyILPER requires: -* Python 3.5 or higher (Python 3.10 is only supported for pyILPER 1.8.6 and above) -* QT 5.6 or higher -* PyQt compatible to the Python and Qt version +* Python 3.5 or higher +* Qt 5.9 or higher with the PyQt5 language bindings or Qt 6.3 with the PySide6 language bindings * The Python bindings for either Qt Webkit or Qt Webengine -* Pyserial 2.7 +* Pyserial 2.7 or higher * [LIFUTILS](https://github.com/bug400/lifutils/releases) (the most recent version) It is recommended to use the [ANACONDA platform](https://www.continuum.io) @@ -165,6 +165,47 @@ directory and type: in a terminal window. +Installation with Qt6 and PIP +----------------------------- + +PySide6 is not available from the ANACONDA platform for now. + +Get a Python interpreter for your system first: + +* Windows: get it from the Microsoft Store +* macOS: download it from the [www.python.org](https://www.python.org) website +* Linux: install it from your repositories if not already installed on your system + +Now create a virtual Python environment for pyILPER. This environment contains +the additional software which is needed to run pyILPER. The virtual environment +is created as a subdirectory in the user's home directory. It can be easily +removed. + +To create the virtual environmnent "pyilper" open a terminal windows (or command shell) and type: + + python -m venv pyilper + +This creates a pyilper subdirectory in your home directory. + +Now activate the new environment: + + source pyilper/bin/activate (Linux, mac OS) + pyilper\Scripts\acivate (Windows) + +Before you run this command, make sure that you’re in the folder that contains the virtual environment you just created. If you can see the name of the environment +in your command prompt, then the environment is active. + +Now install additional software and type: + + python -m pip install PySide6 pyserial + +Install and run the pyILPER software as described in the next section. + +You can deactivate the virtual environment with the command: + + deactivate + + Installation of beta or development versions -------------------------------------------- diff --git a/debian/changelog b/debian/changelog index cfa0bca..527b556 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,10 @@ +pyilper (1.8.6~beta2) bullseye; urgency=medium + + * enabled PySide6 language binding in addition to PyQt5 + * tty device selection combo box improved + + -- focal Fri, 29 Jul 2022 14:09:44 +0200 + pyilper (1.8.6~beta1) focal; urgency=medium * remove deprecated language features which are errors under Python 3.10 diff --git a/pyilper/Manual/changelog.html b/pyilper/Manual/changelog.html index 95f6ac2..2374d17 100644 --- a/pyilper/Manual/changelog.html +++ b/pyilper/Manual/changelog.html @@ -309,7 +309,9 @@

pyILPER 1.8.5

pyILPER 1.8.6

diff --git a/pyilper/Manual/releasenotes.html b/pyilper/Manual/releasenotes.html index 9643b49..dfeb3dc 100644 --- a/pyilper/Manual/releasenotes.html +++ b/pyilper/Manual/releasenotes.html @@ -31,7 +31,9 @@

Release Notes for pyILPER 1.8.6

diff --git a/pyilper/lifexec.py b/pyilper/lifexec.py index 76be5cd..c331391 100644 --- a/pyilper/lifexec.py +++ b/pyilper/lifexec.py @@ -100,12 +100,13 @@ # - show HP-75 text files optional with or without line numbers # 04.01.2021 jsi # - exec_single did not get stderr output of executed program +# 04.04.2022 jsi +# - PySide6 migration # import subprocess import tempfile import os import pathlib -from PyQt5 import QtCore, QtGui, QtWidgets from .lifcore import * from .pilcharconv import barrconv from .pilcore import isWINDOWS, FONT, decode_version, PDF_ORIENTATION_PORTRAIT @@ -113,6 +114,12 @@ from .pilconfig import PILCONFIG if isWINDOWS(): import winreg +from .pilcore import QTBINDINGS +if QTBINDINGS=="PySide6": + from PySide6 import QtCore, QtGui, QtWidgets +if QTBINDINGS=="PyQt5": + from PyQt5 import QtCore, QtGui, QtWidgets + PDF_MARGINS=100 BARCODE_HEIGHT=100 @@ -281,7 +288,7 @@ def exec_double_import(parent,cmd1,cmd2,inputfile): # execute second command # tmpfile.seek(0) - if not cls_chk_import.exec(tmpfile.fileno(), None): + if not cls_chk_import.execute(tmpfile.fileno(), None): tmpfile.close() return tmpfile.seek(0) @@ -359,8 +366,8 @@ def exec_double_export(parent,cmd1,cmd2,outputfile): class cls_LIF_validator(QtGui.QValidator): def validate(self,string,pos): - self.regexp = QtCore.QRegExp('[A-Za-z][A-Za-z0-9]*') - self.validator = QtGui.QRegExpValidator(self.regexp) + self.regexp = QtCore.QRegularExpression('[A-Za-z][A-Za-z0-9]*') + self.validator = QtGui.QRegularExpressionValidator(self.regexp) result=self.validator.validate(string,pos) return result[0], result[1].upper(), result[2] # @@ -372,7 +379,7 @@ def __init__(self,parent= None): super().__init__() @staticmethod - def exec(lifimagefile): + def execute(lifimagefile): d=cls_lifpack() reply = QtWidgets.QMessageBox.question(d, 'Message', 'Do you really want to pack the LIF image file', QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No) if reply == QtWidgets.QMessageBox.Yes: @@ -475,7 +482,7 @@ def __init__(self): super().__init__() @staticmethod - def exec (lifimagefile, liffilename, ft,papersize): + def execute (lifimagefile, liffilename, ft,papersize): d= cls_lifbarcode() # # get output file name @@ -533,7 +540,7 @@ def __init__(self,parent= None): super().__init__() @staticmethod - def exec(lifimagefile,liffile): + def execute(lifimagefile,liffile): d=cls_lifpurge() reply = QtWidgets.QMessageBox.question(d, 'Message', 'Do you really want to purge '+liffile, QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No) if reply == QtWidgets.QMessageBox.Yes: @@ -594,9 +601,9 @@ def do_cancel(self): super().reject() @staticmethod - def exec(lifimagefile,liffile): + def execute(lifimagefile,liffile): d=cls_lifrename(lifimagefile,liffile) - result= d.exec_() + result= d.exec() # # export file dialog # @@ -768,9 +775,9 @@ def do_cancel(self): super().reject() @staticmethod - def exec(lifimagefile,liffilename,liffiletype,workdir): + def execute(lifimagefile,liffilename,liffiletype,workdir): d=cls_lifexport(lifimagefile,liffilename,liffiletype,workdir) - result= d.exec_() + result= d.exec() # # label lif image file dialog @@ -812,9 +819,9 @@ def do_cancel(self): super().reject() @staticmethod - def exec(lifimagefile,oldlabel): + def execute(lifimagefile,oldlabel): d=cls_liflabel(lifimagefile,oldlabel) - result= d.exec_() + result= d.exec() # # import file dialog # @@ -953,7 +960,7 @@ def do_checkenable(self): def do_ok(self): if self.inputfile != "": if self.radioNone.isChecked(): - if cls_chk_import.exec(None, self.inputfile): + if cls_chk_import.execute(None, self.inputfile): exec_single(self,[add_path("lifput"),self.lifimagefile,self.inputfile]) else: self.liffilename=self.leditFileName.text() @@ -980,9 +987,9 @@ def do_cancel(self): super().reject() @staticmethod - def exec(lifimagefile,workdir): + def execute(lifimagefile,workdir): d=cls_lifimport(lifimagefile,workdir) - result= d.exec_() + result= d.exec() # # check import dialog, ensure that we import a valid LIF transport file # @@ -1053,8 +1060,8 @@ def __init__(self,fd,inputfile,parent=None): if self.filetype=="Unknown": self.lblMessage.setText("Unknown file type") return - self.regexp = QtCore.QRegExp('[A-Z][A-Z0-9]*') - self.validator = QtGui.QRegExpValidator(self.regexp) + self.regexp = QtCore.QRegularExpression('[A-Z][A-Z0-9]*') + self.validator = QtGui.QRegularExpressionValidator(self.regexp) result=self.validator.validate(self.filename,0)[0] if result != QtGui.QValidator.Acceptable: self.lblMessage.setText("Illegal file name") @@ -1077,9 +1084,9 @@ def get_retval(self): @staticmethod - def exec(fd,inputfile): + def execute(fd,inputfile): d=cls_chk_import(fd,inputfile) - result= d.exec_() + result= d.exec() return d.get_retval() # # check xroms dialog @@ -1163,9 +1170,9 @@ def get_call(self): return self.call @staticmethod - def exec(): + def execute(): d=cls_chkxrom() - result= d.exec_() + result= d.exec() return d.get_call() # # view file dialog @@ -1250,7 +1257,7 @@ def do_exit(self): # get file and pipe it to filter program, show output in editor window # @staticmethod - def exec(lifimagefile, liffilename, liffiletype,workdir,charset): + def execute(lifimagefile, liffilename, liffiletype,workdir,charset): d=cls_lifview(workdir) ft=get_finfo_name(liffiletype) call= get_finfo_type(ft)[1] @@ -1258,7 +1265,7 @@ def exec(lifimagefile, liffilename, liffiletype,workdir,charset): # decomp41 needs additional parameters (xmoms) # if call == "decomp41": - call= cls_chkxrom.exec() + call= cls_chkxrom.execute() # # liftext75 has the option to show line numbers # @@ -1279,7 +1286,7 @@ def exec(lifimagefile, liffilename, liffiletype,workdir,charset): if output is None: return d.set_text(barrconv(output,charset)) - result= d.exec_() + result= d.exec() # # Init LIF image file dialog # @@ -1341,8 +1348,8 @@ def __init__(self,workdir,parent= None): self.leditDirSize=QtWidgets.QLineEdit(self) self.leditDirSize.setText("500") self.leditDirSize.setMaxLength(4) - self.regexpDirSize = QtCore.QRegExp('[1-9][0-9]*') - self.validatorDirSize = QtGui.QRegExpValidator(self.regexpDirSize) + self.regexpDirSize = QtCore.QRegularExpression('[1-9][0-9]*') + self.validatorDirSize = QtGui.QRegularExpressionValidator(self.regexpDirSize) self.leditDirSize.setValidator(self.validatorDirSize) self.leditDirSize.textChanged.connect(self.do_checkenable) self.hbox1.addWidget(self.leditDirSize) @@ -1455,9 +1462,9 @@ def do_cancel(self): super().reject() @staticmethod - def exec(workdir): + def execute(workdir): d=cls_lifinit(workdir) - result= d.exec_() + result= d.exec() # # fix LIF header dialog # @@ -1593,9 +1600,9 @@ def do_cancel(self): super().reject() @staticmethod - def exec(workdir): + def execute(workdir): d=cls_liffix(workdir) - result= d.exec_() + result= d.exec() # # Check installation of LIFUTILS dialog # @@ -1634,6 +1641,6 @@ def do_exit(self): super().accept() @staticmethod - def exec(): + def execute(): d=cls_installcheck() - result= d.exec_() + result= d.exec() diff --git a/pyilper/penconfig.py b/pyilper/penconfig.py index 4249a83..670ff3d 100644 --- a/pyilper/penconfig.py +++ b/pyilper/penconfig.py @@ -29,9 +29,16 @@ # - cls_PenConfigWindow moved from pilplotter.py # 12.12.2021 jsi: # - add configversion parameter to open method +# 04.05.2022 jsi: +# - PySide6 migration # import copy -from PyQt5 import QtCore, QtGui, QtWidgets +from .pilcore import QTBINDINGS +if QTBINDINGS=="PySide6": + from PySide6 import QtCore, QtGui, QtWidgets +if QTBINDINGS=="PyQt5": + from PyQt5 import QtCore, QtGui, QtWidgets + from .userconfig import cls_userconfig, ConfigError # # Plotter pen table model class -------------------------------------------- @@ -159,7 +166,7 @@ def do_reset(self): def getPenConfig(): dialog= cls_PenConfigWindow() dialog.resize(650,600) - result= dialog.exec_() + result= dialog.exec() if result== QtWidgets.QDialog.Accepted: return True else: diff --git a/pyilper/pilcore.py b/pyilper/pilcore.py index 3a108b8..135a044 100644 --- a/pyilper/pilcore.py +++ b/pyilper/pilcore.py @@ -20,7 +20,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # -# pyILPER support and constants --------------------------------------------------- +# pyILPER support and constants ----------------------------------------------- # # Changelog # 08.07.16 - jsi: @@ -137,18 +137,21 @@ # 18.04.22 jsi # - 1.8.6 beta1 # - used raw string in re.compile to avoid DEPRECATED warning +# 29.07.22 jsi +# - added determination of Qt bindings # import platform import os import re +import sys # # Program constants -------------------------------------------------- # # General constants: # PRODUCTION= False # Production/Development Version -VERSION="1.8.6b1" # pyILPR version number -CONFIG_VERSION="2" # Version number of pyILPER config file, must be string +VERSION="1.8.7b2" # pyILPR version number +CONFIG_VERSION="2" # Version number of pyILPER config file, must be string # # Python minimum version # @@ -242,6 +245,56 @@ BARCODE_WIDE_W= 10 BARCODE_SPACING= 5 +# +# determine QT Bindings +# +QTBINDINGS="None" +HAS_WEBENGINE=False +HAS_WEBKIT=False +# already loaded +for _b in('PyQt5','PySide6'): + if _b+'.QtCore' in sys.modules: + QTBINDINGS=_b + break +else: + try: + import PySide6.QtCore + except ImportError: + if "PySide6" in sys.modules: + del sys.modules["Pyside6"] + try: + import PyQt5.QtCore + except ImportError: + if "PyQt5" in sys.modules: + del sys.modules["Pyside6"] + raise ImportError("No Qt bindings found") + else: + QTBINDINGS="PyQt5" + from PyQt5 import QtPrintSupport + QT_FORM_A4=QtPrintSupport.QPrinter.A4 + QT_FORM_LETTER=QtPrintSupport.QPrinter.Letter + try: + from PyQt5 import QtWebKitWidgets + HAS_WEBKIT=True + except: + pass + try: + from PyQt5 import QtWebEngineWidgets + HAS_WEBENGINE=True + except: + pass + if HAS_WEBKIT and HAS_WEBENGINE: + HAS_WEBENGINE=False + else: + QTBINDINGS="PySide6" + from PySide6 import QtGui + QT_FORM_A4=QtGui.QPageSize.A4 + QT_FORM_LETTER=QtGui.QPageSize.Letter + try: + from PySide6 import QtWebEngineWidgets + HAS_WEBENGINE=True + except: + pass # # utility functions -------------------------------------------------------------- # diff --git a/pyilper/pildrive.py b/pyilper/pildrive.py index 14a7aee..58ab5a6 100644 --- a/pyilper/pildrive.py +++ b/pyilper/pildrive.py @@ -122,12 +122,18 @@ # - lock pildevice if medium changes in raw drive tab # 20.12.2021 jsi # - open raw file: only existing files are allowed +# 04.05.2022 jsi +# - PySide6 migration # -from PyQt5 import QtCore, QtGui, QtWidgets import time import threading import os from .pilcore import * +if QTBINDINGS=="PySide6": + from PySide6 import QtCore, QtGui, QtWidgets +if QTBINDINGS=="PyQt5": + from PyQt5 import QtCore, QtGui, QtWidgets + from .pildevbase import cls_pildevbase from .pilwidgets import cls_tabgeneric, T_STRING, T_INTEGER, O_DEFAULT from .pilconfig import PilConfigError, PILCONFIG @@ -710,17 +716,17 @@ def do_drivetypeChanged(self): self.toggle_controls() def do_pack(self): - cls_lifpack.exec(self.filename) + cls_lifpack.execute(self.filename) self.lifdir.refresh() def do_import(self): workdir=PILCONFIG.get('pyilper','workdir') - cls_lifimport.exec(self.filename, workdir) + cls_lifimport.execute(self.filename, workdir) self.lifdir.refresh() def do_label(self): oldlabel=self.lifdir.getLabel() - cls_liflabel.exec(self.filename, oldlabel) + cls_liflabel.execute(self.filename, oldlabel) self.lifdir.refresh() def do_dirlist(self): @@ -889,6 +895,7 @@ def __init__(self,parent,papersize): # def mousePressEvent(self, event): if event.button()== QtCore.Qt.LeftButton: +#DEPRECATED pos row=self.indexAt(event.pos()).row() isSelected=False # @@ -953,24 +960,24 @@ def contextMenuEvent(self, event): if ft== 0xE080 or ft== 0xE0D0: barcodeAction= menu.addAction("Barcode") - action = menu.exec_(self.mapToGlobal(event.pos())) + action = menu.exec(self.mapToGlobal(event.pos())) if action is None: event.accept() return workdir=PILCONFIG.get('pyilper','workdir') charset=PILCONFIG.get(self.parent.parent.name,"charset") if action ==exportAction: - cls_lifexport.exec(imagefile,liffilename,liffiletype,workdir) + cls_lifexport.execute(imagefile,liffilename,liffiletype,workdir) elif action== purgeAction: - cls_lifpurge.exec(imagefile,liffilename) + cls_lifpurge.execute(imagefile,liffilename) self.parent.refresh() elif action== renameAction: - cls_lifrename.exec(imagefile,liffilename) + cls_lifrename.execute(imagefile,liffilename) self.parent.refresh() elif action== viewAction: - cls_lifview.exec(imagefile, liffilename, liffiletype,workdir, charset) + cls_lifview.execute(imagefile, liffilename, liffiletype,workdir, charset) elif action== barcodeAction: - cls_lifbarcode.exec(imagefile,liffilename,ft,self.papersize) + cls_lifbarcode.execute(imagefile,liffilename,ft,self.papersize) event.accept() class cls_LifDirWidget(QtWidgets.QWidget): diff --git a/pyilper/pilhp2225b.py b/pyilper/pilhp2225b.py index e318b83..1285002 100644 --- a/pyilper/pilhp2225b.py +++ b/pyilper/pilhp2225b.py @@ -1,4 +1,4 @@ -#!/usr/bin/python3 + #!/usr/bin/python3 # -*- coding: utf-8 -*- # pyILPER 1.2.4 for Linux # @@ -33,14 +33,22 @@ # 04.01.2018 jsi: # - support zero length graphics chunks # - switchable print color +# 04.05.2022 jsi: +# - PySide6 migration +# 04.05.2022 jsi +# - force background of printer to be always white (dark mode!) # import copy import queue import threading import re from math import floor -from PyQt5 import QtCore, QtWidgets, QtGui, QtPrintSupport -from .pilcore import UPDATE_TIMER, PDF_ORIENTATION_PORTRAIT +from .pilcore import * +if QTBINDINGS=="PySide6": + from PySide6 import QtCore, QtGui, QtWidgets,QtPrintSupport +if QTBINDINGS=="PyQt5": + from PyQt5 import QtCore, QtGui, QtWidgets,QtPrintSupport + from .pilconfig import PILCONFIG from .pilcharconv import charconv, barrconv, CHARSET_HP2225 from .pildevbase import cls_pildevbase @@ -108,7 +116,7 @@ # HP2225_COLOR_BLACK=0 HP2225_COLOR_RED=1 -HP2225_COLR_BLUE=2 +HP2225_COLOR_BLUE=2 HP2225_COLOR_GREEN=3 COLOR_NAMES= [ "black", "red", "blue", "green" ] HP2225_COLORS=[QtCore.Qt.black, QtCore.Qt.red, QtCore.Qt.blue, QtCore.Qt.green] @@ -672,6 +680,13 @@ def __init__(self,parent,name,papersize): self.name= name self.screenwidth= -1 self.printcolor= QtCore.Qt.black +# +# background of print area is always white +# + self.setAutoFillBackground(True) + p=self.palette() + p.setColor(self.backgroundRole(),QtCore.Qt.white) + self.setPalette(p) self.w=-1 self.h=-1 self.rows= 0 @@ -800,7 +815,7 @@ def do_resize(self): def pdf(self,filename,pdf_rows): self.printer=QtPrintSupport.QPrinter (QtPrintSupport.QPrinter.HighResolution) - self.printer.setOrientation(QtPrintSupport.QPrinter.Portrait) + self.printer.setPageOrientation(QtGui.QPageLayout.Landscape) self.printer.setOutputFormat(QtPrintSupport.QPrinter.PdfFormat) self.pdfscene=QtWidgets.QGraphicsScene() # @@ -814,7 +829,7 @@ def pdf(self,filename,pdf_rows): # A4 format is 8,27 inches x 11,7 inches # if self.papersize== PDF_FORMAT_A4: - self.printer.setPageSize(QtPrintSupport.QPrinter.A4) + self.printer.setPageSize(QT_FORM_A4) lmargin= 151 tmargin= 163 scene_w= 1280 + lmargin*2 @@ -824,7 +839,7 @@ def pdf(self,filename,pdf_rows): # # Letter format is 8.5 inches x 11 inches # - self.printer.setPageSize(QtPrintSupport.QPrinter.Letter) + self.printer.setPageSize(QT_FORM_LANDSCAPE) lmargin= 173 tmargin= 96 scene_w= 1280 + lmargin*2 diff --git a/pyilper/pilhp82162a.py b/pyilper/pilhp82162a.py index e07a9a6..5a659f0 100644 --- a/pyilper/pilhp82162a.py +++ b/pyilper/pilhp82162a.py @@ -67,13 +67,21 @@ # - shrink all parent widgets if the pixel size was changed # 22.02.2018 jsi # - disabled shrinking parent widgets becaus of errors on reconfiguration +# 04.05.2022 jsi +# - PySide6 migration +# 04.05.2022 jsi +# - force printer background to be always white (dark mode) # import copy import queue import threading import re -from PyQt5 import QtCore, QtWidgets -from .pilcore import UPDATE_TIMER, PDF_ORIENTATION_PORTRAIT +from .pilcore import UPDATE_TIMER, PDF_ORIENTATION_PORTRAIT, QTBINDINGS +if QTBINDINGS=="PySide6": + from PySide6 import QtCore, QtWidgets +if QTBINDINGS=="PyQt5": + from PyQt5 import QtCore, QtWidgets + from .pilconfig import PILCONFIG from .pilcharconv import charconv, CHARSET_HP41, CHARSET_ROMAN8 from .pildevbase import cls_pildevbase @@ -933,6 +941,14 @@ def __init__(self,parent,name,pixelsize,pdfpixelsize,papersize,linebuffersize): self.linebuffersize= linebuffersize self.papersize= papersize # +# background is always white +# + self.setAutoFillBackground(True) + p=self.palette() + p.setColor(self.backgroundRole(),QtCore.Qt.white) + self.setPalette(p) + +# # initialize line bitmap buffer # self.lb= [None] * linebuffersize diff --git a/pyilper/pilkeymap.py b/pyilper/pilkeymap.py index 95d25fa..70e7ffc 100644 --- a/pyilper/pilkeymap.py +++ b/pyilper/pilkeymap.py @@ -34,9 +34,15 @@ # - refactoring # 11.02.2019 jsi: # - fixed backtab key +# 04.05.2022 jsi +# - PySide6 migration + +from .pilcore import isMACOS, QTBINDINGS +if QTBINDINGS=="PySide6": + from PySide6 import QtCore, QtGui, QtWidgets +if QTBINDINGS=="PyQt5": + from PyQt5 import QtCore, QtGui, QtWidgets -from PyQt5 import QtCore -from .pilcore import isMACOS # # Modifier masks # diff --git a/pyilper/pilpdf.py b/pyilper/pilpdf.py index ee4a225..4365dad 100644 --- a/pyilper/pilpdf.py +++ b/pyilper/pilpdf.py @@ -31,9 +31,14 @@ # - update page number # 01.03.2018 - jsi # - use monospaced font defined in pilcore.py +# 04.05.2022 jsi +# - PySide6 migration # -from PyQt5 import QtCore, QtGui, QtWidgets, QtPrintSupport from .pilcore import * +if QTBINDINGS=="PySide6": + from PySide6 import QtCore, QtGui, QtWidgets,QtPrintSupport +if QTBINDINGS=="PyQt5": + from PyQt5 import QtCore, QtGui, QtWidgets,QtPrintSupport # # PDF printer class: @@ -55,21 +60,21 @@ def __init__(self,papersize, orientation, outputfilename, title, pagenumbering, # self.printer = QtPrintSupport.QPrinter (QtPrintSupport.QPrinter.HighResolution) if self.orientation== PDF_ORIENTATION_PORTRAIT: - self.printer.setOrientation(QtPrintSupport.QPrinter.Portrait) + self.printer.setPageOrientation(QtGui.QPageLayout.Portrait) else: - self.printer.setOrientation(QtPrintSupport.QPrinter.Landscape) + self.printer.setPageOrientation(QtGui.QPageLayout.Landscape) self.printer.setOutputFormat(QtPrintSupport.QPrinter.PdfFormat) self.pdfscene=QtWidgets.QGraphicsScene() # # page set up, we use 1/10 mm as scene units # if self.papersize== PDF_FORMAT_A4: - self.printer.setPageSize(QtPrintSupport.QPrinter.A4) + self.printer.setPageSize(QT_FORM_A4) self.scene_w= 2100 self.scene_h=2970 self.pdfscene.setSceneRect(0,0,self.scene_w,self.scene_h) else: - self.printer.setPageSize(QtPrintSupport.QPrinter.Letter) + self.printer.setPageSize(QT_FORM_LANDSCAPE) self.scene_w= 2160 self.scene_h= 2790 self.pdfscene.setSceneRect(0,0,self.scene_w,self.scene_h) @@ -204,7 +209,7 @@ def __init__(self,text): self.font.setPointSize(2) metrics= QtGui.QFontMetrics(self.font) self.font_h= metrics.height() - self.font_w= metrics.width("A") + self.font_w= metrics.boundingRect("A").width() self.spacing=20 self.h= self.font_h+self.spacing*2 self.w= len(text)* self.font_w diff --git a/pyilper/pilplotter.py b/pyilper/pilplotter.py index b67a3da..7b26648 100644 --- a/pyilper/pilplotter.py +++ b/pyilper/pilplotter.py @@ -100,14 +100,20 @@ # - assure that the return value of heightForWidth is an integer # - assure that argument for pen.setWidth is int in update_PenDef # - improve shutdown of the plotter subprocess +# 04.05.2022 jsi +# - PySide6 migration import sys import subprocess import queue import threading import array -from PyQt5 import QtCore, QtGui, QtPrintSupport, QtWidgets -from .pilcore import UPDATE_TIMER, FONT, EMU7470_VERSION, decode_version, isWINDOWS +from .pilcore import * +if QTBINDINGS=="PySide6": + from PySide6 import QtCore, QtGui, QtPrintSupport, QtWidgets +if QTBINDINGS=="PyQt5": + from PyQt5 import QtCore, QtGui, QtPrintSupport, QtWidgets + from .pilconfig import PilConfigError, PILCONFIG from .penconfig import PENCONFIG from .pildevbase import cls_pildevbase @@ -475,6 +481,7 @@ def digi_clear(self): # def mousePressEvent(self, event): if self.digitize: +#DEPRECATED x=event.pos() p=self.mapToScene(x) x=p.x() @@ -788,11 +795,11 @@ def do_print(self): return printer = QtPrintSupport.QPrinter (QtPrintSupport.QPrinter.HighResolution) if self.papersize==0: - printer.setPageSize(QtPrintSupport.QPrinter.A4) + printer.setPageSize(QT_FORM_A4) else: - printer.setPageSize(QtPrintSupport.QPrinter.Letter) + printer.setPageSize(QT_FORM_LETTER) - printer.setOrientation(QtPrintSupport.QPrinter.Landscape) + printer.setPageOrientation(QtGui.QPageLayout.Landscape) printer.setOutputFormat(QtPrintSupport.QPrinter.PdfFormat) printer.setOutputFileName(flist[0]) p = QtGui.QPainter(printer) @@ -1247,7 +1254,7 @@ def do_cancel(self): @staticmethod def getPlotterConfig(parent): dialog= cls_PlotterConfigWindow(parent) - result= dialog.exec_() + result= dialog.exec() if result== QtWidgets.QDialog.Accepted: return True else: diff --git a/pyilper/pilqterm.py b/pyilper/pilqterm.py index 15ec165..13a5be7 100644 --- a/pyilper/pilqterm.py +++ b/pyilper/pilqterm.py @@ -175,6 +175,8 @@ # - fixed _kbdfunc call in paste # 18.04.2022 jsi # - cast coorinates to int to avoid crash using Python 3.10 +# 04.05.2022 jsi +# - PySide6 migration # # to do: # fix the reason for a possible index error in HPTerminal.dump() @@ -184,9 +186,13 @@ import threading import time -from PyQt5 import QtCore, QtGui, QtWidgets +from .pilcore import UPDATE_TIMER, CURSOR_BLINK, TERMINAL_MINIMUM_ROWS,FONT, AUTOSCROLL_RATE, isMACOS, KEYBOARD_DELAY, QTBINDINGS +if QTBINDINGS=="PySide6": + from PySide6 import QtCore, QtGui, QtWidgets +if QTBINDINGS=="PyQt5": + from PyQt5 import QtCore, QtGui, QtWidgets + from .pilcharconv import icharconv, CHARSET_HP71, CHARSET_HP75, CHARSET_HP41 -from .pilcore import UPDATE_TIMER, CURSOR_BLINK, TERMINAL_MINIMUM_ROWS,FONT, AUTOSCROLL_RATE, isMACOS, KEYBOARD_DELAY from .shortcutconfig import SHORTCUTCONFIG, SHORTCUT_EXEC, SHORTCUT_EDIT, SHORTCUT_INSERT from .pilconfig import PILCONFIG from .pilkeymap import * @@ -562,7 +568,7 @@ def reconfigure(self): s="" self._true_w= [] for i in range(self._cols+1): - self._true_w.append(metrics.width(s)) + self._true_w.append(metrics.boundingRect(s).width()) s+="A" # # set minimum dimensions for "cols" columns and 24 rows @@ -625,7 +631,7 @@ def contextMenuEvent(self,event): # if self._kbdfunc is not None: pasteAction=menu.addAction("Paste") - action=menu.exec_(self.mapToGlobal(event.pos())) + action=menu.exec(self.mapToGlobal(event.pos())) if action is not None: # # copy to system clipboard @@ -660,6 +666,7 @@ def mousePressEvent(self, event): if button == QtCore.Qt.LeftButton: self._HPTerminal.selectionStop() self._selectionText="" +#DEPRECATED pos call self._press_pos = event.pos() if not self._HPTerminal.selectionStart(self._press_pos,self._true_w, self._char_height): self._press_pos = None diff --git a/pyilper/pilscope.py b/pyilper/pilscope.py index c4852db..0ae23a7 100644 --- a/pyilper/pilscope.py +++ b/pyilper/pilscope.py @@ -22,13 +22,6 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # - -from PyQt5 import QtWidgets -import datetime -from .pilconfig import PILCONFIG -from .pilwidgets import cls_tabtermgeneric, T_BOOLEAN, T_STRING,O_DEFAULT -from .pildevbase import cls_pildevbase - # # Scope tab object classes ---------------------------------------------------- # @@ -63,6 +56,19 @@ # - added "**" to tag to find tags better in the logfile # 16.01.2018 jsi # - send int instead of char to terminal +# 04.05.2022 jsi +# - PySide6 migration + +import datetime +from .pilconfig import PILCONFIG +from .pilwidgets import cls_tabtermgeneric, T_BOOLEAN, T_STRING,O_DEFAULT +from .pildevbase import cls_pildevbase +from .pilcore import QTBINDINGS +if QTBINDINGS=="PySide6": + from PySide6 import QtWidgets +if QTBINDINGS=="PyQt5": + from PyQt5 import QtWidgets + LOG_INBOUND=0 LOG_OUTBOUND=1 diff --git a/pyilper/pilthreads.py b/pyilper/pilthreads.py index f86e01a..2b3f979 100644 --- a/pyilper/pilthreads.py +++ b/pyilper/pilthreads.py @@ -41,13 +41,23 @@ # simulation interface when receiving byte data from the pil box # - removed ACK in pil box simulation after receiving a high byte # - fixed detection and acknowledge of a pil box command +# 04.05.2022 jsi +# - PySide6 migration # -from PyQt5 import QtCore +import sys +import threading from .pilconfig import PILCONFIG from .pilbox import cls_pilbox, PilBoxError from .piltcpip import cls_piltcpip, TcpIpError from .pilcore import assemble_frame, disassemble_frame, COMTMOUTREAD, COMTMOUTACK +from .pilcore import QTBINDINGS from .pilsocket import cls_pilsocket, SocketError +from .pilcore import QTBINDINGS +if QTBINDINGS=="PySide6": + from PySide6 import QtCore +if QTBINDINGS=="PyQt5": + from PyQt5 import QtCore + # class PilThreadError(Exception): def __init__(self,msg,add_msg=None): @@ -191,6 +201,11 @@ def update_addr_framecounter(self,value): def get_addr_framecounter(self): return self.addr_framecounter # +# run method +# + def run(self): + sys.settrace(threading._trace_hook) +# # PIL-Box communications thread over serial port # class cls_PilBoxThread(cls_pilthread_generic): @@ -221,6 +236,7 @@ def enable(self): # thread execution # def run(self): + super().run() # self.send_message("connected to PIL-Box at {:d} baud".format(self.__baudrate__)) try: @@ -324,6 +340,7 @@ def disable(self): # thread execution # def run(self): + super().run() # connected=False try: @@ -388,6 +405,7 @@ def enable(self): # thread execution # def run(self): + super().run() # try: # diff --git a/pyilper/pilwidgets.py b/pyilper/pilwidgets.py index f428515..d504167 100644 --- a/pyilper/pilwidgets.py +++ b/pyilper/pilwidgets.py @@ -202,6 +202,10 @@ # address view to HP71 style in cls_DevStatusWindow() # 19.12.2021 jsi # - tab title text color fixed (macOS problem) +# 04.05.2022 jsi +# - PySide6 migration +# 06.04.2022 jsi +# - set index to old tty value in tty combobox # import os import glob @@ -210,25 +214,21 @@ import sys import functools import pyilper -from PyQt5 import QtCore, QtGui, QtWidgets -HAS_WEBKIT=False -HAS_WEBENGINE=False -try: - from PyQt5 import QtWebKitWidgets - HAS_WEBKIT= True -except: - pass -try: - from PyQt5 import QtWebEngineWidgets - HAS_WEBENGINE=True -except: - pass -if HAS_WEBKIT and HAS_WEBENGINE: - HAS_WEBENGINE=False +from .pilcore import * +if QTBINDINGS=="PySide6": + from PySide6 import QtCore, QtGui, QtWidgets + if HAS_WEBENGINE: + from PySide6 import QtWebEngineWidgets +if QTBINDINGS=="PyQt5": + from PyQt5 import QtCore, QtGui, QtWidgets + if HAS_WEBKIT: + from PyQt5 import QtWebKitWidgets + if HAS_WEBENGINE: + from PyQt5 import QtWebEngineWidgets + from .pilqterm import QScrolledTerminalWidget from .pilcharconv import CHARSET_HP71, charsets from .pilconfig import PILCONFIG -from .pilcore import * if isWINDOWS(): import winreg # @@ -252,7 +252,10 @@ # class cls_config_tool_button(QtWidgets.QToolButton): - config_changed_signal= QtCore.pyqtSignal() + if QTBINDINGS=="PySide6": + config_changed_signal= QtCore.Signal() + if QTBINDINGS=="PyQt5": + config_changed_signal= QtCore.pyqtSignal() def __init__(self,name,text): super().__init__() @@ -866,6 +869,7 @@ def __init__(self,parent=None): self.view = QtWebEngineWidgets.QWebEngineView() if not HAS_WEBENGINE and not HAS_WEBKIT: raise HelpError("The Python bindings for QtWebKit and QtWebEngine are missing. Can not display manual") + self.view.setMinimumWidth(600) self.vlayout.addWidget(self.view) self.buttonExit = QtWidgets.QPushButton('Exit') @@ -933,7 +937,11 @@ class cls_AboutWindow(QtWidgets.QDialog): def __init__(self,version): super().__init__() - self.qtversion=QtCore.QT_VERSION_STR + if QTBINDINGS=="PySide6": + self.qtversion=QtCore.__version__ + if QTBINDINGS=="PyQt5": + self.qtversion=QtCore.QT_VERSION_STR + self.pyversion=str(sys.version_info.major)+"."+str(sys.version_info.minor)+"."+str(sys.version_info.micro) self.setWindowTitle('pyILPER About ...') self.vlayout = QtWidgets.QVBoxLayout() @@ -960,9 +968,10 @@ def do_exit(self): class cls_TtyWindow(QtWidgets.QDialog): - def __init__(self, parent=None): + def __init__(self, tty): super().__init__() + self.oldTty=tty self.setWindowTitle("Select serial device") self.vlayout= QtWidgets.QVBoxLayout() self.setLayout(self.vlayout) @@ -1008,7 +1017,7 @@ def __init__(self, parent=None): for port in devlist: self.__ComboBox__.addItem( port, port ) - self.__ComboBox__.activated['QString'].connect(self.combobox_choosen) + self.__ComboBox__.activated[int].connect(self.combobox_choosen) self.__ComboBox__.editTextChanged.connect(self.combobox_textchanged) self.buttonBox = QtWidgets.QDialogButtonBox() self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) @@ -1021,6 +1030,10 @@ def __init__(self, parent=None): self.hlayout.addWidget(self.buttonBox) self.vlayout.addWidget(self.buttonBox) self.__device__= "" + idx=self.__ComboBox__.findText(self.oldTty,QtCore.Qt.MatchExactly) + if idx > 0 : + self.__ComboBox__.setCurrentIndex(idx) + def do_ok(self): if self.__device__=="": @@ -1036,17 +1049,17 @@ def do_cancel(self): def combobox_textchanged(self, device): self.__device__= device - def combobox_choosen(self, device): - self.__device__= device + def combobox_choosen(self, idx): + self.__device__= self.__ComboBox__.itemText(idx) def getDevice(self): return self.__device__ @staticmethod - def getTtyDevice(parent=None): - dialog= cls_TtyWindow(parent) + def getTtyDevice(tty_device): + dialog= cls_TtyWindow(tty_device) dialog.resize(200,100) - result= dialog.exec_() + result= dialog.exec() if result== QtWidgets.QDialog.Accepted: return dialog.getDevice() else: @@ -1404,7 +1417,7 @@ def setCheckBoxes(self): self.comboBaud.setEnabled(False) def do_config_Interface(self): - interface= cls_TtyWindow.getTtyDevice() + interface= cls_TtyWindow.getTtyDevice(self.__tty__) if interface == "" : return self.__tty__= interface @@ -1554,7 +1567,7 @@ def get_status(self): @staticmethod def getPilConfig(parent): dialog= cls_PilConfigWindow(parent) - result= dialog.exec_() + result= dialog.exec() (reconnect,reconfigure)= dialog.get_status() if result== QtWidgets.QDialog.Accepted: return True, reconnect, reconfigure @@ -1672,7 +1685,7 @@ def do_itemAdd(self): def getDeviceConfig(parent): dialog= cls_DeviceConfigWindow(parent) dialog.resize(350,100) - result= dialog.exec_() + result= dialog.exec() if result== QtWidgets.QDialog.Accepted: return True else: @@ -1683,8 +1696,8 @@ def getDeviceConfig(parent): class cls_Device_validator(QtGui.QValidator): def validate(self,string,pos): - self.regexp = QtCore.QRegExp('[A-Za-z][A-Za-z0-9]*') - self.validator = QtGui.QRegExpValidator(self.regexp) + self.regexp = QtCore.QRegularExpression('[A-Za-z][A-Za-z0-9]*') + self.validator = QtGui.QRegularExpressionValidator(self.regexp) result=self.validator.validate(string,pos) return result[0], result[1], result[2] # @@ -1759,7 +1772,7 @@ def do_cancel(self): def getAddDevice(parent): dialog= cls_AddDeviceWindow(parent) dialog.resize(250,100) - result= dialog.exec_() + result= dialog.exec() if result== QtWidgets.QDialog.Accepted: return dialog.getResult() else: diff --git a/pyilper/pyilpermain.py b/pyilper/pyilpermain.py index c19b60b..27b2207 100644 --- a/pyilper/pyilpermain.py +++ b/pyilper/pyilpermain.py @@ -184,6 +184,8 @@ # - copy config: display versions # 19.12.2021 jsi # - copy config: error processing added +# 04.05.2022 jsi +# - PySide6 migration # import os import sys @@ -194,9 +196,12 @@ import re import argparse import time -from PyQt5 import QtCore, QtWidgets -from .pilwidgets import cls_ui, cls_AboutWindow, cls_HelpWindow, HelpError, cls_DeviceConfigWindow, cls_DevStatusWindow, cls_PilConfigWindow from .pilcore import * +if QTBINDINGS=="PySide6": + from PySide6 import QtCore, QtWidgets +if QTBINDINGS=="PyQt5": + from PyQt5 import QtCore, QtWidgets +from .pilwidgets import cls_ui, cls_AboutWindow, cls_HelpWindow, HelpError, cls_DeviceConfigWindow, cls_DevStatusWindow, cls_PilConfigWindow from .pilconfig import PilConfigError, PILCONFIG, cls_pilconfig from .penconfig import PenConfigError, PENCONFIG, cls_PenConfigWindow from .shortcutconfig import ShortcutConfigError, SHORTCUTCONFIG, cls_ShortcutConfigWindow @@ -223,10 +228,14 @@ class cls_pyilper(QtCore.QObject): - sig_show_message=QtCore.pyqtSignal(str) - sig_crash=QtCore.pyqtSignal() - sig_quit=QtCore.pyqtSignal() - + if QTBINDINGS=="PySide6": + sig_show_message=QtCore.Signal(str) + sig_crash=QtCore.Signal() + sig_quit=QtCore.Signal() + if QTBINDINGS=="PyQt5": + sig_show_message=QtCore.pyqtSignal(str) + sig_crash=QtCore.pyqtSignal() + sig_quit=QtCore.pyqtSignal() def __init__(self,args): @@ -618,19 +627,19 @@ def do_Exit(self): # def do_Init(self): workdir= PILCONFIG.get(self.name,"workdir") - cls_lifinit.exec(workdir) + cls_lifinit.execute(workdir) # # callback fix LIF Medium # def do_Fix(self): workdir= PILCONFIG.get(self.name,"workdir") - cls_liffix.exec(workdir) + cls_liffix.execute(workdir) # # callback check LIFUTILS installation # def do_InstallCheck(self): - cls_installcheck.exec() + cls_installcheck.execute() # # callback copy PILIMAGE.DAT to working directory @@ -782,7 +791,7 @@ def main(): signal.signal(signal.SIGQUIT, dumpstacks) app = QtWidgets.QApplication(sys.argv) pyilper= cls_pyilper(args) - sys.exit(app.exec_()) + sys.exit(app.exec()) if __name__ == '__main__': diff --git a/pyilper/shortcutconfig.py b/pyilper/shortcutconfig.py index 004adb1..6796e3e 100644 --- a/pyilper/shortcutconfig.py +++ b/pyilper/shortcutconfig.py @@ -27,9 +27,16 @@ # - SHORTCUT_INSERT shortcut type added # 12.12.2021 jsi # - add configversion parameter to open method +# 04.05.2022 jsi +# - PySide6 migration # import copy -from PyQt5 import QtCore, QtGui, QtWidgets +from .pilcore import QTBINDINGS +if QTBINDINGS=="PySide6": + from PySide6 import QtCore, QtGui, QtWidgets +if QTBINDINGS=="PyQt5": + from PyQt5 import QtCore, QtGui, QtWidgets + from .userconfig import cls_userconfig, ConfigError SHORTCUT_INPUT=0 @@ -191,7 +198,7 @@ def do_reset(self): def getShortcutConfig(): dialog= cls_ShortcutConfigWindow() dialog.resize(650,600) - result= dialog.exec_() + result= dialog.exec() if result== QtWidgets.QDialog.Accepted: return True else: diff --git a/setup.py b/setup.py index ce97ee4..4946149 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,13 @@ # Versions should comply with PEP440. For a discussion on single-sourcing # the version across setup.py and the project code, see # https://packaging.python.org/en/latest/single_source_version.html - version='1.8.6b1', + version='1.8.6b2', + + # Installation requirements + install_requires=[ + "pyside6 | pyqt5", + "pyserial", + ], description='Virtual HP-IL devices for the PIL-Box', long_description=long_description, @@ -35,8 +41,7 @@ 'Development Status :: 5 - Production/Stable', # Indicate who your project is intended for - 'Intended Audience :: HP Calculator enthusiasts', - 'Topic :: Software Development :: Tools', + 'Topic :: Utilities', # Pick your license as you wish (should match "license" above) 'License :: OSI Approved :: MIT License', @@ -52,6 +57,9 @@ 'Programming Language :: Python :: 3.10', ], + # required python versions + python_requires='>=3.6', + # What does your project relate to? keywords='pyqt5 HP-IL PIL-Box HP-41 HP-71 HP-75',