diff --git a/LICENSE b/LICENSE.txt similarity index 100% rename from LICENSE rename to LICENSE.txt diff --git a/changelog.md b/changelog.md new file mode 100644 index 0000000..b1a2513 --- /dev/null +++ b/changelog.md @@ -0,0 +1,12 @@ +# Changelog GarageCalc1 + +## [0.1] - 2021-06-27 +### Added +- first version + +# Changelog GarageCalc1 + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). diff --git a/img/icons8-garage-32.ico b/img/icons8-garage-32.ico new file mode 100644 index 0000000..83af31c Binary files /dev/null and b/img/icons8-garage-32.ico differ diff --git a/img/icons8-garage-32.png b/img/icons8-garage-32.png deleted file mode 100644 index 6673d8c..0000000 Binary files a/img/icons8-garage-32.png and /dev/null differ diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e190d6a --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +# project: GarageCalc1 +# file: requirements.txt +wheel +pyside2==5.15.2 +XlsxWriter==1.3.8 diff --git a/scripts/pyinstaller.cmd b/scripts/pyinstaller.cmd new file mode 100644 index 0000000..58f1c86 --- /dev/null +++ b/scripts/pyinstaller.cmd @@ -0,0 +1,97 @@ +@ECHO OFF +:: -------------------------------------------------------------------------------------------------------------------------------------- +:: project: GarageCalc1 Window-Exe Generator +:: summary: create a GarageCalc1-executable for Windows (version with no-console-windows as well as version with console windows for traces) +:: file: pyinstaller.cmd +:: date: version author +:: 2021-06-27 1 paul salajean +:: -------------------------------------------------------------------------------------------------------------------------------------- +SET version="v0.1" +SET release_dir="D:\Temp\Prog\ownCloud\profp@uberspace\transfer" + +SET prev_dir=%cd% +SET reinstall_venv="true" +SET do_zip="false" + +CD .. + +ECHO Creating GarageCalc1 Windows.exe-version in folder %cd%\dist +PAUSE +REM pyinstaller --noconfirm --log-level=ERROR ^ +REM --onedir --nowindow ^ +REM --add-data="README;." ^ +REM --add-data="image1.png;img" ^ +REM --add-data="img;doc;ui" ^ +REM --add-data="LICENSE.txt;changelog.md;GarageCalc1.bat" ^ +REM --add-binary="libfoo.so;lib" ^ +REM --hidden-import=secret1 ^ +REM --hidden-import=secret2 ^ +REM --icon=.\img\GarageCalc1.ico ^ +REM --debug=imports ^ +REM --key=N0T1me40pp0ssum5 +REM --paths=.\src ^ +REM GarageCalc1.spec +REM The key-string is a string of 16 characters which is used to encrypt each file of Python byte-code before it is stored in the archive inside the executable file. +REM This feature uses the "tinyaes" module internally for the encryption. +REM + +::one dir: +ECHO ...Running... + +RMDIR /S /Q .\dist>nul +RMDIR /S /Q .\build>nul + +::Use Virtual env +IF %reinstall_venv%=="false" GOTO PYINSTALL +ECHO Installing virtual env as ".\env2"... +RMDIR /S /Q .\env2>NUL +python -m venv env2 +CALL .\env2\Scripts\activate.bat + +ECHO Installing necessary python packages... +python -m pip install --upgrade pip +python -m pip install -r requirements.txt + +:PYINSTALL +IF %reinstall_venv%=="false" CALL .\env2\Scripts\activate.bat +ECHO Running "pyinstaller"... +REM pyinstaller .\main.py --name=GarageCalc1 --noconfirm --console --clean --onedir --log-level=ERROR --hidden-import=PySide2.QtXml --icon=.\img\GarageCalc1.ico --add-data="LICENSE;." --add-data="README.md;." --add-data="changelog.md;." --add-data="*.ui;." --add-data="img;.\img" +pyinstaller .\src\main.py --name=GarageCalc1 --noconfirm --windowed --clean --onefile --log-level=ERROR --hidden-import=PySide2.QtXml ^ + --icon=.\img\icons8-garage-32.ico ^ + --add-data="LICENSE.txt;." ^ + --add-data="README.md;." ^ + --add-data="changelog.md;." ^ + --add-data="requirements.txt;." ^ + --add-data=".\src\*.ui;." ^ + --add-data="img;.\img" + +::MOVE .\dist\GarageCalc1\*.md .\dist\ +::MOVE .\dist\GarageCalc1\LICENSE .\dist\ + +::clean-up img-folder +::MOVE .\dist\GarageCalc1\img .\dist\img>NUL + +::Remove temp dir and files +DEL /Q *.spec>NUL +RMDIR /S /Q .\build + +::Create self extracting archive +IF %do_zip%=="false" GOTO END +::-mx0 = Don't compress at all - just copy the contents to archive. +::-mx1 = Consumes least time, but compression is low. +::-mx3 = Better than -mx1. +::-mx5 = This is default (compression is normal). +::-mx7 = Maximum compression. +::-mx9 = Ultra compression. +"C:\Program Files\7-zip\7z.exe" a %release_dir%\GarageCalc1_%version%_portable.exe -mx9 -sfx7z.sfx .\dist\* + +::Disable Virtual env +CALL .\env2\Scripts\deactivate.bat + +:END +CD %prev_dir% + +ECHO Done. Check %prev_dir%\..\dist +IF %do_zip%=="true" ECHO Done. Check %release_dir% + +PAUSE diff --git a/main.py b/src/main.py similarity index 76% rename from main.py rename to src/main.py index 837a227..1c0c772 100644 --- a/main.py +++ b/src/main.py @@ -20,7 +20,7 @@ from PySide2.QtCore import QFile, QSize, Qt from PySide2.QtUiTools import QUiLoader # Local imports -from utils import show_about +from utils import show_about, resource_path # Local globals UI_FILE = "main.ui" @@ -32,7 +32,7 @@ APP_DESCR = "Berechnet zur Verfügung stehenden Garagenraum" APP_COPYRIGHT = "(c) Paul Salajean 2021" APP_WEBSITE = "https://gitlab.com/ProfP303" APP_DESKTOPFILENAME = "GarageCalc" -APP_ICON = "./img/icons8-garage-32.png" +APP_ICON = "./img/icons8-garage-32.ico" ICON_NEW = "./img/icons8-new-file-32.png" ICON_OPEN = "./img/icons8-opened-folder-32.png" @@ -47,7 +47,11 @@ COL_WIDTH = 2 COL_HEIGHT = 3 COL_WEIGHT = 4 -STUFF_ROW_COUNT = 50 +DEFAULT_GARAGE_LENGTH = "6" +DEFAULT_GARAGE_WIDTH = "2.5" +DEFAULT_GARAGE_HEIGHT = "2.5" + +TBL_STUFF_ROW_COUNT = 50 class MyMainWindow(QMainWindow): def __init__(self): @@ -59,15 +63,15 @@ class MyMainWindow(QMainWindow): self.create_actions() self.create_toolbar() self.create_statusbar() - self.statusBar.showMessage(f"{APP_NAME} {APP_VERSION} by {APP_AUTHOR}", 7000) + self.statusBar.showMessage(f"{APP_NAME} {APP_VERSION} by {APP_AUTHOR}", 5000) self.calc_voluminae() self.ui.efWeight.setText(str("0")) - self.modified = False + self.is_modified = False self.opened_file = None def load_ui(self): loader = QUiLoader() - path = os.path.join(os.path.dirname(__file__), UI_FILE) + path = os.path.join(os.path.dirname(__file__), resource_path( UI_FILE)) ui_file = QFile(path) ui_file.open(QFile.ReadOnly) self.ui = loader.load(ui_file, self) @@ -81,35 +85,35 @@ class MyMainWindow(QMainWindow): def create_actions(self): self.actionNew = QAction() - self.actionNew.setIcon(QIcon(ICON_NEW)) + self.actionNew.setIcon(QIcon(resource_path(ICON_NEW))) self.actionNew.triggered.connect(self.file_new) self.actionNew.setShortcut("Ctrl+N") self.actionNew.setToolTip("Neu (Strg+N)") self.actionOpen = QAction() - self.actionOpen.setIcon(QIcon(ICON_OPEN)) + self.actionOpen.setIcon(QIcon(resource_path(ICON_OPEN))) self.actionOpen.triggered.connect(self.file_open) self.actionOpen.setShortcut("Ctrl+O") self.actionOpen.setToolTip("Öffnen... (Strg+O)") self.actionSave = QAction() - self.actionSave.setIcon(QIcon(ICON_SAVE)) + self.actionSave.setIcon(QIcon(resource_path(ICON_SAVE))) self.actionSave.triggered.connect(self.file_save) self.actionSave.setShortcut("Ctrl+S") self.actionSave.setToolTip("Speichern (Strg+S)") self.actionExport = QAction() - self.actionExport.setIcon(QIcon(ICON_EXPORT)) + self.actionExport.setIcon(QIcon(resource_path(ICON_EXPORT))) self.actionExport.triggered.connect(self.file_export) self.actionExport.setToolTip("Export nach EXCEL...") self.actionAbout = QAction() - self.actionAbout.setIcon(QIcon(ICON_ABOUT)) + self.actionAbout.setIcon(QIcon(resource_path(ICON_ABOUT))) self.actionAbout.triggered.connect(show_about) self.actionAbout.setToolTip("Informationen über das Programm") self.actionQuit = QAction() - self.actionQuit.setIcon(QIcon(ICON_QUIT)) + self.actionQuit.setIcon(QIcon(resource_path(ICON_QUIT))) self.actionQuit.triggered.connect(QApplication.quit) self.actionQuit.setShortcut("Ctrl+Q") self.actionQuit.setToolTip("Programm beenden (Strg+Q)") @@ -132,13 +136,13 @@ class MyMainWindow(QMainWindow): tblGarage = self.ui.tableGarage tblStuff = self.ui.tableStuff - tblGarage.setItem(0, 0, QTableWidgetItem("6")) - tblGarage.setItem(0, 1, QTableWidgetItem("2.5")) - tblGarage.setItem(0, 2, QTableWidgetItem("2.5")) + tblGarage.setItem(0, 0, QTableWidgetItem(DEFAULT_GARAGE_LENGTH)) + tblGarage.setItem(0, 1, QTableWidgetItem(DEFAULT_GARAGE_WIDTH)) + tblGarage.setItem(0, 2, QTableWidgetItem(DEFAULT_GARAGE_HEIGHT)) - tblStuff.setRowCount(STUFF_ROW_COUNT) + tblStuff.setRowCount(TBL_STUFF_ROW_COUNT) - self.modified = False + self.is_modified = False def create_statusbar(self): self.statusBar = QStatusBar() @@ -154,7 +158,6 @@ class MyMainWindow(QMainWindow): def show_rowheader_context_menu(self, position): tblStuff = self.ui.tableStuff row = self.headers.logicalIndexAt(position) - print("row click", row) menu = QMenu() remove_row = menu.addAction("Zeile entfernen") @@ -179,7 +182,7 @@ class MyMainWindow(QMainWindow): # clear stuff # tblStuff.clear() tblStuff.setRowCount(0) - tblStuff.setRowCount(STUFF_ROW_COUNT) + tblStuff.setRowCount(TBL_STUFF_ROW_COUNT) # clear results self.ui.efVol_Garage.clear() @@ -194,124 +197,126 @@ class MyMainWindow(QMainWindow): self.setWindowTitle(f"{APP_NAME} {APP_VERSION} by {APP_AUTHOR}") self.ui.efVol_Free.setStyleSheet("") - self.modified = False + self.is_modified = False def file_new(self): - if self.modified: + if self.is_modified: msg = "Es wurden bereits Einträge manuell geändert. Ohne Speichern sind alle Änderungen verloren. Trotzdem fortfahren?" - reply = QMessageBox.question(self, "Fortfahren?", msg, \ + reply = QMessageBox.question(self, "Neu", msg, \ QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes) if reply == QMessageBox.No: return False self.init_ui() def file_save(self): - print("file_save() called.") tblGarage = self.ui.tableGarage tblStuff = self.ui.tableStuff + is_file_saved = False fileName = self.opened_file if not self.opened_file: # if not file already open options = QFileDialog.Options() - sTxtFilesAll = "Alle Dateien" - sTxtFiles = "CSV-Datei" - fileName, _ = QFileDialog.getSaveFileName(None, "Speichern", None, - sTxtFiles + " (*.csv);;" + sTxtFilesAll + " (*)", + "CSV-Datei (*.csv);;Alle Dateien (*)", options=options) - with open(fileName, mode='w', newline='') as garagecalc_file: - writer = csv.writer(garagecalc_file, delimiter=';', quotechar='"', quoting=csv.QUOTE_MINIMAL) + if fileName: # if not file already open + with open(fileName, mode='w', newline='') as garagecalc_file: + writer = csv.writer(garagecalc_file, delimiter=';', quotechar='"', quoting=csv.QUOTE_MINIMAL) - garage_length = 0 - garage_width = 0 - garage_height = 0 + garage_length = 0 + garage_width = 0 + garage_height = 0 - item_length = tblGarage.item(0, 0) - item_width = tblGarage.item(0, 1) - item_height = tblGarage.item(0, 2) + item_length = tblGarage.item(0, 0) + item_width = tblGarage.item(0, 1) + item_height = tblGarage.item(0, 2) - # loop over table Garage - for row in range(tblGarage.rowCount()): - # get garage length - if item_length: - garage_length = item_length.text() - try: - garage_length = float(garage_length) - except ValueError: - garage_length = 0.0 + # loop over table Garage + for row in range(tblGarage.rowCount()): + # get garage length + if item_length: + garage_length = item_length.text() + try: + garage_length = float(garage_length) + except ValueError: + garage_length = 0.0 - # get garage width - if item_width: - garage_width = item_width.text() - try: - garage_width = float(garage_width) - except ValueError: - garage_width = 0.0 + # get garage width + if item_width: + garage_width = item_width.text() + try: + garage_width = float(garage_width) + except ValueError: + garage_width = 0.0 - # get garage height - if item_height: - garage_height = item_height.text() - try: - garage_height = float(garage_height) - except ValueError: - garage_height = 0.0 + # get garage height + if item_height: + garage_height = item_height.text() + try: + garage_height = float(garage_height) + except ValueError: + garage_height = 0.0 - if garage_length or garage_width or garage_height: - writer.writerow(["Garage", garage_length, garage_width, garage_height]) + if garage_length or garage_width or garage_height: + writer.writerow(["Garage", garage_length, garage_width, garage_height]) - # loop over table Stuff - for row in range(tblStuff.rowCount()): - stuff_text = None - length = None - width = None - height = None - weight = None + # loop over table Stuff + for row in range(tblStuff.rowCount()): + stuff_text = None + length = None + width = None + height = None + weight = None - item_stuff = tblStuff.item(row, COL_STUFF) - item_length = tblStuff.item(row, COL_LENGTH) - item_width = tblStuff.item(row, COL_WIDTH) - item_height = tblStuff.item(row, COL_HEIGHT) - item_weight = tblStuff.item(row, COL_WEIGHT) + item_stuff = tblStuff.item(row, COL_STUFF) + item_length = tblStuff.item(row, COL_LENGTH) + item_width = tblStuff.item(row, COL_WIDTH) + item_height = tblStuff.item(row, COL_HEIGHT) + item_weight = tblStuff.item(row, COL_WEIGHT) - if item_stuff: - stuff_text = item_stuff.text() + if item_stuff: + stuff_text = item_stuff.text() - if item_length: - try: - length = float(item_length.text()) - except ValueError: - length = None + if item_length: + try: + length = float(item_length.text()) + except ValueError: + length = None - if item_width: - try: - width = float(item_width.text()) - except ValueError: - width = None + if item_width: + try: + width = float(item_width.text()) + except ValueError: + width = None - if item_height: - try: - height = float(item_height.text()) - except ValueError: - height = None + if item_height: + try: + height = float(item_height.text()) + except ValueError: + height = None - if item_weight: - try: - weight = float(item_weight.text()) - except ValueError: - weight = None + if item_weight: + try: + weight = float(item_weight.text()) + except ValueError: + weight = None - if item_stuff or item_length or item_width or item_height or item_weight: - writer.writerow([stuff_text, length, width, height, weight]) + if item_stuff or item_length or item_width or item_height or item_weight: + writer.writerow([stuff_text, length, width, height, weight]) + is_file_saved = True self.opened_file = os.path.basename(fileName) self.setWindowTitle(self.opened_file) - self.modified = False + self.is_modified = False + + if is_file_saved: + self.statusBar.showMessage(f"Datei {fileName} gespeichert.", 2000) def file_open(self): - if self.modified: + if self.is_modified: msg = "Es wurden bereits Einträge manuell geändert. Ohne Speichern sind alle Änderungen verloren. Trotzdem fortfahren?" reply = QMessageBox.question(self, "Fortfahren?", msg, \ QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes) @@ -332,7 +337,7 @@ class MyMainWindow(QMainWindow): if fileName: self.init_ui() - file = open(fileName, "rU") + file = open(fileName, "r", newline='') reader = csv.reader(file, delimiter=';') row_idx = 0 @@ -342,9 +347,6 @@ class MyMainWindow(QMainWindow): garage_length = row[1] garage_width = row[2] garage_height = row[3] - print("garage_length", garage_length) - print("garage_width", garage_width) - print("garage_height", garage_height) tblGarage.setItem(0, COL_LENGTH-1, QTableWidgetItem(str(garage_length))) tblGarage.setItem(0, COL_WIDTH-1, QTableWidgetItem(str(garage_width))) @@ -352,7 +354,7 @@ class MyMainWindow(QMainWindow): except IndexError as ex: pass - print("") + if row_idx > 0: try: stuff = row[0] @@ -360,11 +362,6 @@ class MyMainWindow(QMainWindow): stuff_width = row[2] stuff_height = row[3] stuff_weight = row[4] - print("stuff", stuff) - print("length", stuff_length) - print("width", stuff_width) - print("height", stuff_height) - print("weight", stuff_weight) tblStuff.setItem(row_idx - 1, COL_STUFF, QTableWidgetItem(stuff)) tblStuff.setItem(row_idx - 1, COL_LENGTH, QTableWidgetItem(str(stuff_length))) @@ -377,9 +374,9 @@ class MyMainWindow(QMainWindow): row_idx += 1 - tblStuff.setRowCount(STUFF_ROW_COUNT) + tblStuff.setRowCount(TBL_STUFF_ROW_COUNT) - self.modified = False + self.is_modified = False self.opened_file = os.path.basename(fileName) if fileName: @@ -443,7 +440,7 @@ class MyMainWindow(QMainWindow): worksheet.write(start_row + row, COL_HEIGHT, garage_height) start_row = 4 - worksheet.write(start_row, 0, "Dimensionen der zu verstaueneden Gegenstände") + worksheet.write(start_row, 0, "Dimensionen der zu verstauenden Gegenstände") start_row = 5 worksheet.write(start_row, COL_LENGTH, "Länge [m]") @@ -522,14 +519,14 @@ class MyMainWindow(QMainWindow): worksheet.write(row_idx, 1, float(self.ui.efWeight.text())) workbook.close() - self.statusBar.showMessage(f"Erfolgreich nach EXCEL exportiert.", 7000) + self.statusBar.showMessage(f"Erfolgreich nach EXCEL exportiert.", 5000) def on_garage_changed(self): - self.modified = True + self.is_modified = True self.calc_voluminae() def on_stuff_changed(self): - self.modified = True + self.is_modified = True self.calc_voluminae() self.calc_weight() @@ -575,7 +572,7 @@ class MyMainWindow(QMainWindow): garage_vol = garage_length * float(garage_width) * float(garage_height) else: garage_vol = 0.0 - self.statusBar.showMessage("Fehler in der Garagen-Dimension. :-(", 7000) + self.statusBar.showMessage("Fehler in der Garagen-Dimension. :-(", 5000) return garage_vol @@ -627,12 +624,11 @@ class MyMainWindow(QMainWindow): if is_error: stuff_vol = 0.0 - self.statusBar.showMessage("Fehler in den Gegenstände-Dimensionen. :-(", 7000) + self.statusBar.showMessage("Fehler in den Dimensionen der zu verstauenden Gegenstände :-(", 5000) return stuff_vol def calc_voluminae(self): - print("calc_voluminae() called.") tblGarage = self.ui.tableGarage # get garage vol @@ -685,7 +681,6 @@ if __name__ == "__main__": qApp.setDesktopFileName(APP_DESKTOPFILENAME) winMain = MyMainWindow() - # winMain.resize(600, 600) winMain.setFixedWidth(600) winMain.setFixedHeight(620) winMain.show() diff --git a/main.ui b/src/main.ui similarity index 100% rename from main.ui rename to src/main.ui diff --git a/utils.py b/src/utils.py similarity index 72% rename from utils.py rename to src/utils.py index f58f05f..ec0dcfc 100644 --- a/utils.py +++ b/src/utils.py @@ -17,11 +17,22 @@ from PySide2.QtCore import QFile, QSize from PySide2.QtUiTools import QUiLoader # local globals -APP_ICON = "icons8-garage-32.png" +APP_ICON = "./img/icons8-garage-32.ico" + +def resource_path(relative_path): + """ Get absolute path to resource, works for dev and for PyInstaller """ + try: + # PyInstaller creates a temp folder and stores path in _MEIPASS + base_path = sys._MEIPASS + except Exception: + base_path = os.path.abspath(".") + + return os.path.join(base_path, relative_path) def show_about(): + qApp = QApplication.instance() msg = QMessageBox() - msg.setIconPixmap(QPixmap(APP_ICON)) + msg.setIconPixmap(QPixmap(resource_path(APP_ICON))) text = "

" + qApp.applicationDisplayName() + " " + \ "
" + qApp.applicationVersion() + "

" + \