Skip to content

Commit 5d63a0d

Browse files
committed
adds containers
1 parent c5460e5 commit 5d63a0d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+203175
-199366
lines changed

src/artisanlib/canvas.py

+4
Original file line numberDiff line numberDiff line change
@@ -2444,6 +2444,10 @@ def dummy_or_special_device(self, device_id:int, channel_offset:int) -> bool:
24442444
device_id in self.specialDevices
24452445
)
24462446

2447+
def get_container_weight(self, container_idx:int) -> Optional[int]:
2448+
if len(self.container_weights) > container_idx >= 0:
2449+
return self.container_weights[container_idx]
2450+
return None
24472451

24482452
# toggles the y cursor coordinate see self.fmt_data_curve
24492453
def nextFmtDataCurve(self) -> None:

src/artisanlib/devices.py

+182-11
Large diffs are not rendered by default.

src/artisanlib/dialogs.py

+172-21
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,20 @@
2020
import re
2121

2222
try:
23-
from PyQt6.QtCore import Qt, QEvent, QSettings, pyqtSlot, QRegularExpression # @UnusedImport @Reimport @UnresolvedImport
23+
from PyQt6.QtCore import Qt, QEvent, QSettings, pyqtSlot, pyqtSignal, QRegularExpression # @UnusedImport @Reimport @UnresolvedImport
2424
from PyQt6.QtWidgets import (QApplication, QWidget, QDialog, QMessageBox, QDialogButtonBox, QTextEdit, # @UnusedImport @Reimport @UnresolvedImport
25-
QHBoxLayout, QVBoxLayout, QLabel, QLineEdit, QLayout) # @UnusedImport @Reimport @UnresolvedImport
25+
QHBoxLayout, QVBoxLayout, QLabel, QLineEdit, QLayout, QTableWidget, QHeaderView, QPushButton) # @UnusedImport @Reimport @UnresolvedImport
2626
from PyQt6.QtGui import QKeySequence, QAction, QIntValidator, QTextCharFormat, QTextCursor, QColor # @UnusedImport @Reimport @UnresolvedImport
2727
except ImportError:
28-
from PyQt5.QtCore import Qt, QEvent, QSettings, pyqtSlot, QRegularExpression # type: ignore # @UnusedImport @Reimport @UnresolvedImport
28+
from PyQt5.QtCore import Qt, QEvent, QSettings, pyqtSlot, pyqtSignal, QRegularExpression # type: ignore # @UnusedImport @Reimport @UnresolvedImport
2929
from PyQt5.QtWidgets import (QApplication, QWidget, QAction, QDialog, QMessageBox, QDialogButtonBox, QTextEdit, # type: ignore # @UnusedImport @Reimport @UnresolvedImport
30-
QHBoxLayout, QVBoxLayout, QLabel, QLineEdit, QLayout) # @UnusedImport @Reimport @UnresolvedImport
30+
QHBoxLayout, QVBoxLayout, QLabel, QLineEdit, QLayout, QTableWidget, QHeaderView, QPushButton) # @UnusedImport @Reimport @UnresolvedImport
3131
from PyQt5.QtGui import QKeySequence, QIntValidator, QTextCharFormat, QTextCursor, QColor # type: ignore # @UnusedImport @Reimport @UnresolvedImport
3232

33-
from artisanlib.widgets import MyQComboBox
33+
from artisanlib.widgets import MyQComboBox, ClickableQLineEdit
34+
from artisanlib.util import comma2dot
3435

35-
from typing import Optional, List, Tuple, TYPE_CHECKING
36+
from typing import Optional, List, Tuple, cast, Callable, TYPE_CHECKING
3637
from typing import Final # Python <=3.7
3738
if TYPE_CHECKING:
3839
from artisanlib.main import ApplicationWindow # pylint: disable=unused-import
@@ -179,15 +180,15 @@ def __init__(self, parent:QWidget, aw:'ApplicationWindow', title:str = '', conte
179180
# Initialize search state variables
180181
self.matches: List[QTextCursor] = []
181182
self.current_match_index = 0
182-
self.previous_search_term = ""
183+
self.previous_search_term = ''
183184

184185
# Search bar
185186
self.search_input = QLineEdit()
186187
self.search_input.setPlaceholderText(QApplication.translate('Label', 'Enter text to search'))
187188

188189
# Connect Enter key to search and navigate results
189190
self.search_input.returnPressed.connect(self.doSearch)
190-
191+
191192
# Show only the ArtisanDialog standard OK button
192193
self.dialogbuttons.removeButton(self.dialogbuttons.button(QDialogButtonBox.StandardButton.Cancel))
193194

@@ -251,31 +252,31 @@ def doSearch(self) -> None:
251252
tc = self.phelp.textCursor()
252253
tc.clearSelection()
253254
self.phelp.setTextCursor(tc)
254-
255+
255256
search_term = self.search_input.text().strip()
256-
257+
257258
# Clear highlights and state when search term is empty
258-
if search_term == "":
259+
if search_term == '':
259260
self.matches = []
260-
self.previous_search_term = ""
261+
self.previous_search_term = ''
261262
return
262263

263264
# Do a fresh search when a new search_term is entered
264265
if self.previous_search_term.lower() != search_term.lower():
265266
self.previous_search_term = search_term
266267
self.current_match_index = 0
267268
self.matches = []
268-
269+
269270
# Create a case-insensitive regular expression.
270271
regex = QRegularExpression(re.escape(search_term))
271272
regex.setPatternOptions(QRegularExpression.PatternOption.CaseInsensitiveOption)
272-
273+
273274
# Start at the beginning of the document.
274275
cursor = self.phelp.textCursor()
275276
cursor.movePosition(QTextCursor.MoveOperation.Start)
276-
277+
277278
# Collect all matches.
278-
for _ in range(500000): # arbitrarily large limit, better than while True, should always exit via break
279+
for _ in range(500000): # arbitrarily large limit, better than while True, should always exit via break
279280
found = self.phelp.document().find(regex, cursor) # type: ignore #self.phelp.document() will never be None
280281
if found.isNull():
281282
break
@@ -293,10 +294,10 @@ def doSearch(self) -> None:
293294
self.current_match_index = (self.current_match_index + 1) % len(self.matches)
294295

295296
extraSelections = []
296-
match_text = "black"
297-
current_match_highlight = "#A6FF00"
298-
extra_matches_highlight = "yellow"
299-
297+
match_text = 'black'
298+
current_match_highlight = '#A6FF00'
299+
extra_matches_highlight = 'yellow'
300+
300301
if self.matches:
301302
# Highlight all matches, the current match in current_match_highlight and all others in extra_matches_highlight
302303
for i, matchCursor in enumerate(self.matches):
@@ -309,7 +310,7 @@ def doSearch(self) -> None:
309310
else:
310311
fmt.setBackground(QColor(extra_matches_highlight))
311312
if self.aw.app.darkmode:
312-
fmt.setForeground(QColor("black"))
313+
fmt.setForeground(QColor('black'))
313314
selection.format = fmt
314315
extraSelections.append(selection)
315316
# Move the visible cursor to the current match and clear its active selection
@@ -578,3 +579,153 @@ def __init__(self, parent:QWidget, aw:'ApplicationWindow', value:int, value_min:
578579
def accept(self) -> None:
579580
self.value = int(self.valueEdit.text())
580581
super().accept()
582+
583+
584+
##########################################################################
585+
##################### VIEW Tare ########################################
586+
##########################################################################
587+
588+
589+
class tareDlg(ArtisanDialog):
590+
tare_updated_signal = pyqtSignal() # signalled after tare data table got updated
591+
592+
def __init__(self, parent:ArtisanDialog, aw:'ApplicationWindow', get_scale_weight: Callable[[], Optional[float]]) -> None:
593+
super().__init__(parent, aw)
594+
self.parent_dialog = parent
595+
self.get_scale_weight = get_scale_weight
596+
self.setModal(True)
597+
self.setWindowTitle(QApplication.translate('Form Caption','Containers'))
598+
599+
self.taretable = QTableWidget()
600+
self.taretable.setTabKeyNavigation(True)
601+
self.createTareTable()
602+
603+
self.taretable.itemSelectionChanged.connect(self.selectionChanged)
604+
605+
addButton = QPushButton(QApplication.translate('Button','Add'))
606+
addButton.setFocusPolicy(Qt.FocusPolicy.NoFocus)
607+
self.delButton = QPushButton(QApplication.translate('Button','Delete'))
608+
self.delButton.setDisabled(True)
609+
self.delButton.setFocusPolicy(Qt.FocusPolicy.NoFocus)
610+
611+
addButton.clicked.connect(self.addTare)
612+
self.delButton.clicked.connect(self.delTare)
613+
614+
okButton = QPushButton(QApplication.translate('Button','OK'))
615+
cancelButton = QPushButton(QApplication.translate('Button','Cancel'))
616+
cancelButton.setFocusPolicy(Qt.FocusPolicy.NoFocus)
617+
okButton.clicked.connect(self.accept)
618+
cancelButton.clicked.connect(self.reject)
619+
contentbuttonLayout = QHBoxLayout()
620+
contentbuttonLayout.addStretch()
621+
contentbuttonLayout.addWidget(addButton)
622+
contentbuttonLayout.addWidget(self.delButton)
623+
contentbuttonLayout.addStretch()
624+
625+
buttonLayout = QHBoxLayout()
626+
buttonLayout.addStretch()
627+
buttonLayout.addWidget(cancelButton)
628+
buttonLayout.addWidget(okButton)
629+
layout = QVBoxLayout()
630+
layout.addWidget(self.taretable)
631+
layout.addLayout(contentbuttonLayout)
632+
layout.addLayout(buttonLayout)
633+
self.setLayout(layout)
634+
self.setMinimumWidth(230)
635+
self.setMinimumHeight(250)
636+
637+
@pyqtSlot()
638+
def selectionChanged(self) -> None:
639+
if len(self.taretable.selectedRanges()) > 0:
640+
self.delButton.setDisabled(False)
641+
else:
642+
self.delButton.setDisabled(False)
643+
644+
@pyqtSlot()
645+
def accept(self) -> None:
646+
self.saveTareTable()
647+
self.tare_updated_signal.emit()
648+
self.close()
649+
super().accept()
650+
651+
def setTableRow(self, row:int, name:str, weight:float) -> None:
652+
name_widget = QLineEdit()
653+
name_widget.setAlignment(Qt.AlignmentFlag.AlignRight)
654+
name_widget.setText(name)
655+
weight_widget = ClickableQLineEdit()
656+
weight_widget.setAlignment(Qt.AlignmentFlag.AlignRight)
657+
weight_widget.setText(str(weight))
658+
weight_widget.setValidator(QIntValidator(0,1999, weight_widget))
659+
weight_widget.editingFinished.connect(self.weightEdited)
660+
self.taretable.setCellWidget(row, 0, name_widget)
661+
self.taretable.setCellWidget(row, 1, weight_widget)
662+
663+
@pyqtSlot(bool)
664+
def addTare(self, _:bool = False) -> None:
665+
rows = self.taretable.rowCount()
666+
self.taretable.setRowCount(rows + 1)
667+
weight = self.get_scale_weight() # read value from scale in 'g' (or None)
668+
if weight is None or weight < 0:
669+
weight = 0
670+
#add widgets to the table
671+
self.setTableRow(rows, QApplication.translate('Label', 'container'), weight)
672+
673+
@pyqtSlot(bool)
674+
def delTare(self, _:bool = False) -> None:
675+
selected = self.taretable.selectedRanges()
676+
if len(selected) > 0:
677+
bindex = selected[0].topRow()
678+
if bindex >= 0:
679+
self.taretable.removeRow(bindex)
680+
681+
def saveTareTable(self) -> None:
682+
tars = self.taretable.rowCount()
683+
names = []
684+
weights = []
685+
for i in range(tars):
686+
nameWidget = cast(QLineEdit, self.taretable.cellWidget(i,0))
687+
name = nameWidget.text()
688+
weightWidget = cast(QLineEdit, self.taretable.cellWidget(i,1))
689+
weight = 0
690+
try:
691+
weight = int(round(float(comma2dot(weightWidget.text()))))
692+
except Exception: # pylint: disable=broad-except
693+
pass
694+
names.append(name)
695+
weights.append(weight)
696+
self.aw.qmc.container_names = names
697+
self.aw.qmc.container_weights = weights
698+
699+
@pyqtSlot()
700+
def weightEdited(self) -> None:
701+
_log.debug('PRINT weightEdited')
702+
sender = self.sender()
703+
if sender and isinstance(sender, QLineEdit):
704+
text = sender.text().strip()
705+
if text == '':
706+
w = self.get_scale_weight() # read value from scale in 'g'
707+
sender.setText(str(w if w is not None and w > 0 else 0))
708+
709+
def createTareTable(self) -> None:
710+
self.taretable.clear()
711+
self.taretable.setRowCount(len(self.aw.qmc.container_names))
712+
self.taretable.setColumnCount(2)
713+
self.taretable.setHorizontalHeaderLabels([QApplication.translate('Table','Name'),
714+
QApplication.translate('Table','Weight')])
715+
self.taretable.setAlternatingRowColors(True)
716+
self.taretable.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers)
717+
self.taretable.setSelectionBehavior(QTableWidget.SelectionBehavior.SelectRows)
718+
self.taretable.setSelectionMode(QTableWidget.SelectionMode.SingleSelection)
719+
self.taretable.setShowGrid(True)
720+
vheader: Optional[QHeaderView] = self.taretable.verticalHeader()
721+
if vheader is not None:
722+
vheader.setSectionResizeMode(QHeaderView.ResizeMode.Fixed)
723+
for i, cn in enumerate(self.aw.qmc.container_names):
724+
#add widgets to the table
725+
self.setTableRow(i, cn, self.aw.qmc.container_weights[i])
726+
727+
header: Optional[QHeaderView] = self.taretable.horizontalHeader()
728+
if header is not None:
729+
header.setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)
730+
header.setSectionResizeMode(1, QHeaderView.ResizeMode.Fixed)
731+
self.taretable.setColumnWidth(1,65)

src/artisanlib/main.py

+9-3
Original file line numberDiff line numberDiff line change
@@ -1496,7 +1496,7 @@ class ApplicationWindow(QMainWindow): # pyright: ignore [reportGeneralTypeIssue
14961496
'weblcds_index_path', 'weblcds_websocket_path',
14971497
'taskWebDisplayGreenActive', 'taskWebDisplayGreenPort', 'taskWebDisplayRoastedActive', 'taskWebDisplayRoastedPort',
14981498
'taskWebDisplayRoastedIndexPath', 'taskWebDisplayRoastedWebSocketPath', 'taskWebDisplayGreen_server', 'taskWebDisplayRoasted_server',
1499-
'scale_manager', 'scale1_model', 'scale1_id', 'scale2_model', 'scale2_name', 'scale2_id',
1499+
'scale_manager', 'scale1_model', 'scale1_id', 'container1_idx', 'scale2_model', 'scale2_name', 'scale2_id', 'container2_idx',
15001500
'WebLCDsAlerts', 'EventsDlg_activeTab', 'graphColorDlg_activeTab', 'PID_DlgControl_activeTab', 'CurveDlg_activeTab', 'editGraphDlg_activeTab',
15011501
'backgroundDlg_activeTab', 'DeviceAssignmentDlg_activeTab', 'AlarmDlg_activeTab', 'schedule_activeTab', 'StatisticsDlg_activeTab', 'resetqsettings', 'settingspath', 'wheelpath', 'profilepath',
15021502
'userprofilepath', 'printer', 'main_widget', 'defaultdpi', 'dpi', 'qmc', 'HottopControlActive', 'AsyncSamplingTimer', 'wheeldialog',
@@ -1689,10 +1689,12 @@ def __init__(self, parent:Optional[QWidget] = None, *, locale:str, WebEngineSupp
16891689
self.scale1_model:Optional[int] = None
16901690
self.scale1_name:Optional[str] = None # the display/local name of the device (like "ACAIA162FC")
16911691
self.scale1_id:Optional[str] = None # the id, eg. the BT address (like "24:71:89:cc:09:05")
1692+
self.container1_idx:int = -1 # -1: no container set; otherwise index into selected qmc.container_names/qmc.container_weights
16921693
# scale2: just for green
16931694
self.scale2_model:Optional[int] = None
16941695
self.scale2_name:Optional[str] = None # the display/local name of the device (like "ACAIA162FC")
16951696
self.scale2_id:Optional[str] = None # the device id, eg. the BT address (like "24:71:89:cc:09:05")
1697+
self.container2_idx:int = -1 # -1: no container set; otherwise index into selected qmc.container_names/qmc.container_weights
16961698

16971699
# active tab
16981700
self.EventsDlg_activeTab:int = 0
@@ -18578,11 +18580,13 @@ def settingsLoad(self, filename:Optional[str] = None, theme:bool = False, machin
1857818580
self.scale1_model = settings.value('scale1_model',self.scale1_model)
1857918581
self.scale1_name = settings.value('scale1_name',self.scale1_name)
1858018582
self.scale1_id = settings.value('scale1_id',self.scale1_id)
18583+
self.container1_idx = settings.value('container1_idx',self.container1_idx)
1858118584
self.scale2_model = settings.value('scale2_model',self.scale2_model)
1858218585
self.scale2_name = settings.value('scale2_name',self.scale2_name)
1858318586
self.scale2_id = settings.value('scale2_id',self.scale2_id)
18587+
self.container2_idx = settings.value('container2_idx',self.container2_idx)
1858418588
settings.endGroup()
18585-
#--- END GROUP Tasks
18589+
#--- END GROUP Scales
1858618590

1858718591
self.schedule_day_filter =toBool(settings.value('ScheduleDayFilter',self.schedule_day_filter))
1858818592
self.schedule_user_filter = toBool(settings.value('ScheduleUserFilter',self.schedule_user_filter))
@@ -20210,11 +20214,13 @@ def saveAllSettings(self, settings:QSettings, default_settings:Optional[Dict[str
2021020214
self.settingsSetValue(settings, default_settings, 'scale1_model',self.scale1_model, read_defaults)
2021120215
self.settingsSetValue(settings, default_settings, 'scale1_name',self.scale1_name, read_defaults)
2021220216
self.settingsSetValue(settings, default_settings, 'scale1_id',self.scale1_id, read_defaults)
20217+
self.settingsSetValue(settings, default_settings, 'container1_idx',self.container1_idx, read_defaults)
2021320218
self.settingsSetValue(settings, default_settings, 'scale2_model',self.scale2_model, read_defaults)
2021420219
self.settingsSetValue(settings, default_settings, 'scale2_name',self.scale2_name, read_defaults)
2021520220
self.settingsSetValue(settings, default_settings, 'scale2_id',self.scale2_id, read_defaults)
20221+
self.settingsSetValue(settings, default_settings, 'container2_idx',self.container2_idx, read_defaults)
2021620222
settings.endGroup()
20217-
#--- END GROUP Tasks
20223+
#--- END GROUP Scales
2021820224

2021920225
self.settingsSetValue(settings, default_settings, 'ScheduleDayFilter',self.schedule_day_filter, read_defaults)
2022020226
self.settingsSetValue(settings, default_settings, 'ScheduleUserFilter',self.schedule_user_filter, read_defaults)

0 commit comments

Comments
 (0)