GarageCalc1/src/main.py

1261 lines
52 KiB
Python

#!/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()