#!/usr/bin/env python3 """ project: GarageCalc1 file: main.py summary: Calculates available size of a garage space license: GPL author: Paul Salajean (p.salajean[at]gmx.de) """ # Standard library imports import sys import os import csv import configparser # Third party imports from PySide2.QtWidgets import QApplication, QMainWindow, QTableWidgetItem, QStatusBar, QFileDialog, \ QAbstractItemView, QMenu, QMessageBox, QHBoxLayout, QVBoxLayout, QSizePolicy, QAction, QActionGroup from PySide2.QtGui import QIcon, QColor from PySide2.QtCore import QFile, QSize, Qt, QCoreApplication, QTranslator from PySide2.QtUiTools import QUiLoader import xlsxwriter # Local imports import icons_rc from utils import show_about, resource_path, str_iff, fit_col_widths, convert_uom_to_length, convert_uom_to_mass, \ optimizeTableLayout, table_has_items # my own classes imports from clsTableWidget import TableWidget # Local globals APP_VERSION = "v0.7.3" DIR_CURRENT = os.getcwd() APP_NAME = "Garage Space Calculator" APP_DISPNAME = "GarageCalc" APP_AUTHOR = "Paul Salajean" APP_DESCR = "Calculates available garage space" APP_COPYRIGHT = "(c) Paul Salajean 2021" APP_WEBSITE = "https://gitlab.com/ProfP303" APP_DESKTOPFILENAME = APP_DISPNAME APP_INI_FILE = os.path.join(DIR_CURRENT, APP_DISPNAME + '.ini') UI_MAIN = "./ui/win_main.ui" DEFAULT_GARAGE_LENGTH = "6" DEFAULT_GARAGE_WIDTH = "2,5" DEFAULT_GARAGE_HEIGHT = "2,5" DEFAULT_UOM_LENGTH = "m" DEFAULT_UOM_MASS = "kg" COL_GARAGE_LENGTH = 0 COL_GARAGE_WIDTH = 1 COL_GARAGE_HEIGHT = 2 COL_GARAGE_COMMENT = 3 CSV_COL_GARAGE_LENGTH = 1 CSV_COL_GARAGE_WIDTH = 2 CSV_COL_GARAGE_HEIGHT = 3 CSV_COL_GARAGE_COMMENT = 4 COL_STUFF_NAME = 0 COL_STUFF_LENGTH = 1 COL_STUFF_WIDTH = 2 COL_STUFF_HEIGHT = 3 COL_STUFF_WEIGHT = 4 COL_STUFF_COMMENT = 5 CSV_COL_STUFF_NAME = 0 CSV_COL_STUFF_LENGTH = 1 CSV_COL_STUFF_LENGTH_UOM = 2 CSV_COL_STUFF_WIDTH = 3 CSV_COL_STUFF_WIDTH_UOM = 4 CSV_COL_STUFF_HEIGHT = 5 CSV_COL_STUFF_HEIGHT_UOM = 6 CSV_COL_STUFF_WEIGHT = 7 CSV_COL_STUFF_WEIGHT_UOM = 8 CSV_COL_STUFF_COMMENT = 9 TBL_STUFF_COL_COUNT = 6 TBL_STUFF_ROW_COUNT = 50 WIN_WIDTH = 700 WIN_HEIGHT = 640 TXT_UNSAVED_CHANGES = QCoreApplication.translate("main", "There are unsaved entries. Without saving, all changes are lost. Continue anyway?") CONFIG = configparser.ConfigParser() TRANSLATOR = QTranslator() def my_func(): pass #################################################################### def main(): qApp = QApplication(sys.argv) global APP_NAME, CONFIG, TRANSLATOR, DIR_CURRENT, APP_INI_FILE, DEFAULT_UOM_LENGTH, DEFAULT_UOM_MASS qApp.setApplicationName(APP_NAME) qApp.setApplicationDisplayName(APP_DISPNAME) qApp.setApplicationVersion(APP_VERSION) qApp.description = APP_DESCR qApp.copyright = APP_COPYRIGHT qApp.website = APP_WEBSITE qApp.setWindowIcon(QIcon(u":ICONS/ICON_APP")) qApp.setDesktopFileName(APP_DESKTOPFILENAME) print("Current dir:", DIR_CURRENT) # define ini-file defaults language = "Deutsch" # read from existing ini-file if os.path.exists(APP_INI_FILE): print("Reading from existing ini-file", APP_INI_FILE) CONFIG.read(APP_INI_FILE) try: language = CONFIG['DEFAULT']['language'] DEFAULT_UOM_LENGTH = CONFIG['DEFAULT']['UOM_length'] DEFAULT_UOM_MASS = CONFIG['DEFAULT']['UOM_mass'] except KeyError: pass # key will be created during saving anyway else: # create new ini-file print("Creating new ini-file", APP_INI_FILE) CONFIG['DEFAULT']['language'] = language CONFIG['DEFAULT']['UOM_length'] = DEFAULT_UOM_LENGTH CONFIG['DEFAULT']['UOM_mass'] = DEFAULT_UOM_MASS with open(APP_INI_FILE, 'w') as configfile: # save CONFIG.write(configfile) if language == "Deutsch": print("Loading german language file.") TRANSLATOR.load(resource_path('./i18n/de_DE')) elif language == "Magyar": print("Loading hungarian langauge file.") TRANSLATOR.load(resource_path('./i18n/hu_HU')) else: print(f"Unknown language setting '{language}' -> defaulting to english language.") qApp.installTranslator(TRANSLATOR) app_name = qApp.translate("main", APP_NAME) qApp.setApplicationName(app_name) winMain = MainWindow(language, DEFAULT_UOM_LENGTH, DEFAULT_UOM_MASS) if qApp.primaryScreen().size().width() <= 800: winMain.showMaximized() else: winMain.resize(WIN_WIDTH, WIN_HEIGHT) winMain.show() sys.exit(qApp.exec_()) #################################################################### def create_examples(table): from random import randint, choice, uniform from string import ascii_letters, punctuation, digits min = 12 max = 15 string_format = ascii_letters for row in range(10): generated_string1 = "".join(choice(string_format) for x in range(randint(min, max))) generated_string2 = "".join(choice(string_format) for x in range(randint(min, max))) table.setItem(row, COL_STUFF_NAME, QTableWidgetItem(generated_string1)) table.setItem(row, COL_STUFF_LENGTH, QTableWidgetItem(str(round(uniform(1.1, 10.2), 2)).replace('.', ','))) table.setItem(row, COL_STUFF_WIDTH, QTableWidgetItem(str(round(uniform(1.5, 10.2), 2)).replace('.', ','))) table.setItem(row, COL_STUFF_HEIGHT, QTableWidgetItem(str(round(uniform(1.125, 8.75), 2)).replace('.', ','))) table.setItem(row, COL_STUFF_WEIGHT, QTableWidgetItem(str(round(uniform(20, 100), 2)).replace('.', ','))) table.setItem(row, COL_STUFF_COMMENT, QTableWidgetItem(generated_string2)) #################################################################### class MainWindow(QMainWindow): def __init__(self, language="Deutsch", uom_length=DEFAULT_UOM_LENGTH, uom_mass=DEFAULT_UOM_MASS): super().__init__() self.is_modified = False self.opened_file = None self.remembered_row = None self.load_ui() self.init_ui() self.set_defaults() self.create_menu(language, uom_length, uom_mass) self.connect_signals() self.create_toolbar() self.create_statusbar() self.statusBar.showMessage(f"{APP_DISPNAME} {APP_VERSION} - {APP_AUTHOR}", 5000) # TODO: disable for PROD! # create_examples(self.ui.tableStuff) # self.ui.tableGarage.setItem(0, 3, QTableWidgetItem("Garázs az udvaron")) # self.is_modified = False # END TODO self.retranslateUi() optimizeTableLayout(self.ui.tableStuff) self.ui.tableStuff.setFocus() def retranslateUi(self): global DEFAULT_UOM_LENGTH, DEFAULT_UOM_MASS # menus self.menuSettings.setTitle(QCoreApplication.translate("main", "&Settings")) self.menuSettings_Language.setTitle(QCoreApplication.translate("main", "Language")) self.menuSettings_UOMs.setTitle(QCoreApplication.translate("main", "Unit of Measurements")) self.menuSettings_UOMs_Length.setTitle(QCoreApplication.translate("main", "Length units")) self.menuSettings_UOMs_Mass.setTitle(QCoreApplication.translate("main", "Mass units")) # tables self.ui.tableGarage.setVerticalHeaderLabels([ QCoreApplication.translate("main","Garage")]) self.ui.tableGarage.setHorizontalHeaderLabels([ QCoreApplication.translate("main","Length") + " [m]", QCoreApplication.translate("main","Width") + " [m]", QCoreApplication.translate("main","Height") + " [m]", QCoreApplication.translate("main","Comment") ]) self.ui.tableStuff.setHorizontalHeaderLabels([ QCoreApplication.translate("main","Stuff"), QCoreApplication.translate("main","Length") + " [" + DEFAULT_UOM_LENGTH + "]", QCoreApplication.translate("main","Width") + " [" + DEFAULT_UOM_LENGTH + "]", QCoreApplication.translate("main","Height") + " [" + DEFAULT_UOM_LENGTH + "]", QCoreApplication.translate("main","Weight") + " [" + DEFAULT_UOM_MASS + "]", QCoreApplication.translate("main","Comment") ]) # labels self.ui.gbGarage.setTitle(QCoreApplication.translate("main","Dimension of the garage")) self.ui.gbStuff.setTitle(QCoreApplication.translate("main", "Dimensions of the objects to be stored")) self.ui.gbResults.setTitle(QCoreApplication.translate("main", "Result")) self.ui.lblVol_Garage.setText(QCoreApplication.translate("main","Volume of the garage") + ":") self.ui.lblVol_Stuff.setText(QCoreApplication.translate("main","Volume of the items") + ":") self.ui.lblVol_Free.setText(QCoreApplication.translate("main","Free space in the garage") + ":") self.ui.lblWeight.setText(QCoreApplication.translate("main","Total weight") + ":") # Translate actions self.ui.actionNew.setText(QCoreApplication.translate("main","New")) self.ui.actionNew.setToolTip(QCoreApplication.translate("main","New")) self.ui.actionOpen.setText(QCoreApplication.translate("main","Open") + "...") self.ui.actionOpen.setToolTip(QCoreApplication.translate("main","Open") + "...") self.ui.actionSave.setText(QCoreApplication.translate("main","Save")) self.ui.actionSave.setToolTip(QCoreApplication.translate("main","Save")) self.ui.actionExport.setText(QCoreApplication.translate("main","Export to EXCEL...")) self.ui.actionExport.setToolTip(QCoreApplication.translate("main","Export to EXCEL...")) self.ui.actionAbout_Qt.setText(QCoreApplication.translate("main","About Qt")) self.ui.actionAbout_Qt.setToolTip(QCoreApplication.translate("main","About Qt")) self.ui.actionAbout.setText(QCoreApplication.translate("main","Information about the application")) self.ui.actionAbout.setToolTip(QCoreApplication.translate("main","Information about the application")) self.ui.actionQuit.setText(QCoreApplication.translate("main","Quit")) self.ui.actionQuit.setToolTip(QCoreApplication.translate("main","Quit")) def load_ui(self): global TableWidget loader = QUiLoader() path = os.path.join(os.path.dirname(__file__), resource_path(UI_MAIN)) ui_file = QFile(path) ui_file.open(QFile.ReadOnly) self.ui = loader.load(ui_file, self) ui_file.close() # define columns which contain UOM length and mass self.cols_with_uom_length = [COL_STUFF_LENGTH, COL_STUFF_WIDTH, COL_STUFF_HEIGHT] self.cols_with_uom_mass = [COL_STUFF_WEIGHT] # implement custom class 'TableWidget' layoutGb = self.ui.gbStuff.layout() self.ui.tableStuff = TableWidget(self, DEFAULT_UOM_LENGTH, DEFAULT_UOM_MASS) self.ui.tableStuff.setColumnCount(TBL_STUFF_COL_COUNT) self.ui.tableStuff.setRowCount(TBL_STUFF_ROW_COUNT) self.ui.tableStuff.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.ui.splitter.setStretchFactor(1, 10) layoutGb.addWidget(self.ui.tableStuff) def create_menu(self, language=None, uom_length=None, uom_mass=None): menuMain = self.menuBar() menuFile = menuMain.addMenu(QCoreApplication.translate("main", "&File")) menuFile_New = menuFile.addAction(self.ui.actionNew) menuFile_Open = menuFile.addAction(self.ui.actionOpen) menuFile_Save = menuFile.addAction(self.ui.actionSave) menuFile.addSeparator() menuFile.addAction(self.ui.actionExport) menuFile.addSeparator() menuFile.addAction(self.ui.actionQuit) self.menuSettings = menuMain.addMenu(QCoreApplication.translate("main", "&Settings")) self.menuSettings_Language = self.menuSettings.addMenu(QCoreApplication.translate("main", "Language")) self.menuSettings_UOMs = self.menuSettings.addMenu(QCoreApplication.translate("main", "Unit of Measurements")) self.menuSettings_UOMs_Length = self.menuSettings_UOMs.addMenu(QCoreApplication.translate("main", "Length units")) self.menuSettings_UOMs_Mass = self.menuSettings_UOMs.addMenu(QCoreApplication.translate("main", "Mass units")) ag = QActionGroup(self, exclusive=True) a = ag.addAction(QAction('English', self.menuSettings, checkable=True)) self.menuSettings_Language.addAction(a) a = ag.addAction(QAction('Deutsch', self.menuSettings, checkable=True)) self.menuSettings_Language.addAction(a) a = ag.addAction(QAction('Magyar', self.menuSettings, checkable=True)) self.menuSettings_Language.addAction(a) self.menuSettings_Language.triggered.connect(lambda: self.store_selected_language(self.menuSettings_Language)) if language: [action.setChecked(True) for action in self.menuSettings_Language.actions() if action.text()==language] ag = QActionGroup(self, exclusive=True) a = ag.addAction(QAction("m", self.menuSettings_UOMs_Length, checkable=True)) self.menuSettings_UOMs_Length.addAction(a) a = ag.addAction(QAction("cm", self.menuSettings_UOMs_Length, checkable=True)) self.menuSettings_UOMs_Length.addAction(a) a = ag.addAction(QAction("mm", self.menuSettings_UOMs_Length, checkable=True)) self.menuSettings_UOMs_Length.addAction(a) if uom_length: [action.setChecked(True) for action in self.menuSettings_UOMs_Length.actions() if action.text()==uom_length] ag = QActionGroup(self, exclusive=True) a = ag.addAction(QAction("kg", self.menuSettings_UOMs_Mass, checkable=True)) self.menuSettings_UOMs_Mass.addAction(a) a = ag.addAction(QAction("g", self.menuSettings_UOMs_Mass, checkable=True)) self.menuSettings_UOMs_Mass.addAction(a) self.menuSettings_UOMs_Length.triggered.connect(lambda: self.store_selected_uom_length(self.menuSettings_UOMs_Length)) self.menuSettings_UOMs_Mass.triggered.connect(lambda: self.store_selected_uom_mass(self.menuSettings_UOMs_Mass)) if uom_mass: [action.setChecked(True) for action in self.menuSettings_UOMs_Mass.actions() if action.text()==uom_mass] self.menuHelp = menuMain.addMenu(QCoreApplication.translate("main", "Help")) self.menuHelp_About_Qt = self.menuHelp.addAction(self.ui.actionAbout_Qt) self.menuHelp_About = self.menuHelp.addAction(self.ui.actionAbout) def create_statusbar(self): self.statusBar = QStatusBar() self.setStatusBar(self.statusBar) def create_toolbar(self): # Main Toolbar (for all pages/views) self.toolbar = self.addToolBar('Main Toolbar') self.toolbar.setIconSize(QSize(32, 32)) self.toolbar.addAction(self.ui.actionNew) self.toolbar.addAction(self.ui.actionOpen) self.toolbar.addAction(self.ui.actionSave) self.toolbar.addSeparator() self.toolbar.addAction(self.ui.actionExport) self.toolbar.addSeparator() self.toolbar.addAction(self.ui.actionAbout) self.toolbar.addSeparator() self.toolbar.addAction(self.ui.actionQuit) def set_defaults(self): tblGarage = self.ui.tableGarage tblStuff = self.ui.tableStuff 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(TBL_STUFF_ROW_COUNT) # display results self.ui.efVol_Garage.setText("0") self.ui.efVol_Stuff.setText("0") self.ui.efVol_Free.setText("0") self.ui.efWeight.setText("0") self.is_modified = False def connect_signals(self): global APP_INI_FILE tblGarage = self.ui.tableGarage tblStuff = self.ui.tableStuff tblGarage.itemChanged.connect(self.on_garage_changed) tblStuff.itemChanged.connect(self.on_stuff_changed) self.ui.actionNew.triggered.connect(self.file_new) self.ui.actionOpen.triggered.connect(self.file_open) self.ui.actionSave.triggered.connect(self.file_save) self.ui.actionExport.triggered.connect(self.file_export) self.ui.actionAbout_Qt.triggered.connect(QApplication.aboutQt) self.ui.actionAbout.triggered.connect(lambda: show_about(APP_INI_FILE, self.opened_file)) self.ui.actionQuit.triggered.connect(self.quit_app) def closeEvent(self, event): if self.quit_app(): event.accept() else: event.ignore() def init_ui(self): tblGarage = self.ui.tableGarage tblStuff = self.ui.tableStuff # clear garage # tblGarage.setRowCount(1) # clear stuff ## tblStuff.clear() # tblStuff.setRowCount(0) # tblStuff.setRowCount(TBL_STUFF_ROW_COUNT) tblStuff.clearContents() tblStuff.sortByColumn(-1, Qt.AscendingOrder) # reset sorting # clear results self.ui.efVol_Garage.clear() self.ui.efVol_Stuff.clear() self.ui.efVol_Free.clear() self.ui.efWeight.clear() self.opened_file = None self.setWindowTitle(f"{qApp.applicationName()} {APP_VERSION} - {APP_AUTHOR}") self.ui.efVol_Free.setStyleSheet("") self.is_modified = False def quit_app(self) -> bool: if self.is_modified: msg = QCoreApplication.translate("main", TXT_UNSAVED_CHANGES) reply = QMessageBox.question(self, QCoreApplication.translate("main", "Quit"), msg, \ QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes) if reply == QMessageBox.No: return False QApplication.quit() return False def file_new(self): if self.is_modified: msg = QCoreApplication.translate("main", TXT_UNSAVED_CHANGES) reply = QMessageBox.question(self, QCoreApplication.translate("main", "New"), msg, \ QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes) if reply == QMessageBox.No: return False self.init_ui() self.ui.tableStuff.sortByColumn(-1, Qt.AscendingOrder) # reset sorting optimizeTableLayout(self.ui.tableStuff) self.ui.tableStuff.setFocus() def file_save(self): global DEFAULT_UOM_LENGTH, DEFAULT_UOM_MASS 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() txt_title = QCoreApplication.translate("main", "Save") txt_file = QCoreApplication.translate("main", "CSV-file") txt_all_files = QCoreApplication.translate("main", "All files") fileName, _ = QFileDialog.getSaveFileName(None, txt_title, None, txt_file + " (*.csv);;" + txt_all_files + " (*)", options=options) 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_comment = "" item_length = tblGarage.item(0, COL_GARAGE_LENGTH) item_width = tblGarage.item(0, COL_GARAGE_WIDTH) item_height = tblGarage.item(0, COL_GARAGE_HEIGHT) item_comment = tblGarage.item(0, COL_GARAGE_COMMENT) # loop over table Garage for row in range(tblGarage.rowCount()): # get garage length if item_length: garage_length = item_length.text() # get garage width if item_width: garage_width = item_width.text() # get garage height if item_height: garage_height = item_height.text() # get garage comment if item_comment: garage_comment = item_comment.text() if garage_length or garage_width or garage_height: writer.writerow([QCoreApplication.translate("main","Garage"), garage_length, garage_width, garage_height, garage_comment]) # loop over table Stuff for row in range(tblStuff.rowCount()): stuff_text = None length = None length_uom = None width = None width_uom = None height = None height_uom = None weight = None weight_uom = None comment = None item_stuff = tblStuff.item(row, COL_STUFF_NAME) item_length = tblStuff.item(row, COL_STUFF_LENGTH) item_width = tblStuff.item(row, COL_STUFF_WIDTH) item_height = tblStuff.item(row, COL_STUFF_HEIGHT) item_weight = tblStuff.item(row, COL_STUFF_WEIGHT) item_comment = tblStuff.item(row, COL_STUFF_COMMENT) if item_stuff: stuff_text = item_stuff.text() if item_length: length = item_length.text() if length: length_uom = item_length.data(Qt.UserRole) if not length_uom: length_uom = DEFAULT_UOM_LENGTH if item_width: width = item_width.text() if width: width_uom = item_width.data(Qt.UserRole) if not width_uom: width_uom = DEFAULT_UOM_LENGTH if item_height: height = item_height.text() if height: height_uom = item_height.data(Qt.UserRole) if not height_uom: height_uom = DEFAULT_UOM_LENGTH if item_weight: weight = item_weight.text() if weight: weight_uom = item_weight.data(Qt.UserRole) if not weight_uom: weight_uom = DEFAULT_UOM_MASS if item_comment: comment = item_comment.text() if stuff_text or length or width or height or weight: writer.writerow([stuff_text, length, length_uom, width, width_uom, height, height_uom, weight, weight_uom, comment]) is_file_saved = True self.opened_file = fileName self.setWindowTitle(os.path.basename(fileName)) self.is_modified = False if is_file_saved: msg = QCoreApplication.translate("main", "file") + " '" + fileName + "' " + QCoreApplication.translate("main", "saved") self.statusBar.showMessage(msg, 2000) def file_open(self): if self.is_modified: msg = QCoreApplication.translate("main", TXT_UNSAVED_CHANGES) reply = QMessageBox.question(self, QCoreApplication.translate("main", "Open"), msg, \ QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes) if reply == QMessageBox.No: return False tblGarage = self.ui.tableGarage tblStuff = self.ui.tableStuff options = QFileDialog.Options() txt_title = QCoreApplication.translate("main", "Open") txt_file = QCoreApplication.translate("main", "CSV-file") txt_all_files = QCoreApplication.translate("main", "All files") fileName, _ = QFileDialog.getOpenFileName(self, txt_title, None, txt_file + " (*.csv);;" + txt_all_files + " (*)", options=options) if fileName: self.init_ui() # disable calc trigger for any cell! tblGarage.itemChanged.disconnect() tblStuff.itemChanged.disconnect() file = open(fileName, "r", newline='') reader = csv.reader(file, delimiter=';') row_idx = 0 for row in reader: if row_idx == 0: # first row (index=0) ist always garage dimension try: garage_length = str(row[CSV_COL_GARAGE_LENGTH].replace(".", ",")) garage_width = str(row[CSV_COL_GARAGE_WIDTH].replace(".", ",")) garage_height = str(row[CSV_COL_GARAGE_HEIGHT].replace(".", ",")) garage_comment = row[CSV_COL_GARAGE_COMMENT] tblGarage.setItem(0, COL_GARAGE_LENGTH, QTableWidgetItem(garage_length)) tblGarage.setItem(0, COL_GARAGE_WIDTH, QTableWidgetItem(garage_width)) tblGarage.setItem(0, COL_GARAGE_HEIGHT, QTableWidgetItem(garage_height)) tblGarage.setItem(0, COL_GARAGE_COMMENT, QTableWidgetItem(garage_comment)) except IndexError as ex: pass if row_idx > 0: try: stuff = row[COL_STUFF_NAME] stuff_length = str(row[CSV_COL_STUFF_LENGTH].replace(".", ",")) stuff_length_uom = row[CSV_COL_STUFF_LENGTH_UOM] stuff_width = str(row[CSV_COL_STUFF_WIDTH].replace(".", ",")) stuff_width_uom = row[CSV_COL_STUFF_WIDTH_UOM] stuff_height = str(row[CSV_COL_STUFF_HEIGHT].replace(".", ",")) stuff_height_uom = row[CSV_COL_STUFF_HEIGHT_UOM] stuff_weight = str(row[CSV_COL_STUFF_WEIGHT].replace(".", ",")) stuff_weight_uom = str(row[CSV_COL_STUFF_WEIGHT_UOM].replace(".", ",")) stuff_comment = row[CSV_COL_STUFF_COMMENT] tblStuff.setItem(row_idx - 1, COL_STUFF_NAME, QTableWidgetItem(stuff)) item = QTableWidgetItem() item.setData(Qt.DisplayRole, stuff_length) tblStuff.setItem(row_idx - 1, COL_STUFF_LENGTH, item) tblStuff.setUOM(item, stuff_length_uom) # tblStuff.setItem(row_idx - 1, COL_STUFF_WIDTH, QTableWidgetItem(float(stuff_width))) item = QTableWidgetItem() item.setData(Qt.DisplayRole, stuff_width) tblStuff.setItem(row_idx - 1, COL_STUFF_WIDTH, item) tblStuff.setUOM(item, stuff_width_uom) #tblStuff.setItem(row_idx - 1, COL_STUFF_HEIGHT, QTableWidgetItem(float(stuff_height))) item = QTableWidgetItem(stuff_height) item.setData(Qt.DisplayRole, stuff_height) tblStuff.setItem(row_idx - 1, COL_STUFF_HEIGHT, item) tblStuff.setUOM(item, stuff_height_uom) # tblStuff.setItem(row_idx - 1, COL_STUFF_WEIGHT, QTableWidgetItem(float(stuff_weight))) item = QTableWidgetItem(stuff_weight) item.setData(Qt.DisplayRole, stuff_weight) tblStuff.setItem(row_idx - 1, COL_STUFF_WEIGHT, item) tblStuff.setUOM(item, stuff_weight_uom) tblStuff.setItem(row_idx - 1, COL_STUFF_COMMENT, QTableWidgetItem(stuff_comment)) except IndexError as ex: pass row_idx += 1 # enable calc trigger tblGarage.itemChanged.connect(self.on_garage_changed) tblStuff.itemChanged.connect(self.on_stuff_changed) # fire trigger once self.on_garage_changed() self.on_stuff_changed() optimizeTableLayout(self.ui.tableStuff) self.ui.tableStuff.setFocus() self.opened_file = fileName self.setWindowTitle(os.path.basename(fileName)) self.is_modified = False def file_export(self): tblGarage = self.ui.tableGarage tblStuff = self.ui.tableStuff options = QFileDialog.Options() txt_title = QCoreApplication.translate("main", "Export") txt_file = QCoreApplication.translate("main", "EXCEL-file") txt_all_files = QCoreApplication.translate("main", "All files") file_name, _ = QFileDialog.getSaveFileName(None, txt_title, None, txt_file + " (*.xlsx);;" + txt_all_files + " (*)", options=options) if file_name: print(f"Exporting into file -> {file_name}") workbook = xlsxwriter.Workbook(file_name) worksheet = workbook.add_worksheet() # write col header start_row = 0 worksheet.write_string(start_row, 0, QCoreApplication.translate("main", "Dimension of the garage")) # table Garage headerts start_row = 1 worksheet.write_string(start_row, COL_STUFF_LENGTH, QCoreApplication.translate("main","Length") + " [m]") worksheet.write_string(start_row, COL_STUFF_WIDTH, QCoreApplication.translate("main","Width") + " [m]") worksheet.write_string(start_row, COL_STUFF_HEIGHT, QCoreApplication.translate("main","Height") + " [m]") worksheet.write_string(start_row, COL_STUFF_COMMENT, QCoreApplication.translate("main","Comment")) # TODO: check if this is not done later automatically # worksheet.set_column(0, 0, 25) # worksheet.set_column(1, 3, 10) # worksheet.set_column(4, 5, 20) # loop over table Garage start_row = 2 for row in range(tblGarage.rowCount()): garage_length = tblGarage.item(0, 0).text() garage_width = tblGarage.item(0, 1).text() garage_height = tblGarage.item(0, 2).text() garage_comment = tblGarage.item(0, 3).text() worksheet.write_number(start_row + row, COL_STUFF_LENGTH, float(garage_length.replace(',', '.'))) worksheet.write_number(start_row + row, COL_STUFF_WIDTH, float(garage_width.replace(',', '.'))) worksheet.write_number(start_row + row, COL_STUFF_HEIGHT, float(garage_height.replace(',', '.'))) worksheet.write_string(start_row + row, COL_STUFF_COMMENT, garage_comment) start_row = 4 worksheet.write_string(start_row, 0, QCoreApplication.translate("main", "Dimensions of the objects to be stored")) # table Stuff headers start_row = 5 worksheet.write_string(start_row, COL_STUFF_LENGTH, QCoreApplication.translate("main","Length") + " [m]") worksheet.write_string(start_row, COL_STUFF_WIDTH, QCoreApplication.translate("main","Width") + " [m]") worksheet.write_string(start_row, COL_STUFF_HEIGHT, QCoreApplication.translate("main","Height") + " [m]") worksheet.write_string(start_row, COL_STUFF_WEIGHT, QCoreApplication.translate("main","Weight") + " [kg]") worksheet.write_string(start_row, COL_STUFF_COMMENT, QCoreApplication.translate("main","Comment")) # loop over table Stuff start_row = 6 formula = "" row_idx = start_row for row in range(tblStuff.rowCount()): item_stuff = tblStuff.item(row, COL_STUFF_NAME) item_length = tblStuff.item(row, COL_STUFF_LENGTH) item_width = tblStuff.item(row, COL_STUFF_WIDTH) item_height = tblStuff.item(row, COL_STUFF_HEIGHT) item_weight = tblStuff.item(row, COL_STUFF_WEIGHT) item_comment = tblStuff.item(row, COL_STUFF_COMMENT) if item_stuff: stuff_text = item_stuff.text() if len(stuff_text)>0: worksheet.write_string(start_row + row, COL_STUFF_NAME, stuff_text) if item_length: try: length = item_length.text() if length: value = float(length.replace(',', '.')) uom = item_length.data(Qt.UserRole) # always convert to "m" for the export if uom != "m": value = convert_uom_to_length(value, uom, "m") worksheet.write_number(start_row + row, COL_STUFF_LENGTH, value) # value except ValueError: pass if item_width: try: width = item_width.text() if width: value = float(width.replace(',', '.')) uom = item_width.data(Qt.UserRole) # always convert to "m" for the export if uom != "m": value = convert_uom_to_length(value, uom, "m") worksheet.write_number(start_row + row, COL_STUFF_WIDTH, value) except ValueError: pass if item_height: try: height = item_height.text() if height: value = float(height.replace(',', '.')) uom = item_height.data(Qt.UserRole) # always convert to "m" for the export if uom != "m": value = convert_uom_to_length(value, uom, "m") worksheet.write_number(start_row + row, COL_STUFF_HEIGHT, value) except ValueError: pass if item_weight: try: weight = item_weight.text() if weight: value = float(weight.replace(',', '.')) uom = item_weight.data(Qt.UserRole) # always convert to "kg" for the export if uom != "kg": value = convert_uom_to_mass(value, uom, "kg") worksheet.write_number(start_row + row, COL_STUFF_WEIGHT, value) except ValueError: pass if item_comment: comment = item_comment.text() if len(comment)>0: worksheet.write_string(start_row + row, COL_STUFF_COMMENT, comment) if item_stuff or item_length or item_width or item_height or item_weight: row_idx += 1 formula = formula + str_iff(formula, "", "=ROUND(", " + ") + f"(B{row_idx} * C{row_idx} * D{row_idx})" formula += ", 2)" row_idx += 1 # loop over Results worksheet.write_string(row_idx, 0, QCoreApplication.translate("main","Result")) row_idx += 1 worksheet.write_string(row_idx, 0, QCoreApplication.translate("main","Volume of the garage") + ":") # worksheet.write(row_idx, 1, self.ui.efVol_Garage.text()) worksheet.write_formula(row_idx, 1, '=B3 * C3 * D3') row_idx += 1 worksheet.write_string(row_idx, 0, QCoreApplication.translate("main","Volume of the items") + ":") #worksheet.write_number(row_idx, 1, float(self.ui.efVol_Stuff.text().replace(',', '.'))) worksheet.write_formula(row_idx, 1, formula) row_idx += 1 worksheet.write_string(row_idx, 0, QCoreApplication.translate("main","Free space in the garage") + ":") # worksheet.write(row_idx, 1, self.ui.efVol_Free.text()) worksheet.write_formula(row_idx, 1, f"=B{row_idx-1}-B{row_idx}") row_idx += 1 worksheet.write_string(row_idx, 0, QCoreApplication.translate("main", "Total weight") + ":") # worksheet.write(row_idx, 1, self.ui.efWeight.text()) worksheet.write_formula(row_idx, 1, f"=SUM(E7:E{row_idx-5}") fit_col_widths(tblStuff, worksheet) workbook.close() msg = QCoreApplication.translate("main", "Successfully exported to EXCEL") + " :-)" self.statusBar.showMessage(msg, 5000) def on_garage_changed(self): self.is_modified = True # print("on_garage_changed() calling calc_voluminae()") self.calc_voluminae() # print("--") def on_stuff_changed(self): self.is_modified = True # print("on_stuff_changed() calling calc_voluminae()") self.calc_voluminae() # print("on_stuff_changed() calling calc_weight()") self.calc_weight() # print("--") def get_garage_vol(self): tblGarage = self.ui.tableGarage is_error = False garage_length = 0 garage_width = 0 garage_height = 0 # get garage length if tblGarage.item(0, 0): garage_length = tblGarage.item(0, 0).text() if garage_length: try: garage_length = float(garage_length.replace(',', '.')) except ValueError: garage_length = 0.0 is_error = True # get garage width if tblGarage.item(0, 1): garage_width = tblGarage.item(0, 1).text() if garage_width: try: garage_width = float(garage_width.replace(',', '.')) except ValueError: garage_width = 0.0 is_error = True # get garage height if tblGarage.item(0, 2): garage_height = tblGarage.item(0, 2).text() if garage_height: try: garage_height = float(garage_height.replace(',', '.')) except ValueError: garage_height = 0.0 is_error = True if not is_error: garage_vol = round(garage_length * garage_width * garage_height, 2) else: msg = QCoreApplication.translate("main", "Error in the garage dimension") + " :-(" self.statusBar.showMessage(msg, 5000) return garage_vol def get_stuff_vol(self): global DEFAULT_UOM_LENGTH tblStuff = self.ui.tableStuff stuff_vol = 0 length = 0 width = 0 height = 0 # get number of rows row_count = tblStuff.rowCount() is_error = False # get stuff voluminae for row in range(row_count): vol = 0 length = 0 width = 0 height = 0 item_length = tblStuff.item(row, COL_STUFF_LENGTH) item_width = tblStuff.item(row, COL_STUFF_WIDTH) item_height = tblStuff.item(row, COL_STUFF_HEIGHT) if item_length: try: length = float(item_length.text().replace(',', '.')) if item_length.data(Qt.UserRole) and item_length.data(Qt.UserRole) != DEFAULT_UOM_LENGTH: length = convert_uom_to_length(length, item_length.data(Qt.UserRole), DEFAULT_UOM_LENGTH) # convert to std unit except ValueError: length = 0 is_error = True if item_width: try: width = float(item_width.text().replace(',', '.')) if item_width.data(Qt.UserRole) and item_width.data(Qt.UserRole) != DEFAULT_UOM_LENGTH: width = convert_uom_to_length(width, item_width.data(Qt.UserRole), DEFAULT_UOM_LENGTH) # convert to std unit except ValueError: width = 0 is_error = True if item_height: try: height = float(item_height.text().replace(',', '.')) if item_height.data(Qt.UserRole) and item_height.data(Qt.UserRole) != DEFAULT_UOM_LENGTH: height = convert_uom_to_length(height, item_height.data(Qt.UserRole), DEFAULT_UOM_LENGTH) # convert to std unit except ValueError: height = 0 is_error = True vol = convert_uom_to_length(length, DEFAULT_UOM_LENGTH, "m") * convert_uom_to_length(width, DEFAULT_UOM_LENGTH, "m") * convert_uom_to_length(height, DEFAULT_UOM_LENGTH, "m") stuff_vol = round(stuff_vol + vol, 2) if is_error: # stuff_vol = 0.0 msg = QCoreApplication.translate("main", "Error in the dimensions of the objects to be stored") + " :-(" self.statusBar.showMessage(msg, 5000) return stuff_vol def calc_voluminae(self): # print("calc_voluminae()") global DEFAULT_UOM_LENGTH tblGarage = self.ui.tableGarage # get garage vol # print(" calc_voluminae() calling get_garage_vol()") garage_vol = self.get_garage_vol() # print(f"{garage_vol=}") # get stuff vol # print(" calc_voluminae() calling get_stuff_vol()") stuff_vol = self.get_stuff_vol() # get free space free_vol = garage_vol - stuff_vol # display results self.ui.efVol_Garage.setText(f"{str(garage_vol).replace('.', ',')}") self.ui.efVol_Stuff.setText(f"{str(stuff_vol).replace('.', ',')}") self.ui.efVol_Free.setText(f"{str(free_vol).replace('.', ',')}") if free_vol < 0: self.ui.efVol_Free.setStyleSheet("border: 1px solid red;") else: self.ui.efVol_Free.setStyleSheet("") def calc_weight(self): # print("calc_weight()") global DEFAULT_UOM_MASS tblStuff = self.ui.tableStuff weight_sum = 0 # get number of rows row_count = tblStuff.rowCount() for row in range(row_count): weight = 0.0 item_weight = tblStuff.item(row, COL_STUFF_WEIGHT) if item_weight: try: weight = float(item_weight.text().replace(',', '.')) if item_weight.data(Qt.UserRole) and item_weight.data(Qt.UserRole) != DEFAULT_UOM_MASS: weight = convert_uom_to_mass(weight, item_weight.data(Qt.UserRole), DEFAULT_UOM_MASS) # convert to std unit except ValueError: weight = 0 weight_sum = round(weight_sum + convert_uom_to_mass(weight, DEFAULT_UOM_MASS, "kg"), 2) self.ui.efWeight.setText(f"{str(weight_sum).replace('.', ',')}") def store_selected_language(self, menu): """ Stores selected menu labels to ini-file. """ # [print(action.text()) for action in menu.actions() if action.isChecked()] global CONFIG, DIR_CURRENT, APP_INI_FILE language = "English" for action in menu.actions(): if action.isChecked(): language = action.text() CONFIG['DEFAULT']['language'] = language with open(APP_INI_FILE, 'w') as configfile: # save CONFIG.write(configfile) if language == "Deutsch": print("Loading german language file.") TRANSLATOR.load(resource_path('./i18n/de_DE')) elif language == "Magyar": print("Loading hungarian langauge file.") TRANSLATOR.load(resource_path('./i18n/hu_HU')) else: print(f"Unknown language setting '{language}' -> defaulting to english language.") TRANSLATOR.load("dummy") self.retranslateUi() optimizeTableLayout(self.ui.tableStuff) self.ui.tableStuff.setFocus() def store_selected_uom_length(self, menu): """ Stores selected uom to ini-file. """ # [print(action.text()) for action in menu.actions() if action.isChecked()] global CONFIG, APP_INI_FILE, DEFAULT_UOM_LENGTH selected_uom = "" for action in menu.actions(): if action.isChecked(): selected_uom = action.text() CONFIG['DEFAULT']['UOM_length'] = selected_uom with open(APP_INI_FILE, 'w') as configfile: # save CONFIG.write(configfile) DEFAULT_UOM_LENGTH = selected_uom self.ui.tableStuff.default_uom_length = DEFAULT_UOM_LENGTH if table_has_items(self.ui.tableStuff): msg = QCoreApplication.translate("main", "Do you want to convert all existing stuff of type length to the new unit of measurement (UOM)") + " " + selected_uom + "?" reply = QMessageBox.question(self, QCoreApplication.translate("main", "UOM conversion"), msg, \ QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes) if reply == QMessageBox.No: self.flag_deviating_uoms() self.retranslateUi() optimizeTableLayout(self.ui.tableStuff) return False self.convert_length_cells_to_std_uom() self.retranslateUi() optimizeTableLayout(self.ui.tableStuff) return True def store_selected_uom_mass(self, menu): """ Stores selected uom to ini-file. """ # [print(action.text()) for action in menu.actions() if action.isChecked()] global CONFIG, APP_INI_FILE, DEFAULT_UOM_MASS selected_uom = "" for action in menu.actions(): if action.isChecked(): selected_uom = action.text() CONFIG['DEFAULT']['UOM_mass'] = selected_uom with open(APP_INI_FILE, 'w') as configfile: # save CONFIG.write(configfile) DEFAULT_UOM_MASS = selected_uom self.ui.tableStuff.default_uom_mass = DEFAULT_UOM_MASS if table_has_items(self.ui.tableStuff): msg = QCoreApplication.translate("main", "Do you want to convert all existing stuff of type mass to the new unit of measurement (UOM)") + " " + selected_uom + "?" reply = QMessageBox.question(self, QCoreApplication.translate("main", "UOM conversion"), msg, \ QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes) if reply == QMessageBox.No: self.flag_deviating_uoms() self.retranslateUi() optimizeTableLayout(self.ui.tableStuff) return False self.convert_mass_cells_to_std_uom() self.retranslateUi() optimizeTableLayout(self.ui.tableStuff) return True def convert_length_cells_to_std_uom(self): print("convert_length_cells_to_std_uom() called") global DEFAULT_UOM_LENGTH tblGarage = self.ui.tableGarage tblStuff = self.ui.tableStuff # loop over all items # disable calc trigger for any cell! tblGarage.itemChanged.disconnect() tblStuff.itemChanged.disconnect() for row in range(tblStuff.rowCount()): for col in range(tblStuff.columnCount()): item = tblStuff.item(row, col) if item: if col in self.cols_with_uom_length: # read current value value = float(item.text().replace(",", ".")) # read current UOM uom = item.data(Qt.UserRole) # convert to new value if uom != DEFAULT_UOM_LENGTH: new_val = convert_uom_to_length(value, uom, DEFAULT_UOM_LENGTH) # set new value item.setText(str(new_val).replace(".", ",")) # set new std UOM tblStuff.setUOM(item, DEFAULT_UOM_LENGTH) # enable calc trigger tblGarage.itemChanged.connect(self.on_garage_changed) tblStuff.itemChanged.connect(self.on_stuff_changed) # fire trigger once self.on_garage_changed() self.on_stuff_changed() def convert_mass_cells_to_std_uom(self): print("convert_mass_cells_to_std_uom() called") global DEFAULT_UOM_MASS tblGarage = self.ui.tableGarage tblStuff = self.ui.tableStuff # loop over all items # disable calc trigger for any cell! tblGarage.itemChanged.disconnect() tblStuff.itemChanged.disconnect() for row in range(tblStuff.rowCount()): for col in range(tblStuff.columnCount()): item = tblStuff.item(row, col) if item: if col in self.cols_with_uom_mass: # read current value value = float(item.text().replace(",", ".")) # read current UOM uom = item.data(Qt.UserRole) if uom != DEFAULT_UOM_MASS: new_val = convert_uom_to_mass(value, uom, DEFAULT_UOM_MASS) # set new value item.setText(str(new_val).replace(".", ",")) # set new std UOM tblStuff.setUOM(item, DEFAULT_UOM_MASS) # enable calc trigger tblGarage.itemChanged.connect(self.on_garage_changed) tblStuff.itemChanged.connect(self.on_stuff_changed) # fire trigger once self.on_garage_changed() self.on_stuff_changed() def flag_deviating_uoms(self): print("flag_deviating_uoms() called") global DEFAULT_UOM_LENGTH, DEFAULT_UOM_MASS tblGarage = self.ui.tableGarage tblStuff = self.ui.tableStuff # loop over all items # disable calc trigger for any cell! tblGarage.itemChanged.disconnect() tblStuff.itemChanged.disconnect() for row in range(tblStuff.rowCount()): for col in range(tblStuff.columnCount()): item = tblStuff.item(row, col) if item: uom = item.data(Qt.UserRole) if col in self.cols_with_uom_length: # read current UOM if uom != DEFAULT_UOM_LENGTH: # color the cell item.setData(Qt.BackgroundRole, QColor(Qt.yellow)) item.setToolTip(f"[{uom}]") else: # uncolor the cell item.setData(Qt.BackgroundRole, None) item.setToolTip(None) elif col in self.cols_with_uom_mass: # read current UOM if uom != DEFAULT_UOM_MASS: # color the cell item.setData(Qt.BackgroundRole, QColor(Qt.yellow)) item.setToolTip(f"[{uom}]") else: # uncolor the cell item.setData(Qt.BackgroundRole, None) item.setToolTip(None) # enable calc trigger tblGarage.itemChanged.connect(self.on_garage_changed) tblStuff.itemChanged.connect(self.on_stuff_changed) #################################################################### if __name__ == "__main__": main()