diff --git a/src/main/python/main/ayab/ayab.py b/src/main/python/main/ayab/ayab.py
index 49ca5e4b..010c0ecc 100644
--- a/src/main/python/main/ayab/ayab.py
+++ b/src/main/python/main/ayab/ayab.py
@@ -176,9 +176,7 @@ def set_image_dimensions(self) -> None:
width, height = self.scene.ayabimage.image.size
self.engine.config.update_needles() # in case machine width changed
self.engine.config.set_image_dimensions(width, height)
- self.progbar.row = self.scene.row_progress + 1
- self.progbar.total = height
- self.progbar.refresh()
+ self.progbar.update(self.engine.status)
self.notify(
QCoreApplication.translate("Scene", "Image dimensions")
+ f": {width} x {height}",
@@ -191,8 +189,9 @@ def reverse_image(self) -> None:
self.scene.reverse()
def update_start_row(self, start_row: int) -> None:
- self.progbar.update(start_row)
self.scene.row_progress = start_row
+ self.engine.status.current_row = start_row
+ self.progbar.update(self.engine.status)
def notify(self, text: str, log: bool = True) -> None:
"""Update the notification field."""
diff --git a/src/main/python/main/ayab/engine/control.py b/src/main/python/main/ayab/engine/control.py
index 6c9fb694..0f290f1a 100644
--- a/src/main/python/main/ayab/engine/control.py
+++ b/src/main/python/main/ayab/engine/control.py
@@ -243,6 +243,7 @@ def cnf_line_API6(self, line_number: int) -> bool:
return True # pattern finished
def __update_status(self, line_number: int, color: int, bits: bitarray) -> None:
+ self.status.total_rows = self.pat_height
self.status.current_row = self.pat_row + 1
self.status.line_number = line_number
if self.inf_repeat:
diff --git a/src/main/python/main/ayab/engine/engine.py b/src/main/python/main/ayab/engine/engine.py
index 3ae3bea2..805d712b 100644
--- a/src/main/python/main/ayab/engine/engine.py
+++ b/src/main/python/main/ayab/engine/engine.py
@@ -142,9 +142,9 @@ def knit_config(self, image: Image.Image) -> None:
self.pattern.alignment = self.config.alignment
# update progress bar
- self.emit_progress_bar_updater(
- self.config.start_row + 1, self.pattern.pat_height, 0, ""
- )
+ data = Status()
+ data.copy(self.status)
+ self.emit_progress_bar_updater(data)
# switch to status tab
# if self.config.continuous_reporting:
@@ -214,9 +214,7 @@ def __handle_status(self) -> None:
self.control.midline,
self.config.auto_mirror,
)
- self.emit_progress_bar_updater(
- data.current_row, self.pattern.pat_height, data.repeats, data.color_symbol
- )
+ self.emit_progress_bar_updater(data)
def cancel(self) -> None:
self.__canceled = True
diff --git a/src/main/python/main/ayab/engine/status.py b/src/main/python/main/ayab/engine/status.py
index f8b11c4b..511e5812 100644
--- a/src/main/python/main/ayab/engine/status.py
+++ b/src/main/python/main/ayab/engine/status.py
@@ -165,6 +165,8 @@ def copy(self, status: Status) -> None:
self.carriage_type = status.carriage_type
self.carriage_position = status.carriage_position
self.carriage_direction = status.carriage_direction
+ self.total_rows = status.total_rows
+ self.mirror = status.mirror
def parse_device_state_API6(self, state: Any, msg: bytes) -> None:
if not (self.active):
diff --git a/src/main/python/main/ayab/knitprogress.py b/src/main/python/main/ayab/knitprogress.py
index b295b0e3..5fb02fa9 100644
--- a/src/main/python/main/ayab/knitprogress.py
+++ b/src/main/python/main/ayab/knitprogress.py
@@ -19,12 +19,15 @@
# https://github.com/AllYarnsAreBeautiful/ayab-desktop
from __future__ import annotations
-from PySide6.QtCore import QCoreApplication, QRect, QSize
-from PySide6.QtWidgets import QTableWidget, QTableWidgetItem, QLabel, QHeaderView
-from typing import TYPE_CHECKING, Optional, cast
+from PySide6.QtCore import QCoreApplication, QRect, Qt
+from PySide6.QtWidgets import QTableWidget, QTableWidgetItem, QHeaderView
+from PySide6.QtGui import QBrush, QColor
+from typing import TYPE_CHECKING, Optional, cast, List
+from math import floor
if TYPE_CHECKING:
from .ayab import GuiMain
+ from .preferences import Preferences
from .engine.status import Status
@@ -43,17 +46,12 @@ def __init__(self, parent: GuiMain):
super().__init__(parent.ui.graphics_splitter)
self.clear()
self.setRowCount(0)
+ self.__prefs: Preferences = parent.prefs
self.setGeometry(QRect(0, 0, 700, 220))
self.setContentsMargins(1, 1, 1, 1)
self.verticalHeader().setSectionResizeMode(
- QHeaderView.ResizeMode.ResizeToContents
+ QHeaderView.ResizeMode.Fixed
)
- self.verticalHeader().setVisible(False)
- self.setColumnCount(6)
- for r in range(6):
- blank = QTableWidgetItem()
- blank.setSizeHint(QSize(0, 0))
- self.setHorizontalHeaderItem(r, blank)
self.previousStatus: Optional[Status] = None
self.scene = parent.scene
@@ -61,7 +59,6 @@ def start(self) -> None:
self.clearContents()
self.clearSelection()
self.setRowCount(0)
- self.horizontalHeader().setSectionHidden(5, False)
self.setCurrentCell(-1, -1)
self.color = True
@@ -95,83 +92,129 @@ def update_progress(
if status.current_row < 0:
return
- # else
- tr_ = QCoreApplication.translate
- row, swipe = divmod(status.line_number, row_multiplier)
-
- columns = []
-
- # row
- columns.append(tr_("KnitProgress", "Row") + " " + str(status.current_row))
- # pass
- columns.append(tr_("KnitProgress", "Pass") + " " + str(swipe + 1))
-
- # color
+ columns: List[QTableWidgetItem] = []
if status.color_symbol == "":
self.color = False
else:
self.color = True
- coltext = tr_("KnitProgress", "Color") + " " + status.color_symbol
- columns.append(coltext)
- carriage = status.carriage_type
- direction = status.carriage_direction
- columns.append(carriage.symbol + " " + direction.symbol)
+ midline = self.load_columns_from_status(status, midline, columns)
- # graph line of stitches
- midline = len(status.bits) - midline
-
- table_text = (
- "
"
- )
- for c in range(0, midline):
- table_text += self.__stitch(
- status.color, cast(bool, status.bits[c]), status.alt_color
- )
- table_text += "
"
- left_side = QLabel(table_text)
-
- table_text = (
- ""
- )
- for c in range(midline, len(status.bits)):
- table_text += self.__stitch(
- status.color, cast(bool, status.bits[c]), status.alt_color
- )
- table_text += "
"
- right_side = QLabel(table_text)
+ # For the top row (row idx 0), we show the row header as "To Be Selected",
+ # When we show a new row, we recover the header info and recombine it with its row (now row idx 2)
+ self.make_row_with_spacer()
- self.insertRow(0)
- for i, col in enumerate(columns):
- self.setItem(0, i, self.__item(col))
+ self.instantiate_row_from_columns(midline, columns)
+ if self.columnCount() != len(columns):
+ self.setColumnCount(len(columns))
n_cols = len(columns)
- self.setCellWidget(0, n_cols, left_side)
- self.setCellWidget(0, n_cols + 1, right_side)
- if row_multiplier == 1:
- self.hideColumn(1)
if n_cols < 4:
self.hideColumn(5)
- self.resizeColumnsToContents()
self.previousStatus = status
+ self.previous_row_mulitplier = row_multiplier
# update bar in Scene
self.scene.row_progress = status.current_row
- def __item(self, text: str) -> QTableWidgetItem:
- item = QTableWidgetItem(text)
- return item
+ def load_columns_from_status(self, status: Status, midline: int, columns: List[QTableWidgetItem]) -> int:
+ midline = len(status.bits) - midline
+
+ for c in range(0, midline):
+ columns.append(self.__stitch(
+ status.color, cast(bool, status.bits[c]), status.alt_color, self.__alternate_bg_colors(midline-c, self.orange)
+ ))
+
+ # if we are only working on the right side, midline is negative.
+ green_start = midline
+ if green_start < 0:
+ green_start = 0
+ for c in range(green_start, len(status.bits)):
+ columns.append(self.__stitch(
+ status.color, cast(bool, status.bits[c]), status.alt_color, self.__alternate_bg_colors(c-green_start, self.green)
+ ))
+
+ return midline
+
+ def instantiate_row_from_columns(self, midline: int, columns: List[QTableWidgetItem]) -> None:
+ self.setVerticalHeaderItem(0, QTableWidgetItem("To Be Selected"))
+ for i, col in enumerate(columns):
+ self.setItem(0, i, col)
+ self.setColumnWidth(i, cast(int, self.__prefs.settings.value("lower_display_stitch_width")))
+ # when width is under 20, the column numbers are unreadable.
+ if self.columnWidth(i) < 20:
+ self.horizontalHeader().setVisible(False)
+ continue
+ self.horizontalHeader().setVisible(True)
+ if i < midline:
+ header = QTableWidgetItem(f"{(midline)-(i)}")
+ header.font().setBold(True)
+ header.setForeground(QBrush(QColor(f"#{self.orange:06x}")))
+ header.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
+ self.setHorizontalHeaderItem(i, header)
+ self.horizontalHeader().setMinimumSectionSize(0)
+ else:
+ header = QTableWidgetItem(f"{(i+1)-(midline)}")
+ header.setForeground(QBrush(QColor(f"#{self.green:06x}")))
+ header.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
+ self.setHorizontalHeaderItem(i, header)
+
+ def make_row_with_spacer(self) -> None:
+ self.removeRow(1)
+ self.insertRow(0)
+ self.insertRow(1)
+ if self.rowCount() > 2:
+ self.setVerticalHeaderItem(2, self.format_row_header_text(self.previousStatus, self.previous_row_mulitplier))
+ self.verticalHeader().setSectionResizeMode(1, QHeaderView.ResizeMode.Fixed)
+ self.verticalHeader().setMinimumSectionSize(0)
+ self.verticalHeader().resizeSection(1, 5)
+
+ def format_row_header_text(self, status: Optional[Status], row_multiplier: int) -> QTableWidgetItem:
+ if status is None:
+ return QTableWidgetItem("")
+ tr_ = QCoreApplication.translate
+ info_header = QTableWidgetItem()
+ info_text = ""
+ row, swipe = divmod(status.line_number, row_multiplier)
+ # row "Row [1]"
+ info_text = (tr_("KnitProgress", "Row") + " " + str(status.current_row))
- def __stitch(self, color: int, bit: bool, alt_color: Optional[int] = None) -> str:
- # FIXME: borders are not visible
- text = " | "
+ if bg_color is not None:
+ stitch.setBackground(QBrush(bg_color))
+ return stitch
diff --git a/src/main/python/main/ayab/preferences.py b/src/main/python/main/ayab/preferences.py
index 1c6c3a12..d0e032de 100644
--- a/src/main/python/main/ayab/preferences.py
+++ b/src/main/python/main/ayab/preferences.py
@@ -27,7 +27,7 @@
from __future__ import annotations
from PySide6.QtCore import Qt, QSettings, QCoreApplication
-from PySide6.QtWidgets import QDialog, QFormLayout, QLabel, QCheckBox, QComboBox
+from PySide6.QtWidgets import QDialog, QFormLayout, QLabel, QCheckBox, QComboBox, QSpinBox
from .prefs_gui import Ui_Prefs
from .signal_sender import SignalSender
@@ -67,11 +67,12 @@ def str2bool(qvariant: str | bool) -> bool:
"quiet_mode",
"disable_hardware_beep",
]
+PreferencesDictIntKeys: TypeAlias = Literal["lower_display_stitch_width"]
PreferencesDictObjKeys: TypeAlias = Literal[
"aspect_ratio", "default_alignment", "default_knitting_mode", "machine"
]
PreferencesDictKeys: TypeAlias = Literal[
- PreferencesDictBoolKeys, PreferencesDictObjKeys, "language"
+ PreferencesDictBoolKeys, PreferencesDictObjKeys, PreferencesDictIntKeys, "language"
]
PreferencesDict = TypedDict(
@@ -87,6 +88,7 @@ def str2bool(qvariant: str | bool) -> bool:
"quiet_mode": type[bool],
"disable_hardware_beep": type[bool],
"language": type[Language],
+ "lower_display_stitch_width": type[int]
},
)
@@ -116,13 +118,14 @@ class Preferences(SignalSender):
"quiet_mode": bool,
"disable_hardware_beep": bool,
"language": Language,
+ "lower_display_stitch_width": int
}
def __init__(self, parent: GuiMain):
super().__init__(parent.signal_receiver)
self.parent = parent
self.languages = Language(self.parent.app_context)
- self.settings = QSettings()
+ self.settings: QSettings = QSettings()
self.settings.setFallbacksEnabled(False)
self.refresh()
@@ -148,23 +151,23 @@ def value(self, var: PreferencesDictKeys) -> Any:
else:
return self.default_value(var)
- def convert(self, var: PreferencesDictKeys) -> Callable[[T], Any]:
+ def convert(self, var: PreferencesDictKeys) -> Callable[[object], Any]:
try:
cls = self.variables[var]
except KeyError:
return str
# else
if cls == bool:
- return cast(Callable[[T], Any], str2bool)
+ return cast(Callable[[object], Any], str2bool)
# else
if cls == Language:
return str
# else
- return cast(Callable[[T], Any], int)
+ return cast(Callable[[object], Any], int)
def default_value(
self, var: PreferencesDictKeys
- ) -> Optional[bool | str | Literal[0]]:
+ ) -> Optional[bool | str | int | Literal[0]]:
try:
cls = self.variables[var]
except KeyError:
@@ -175,6 +178,8 @@ def default_value(
# else
if cls == Language:
return self.languages.default_language()
+ if cls == int:
+ return 20
# else
return 0
@@ -226,6 +231,8 @@ def __make_widget(self, var: PreferencesDictKeys) -> PrefsWidgetTypes:
cls = self.__prefs.variables[var]
if cls == bool:
return PrefsBoolWidget(self.__prefs, cast(PreferencesDictBoolKeys, var))
+ elif cls == int:
+ return PrefsIntWidget(self.__prefs, cast(PreferencesDictIntKeys, var))
elif cls == Language:
return PrefsLangWidget(self.__prefs)
else:
@@ -250,7 +257,7 @@ class PrefsBoolWidget(QCheckBox):
def __init__(self, prefs: Preferences, var: PreferencesDictBoolKeys):
super().__init__()
- self.var = var
+ self.var: PreferencesDictBoolKeys = var
self.prefs = prefs
def connectChange(self) -> None:
@@ -269,6 +276,29 @@ def refresh(self) -> None:
self.setCheckState(Qt.CheckState.Unchecked)
+class PrefsIntWidget(QSpinBox):
+ """Spinbox for Integer preferences setting.
+
+ @author Sam Bonfante
+ @date July 2024
+ """
+
+ def __init__(self, prefs: Preferences, var: PreferencesDictIntKeys):
+ super().__init__()
+ self.var: PreferencesDictIntKeys = var
+ self.prefs = prefs
+ self.setMinimum(2)
+
+ def connectChange(self) -> None:
+ self.valueChanged.connect(self.update_setting)
+
+ def update_setting(self, new_value: int) -> None:
+ self.prefs.settings.setValue(self.var, new_value)
+
+ def refresh(self) -> None:
+ self.setValue(self.prefs.value(self.var))
+
+
class PrefsComboWidget(QComboBox):
"""ComboBox for categorical preferences setting.
@@ -278,7 +308,7 @@ class PrefsComboWidget(QComboBox):
def __init__(self, prefs: Preferences, var: PreferencesDictObjKeys):
super().__init__()
- self.var = var
+ self.var: PreferencesDictObjKeys = var
self.prefs = prefs
cls = self.prefs.variables[self.var]
cls.add_items(self)
@@ -315,4 +345,4 @@ def refresh(self) -> None:
self.setCurrentIndex(self.findData(self.prefs.value("language")))
-PrefsWidgetTypes: TypeAlias = PrefsBoolWidget | PrefsLangWidget | PrefsComboWidget
+PrefsWidgetTypes: TypeAlias = PrefsBoolWidget | PrefsLangWidget | PrefsComboWidget | PrefsIntWidget
diff --git a/src/main/python/main/ayab/progressbar.py b/src/main/python/main/ayab/progressbar.py
index e45543c6..4d723bb5 100644
--- a/src/main/python/main/ayab/progressbar.py
+++ b/src/main/python/main/ayab/progressbar.py
@@ -20,10 +20,11 @@
from __future__ import annotations
from typing import TYPE_CHECKING
+from PySide6.QtGui import QColor
if TYPE_CHECKING:
from .ayab import GuiMain
- from .engine.status import ColorSymbolType
+ from .engine.status import Status
class ProgressBar(object):
@@ -40,23 +41,22 @@ def reset(self) -> None:
self.total = -1
self.repeats = -1
self.color = ""
+ self.background_color = 0xFFFFFF
self.__row_label.setText("")
self.__color_label.setText("")
self.__status_label.setText("")
def update(
self,
- row: int,
- total: int = 0,
- repeats: int = 0,
- color_symbol: ColorSymbolType = "",
+ status: Status
) -> bool:
- if row < 0:
+ if status.current_row < 0:
return False
- self.row = row
- self.total = total
- self.repeats = repeats
- self.color = color_symbol
+ self.row = status.current_row
+ self.total = status.total_rows
+ self.repeats = status.repeats
+ self.color = status.color_symbol
+ self.background_color = status.color
self.refresh()
return True
@@ -69,6 +69,12 @@ def refresh(self) -> None:
color_text = ""
else:
color_text = "Color " + self.color
+ bg_color = QColor.fromRgb(self.background_color)
+ if bg_color.lightness() < 128:
+ fg_color = 0xffffff
+ else: fg_color = 0x000000
+ self.__color_label.setStyleSheet("QLabel {background-color: "+f"#{self.background_color:06x}"+f";color:#{fg_color:06x}"+";}")
+
self.__color_label.setText(color_text)
# Update labels
diff --git a/src/main/python/main/ayab/signal_receiver.py b/src/main/python/main/ayab/signal_receiver.py
index 187392ef..226645cb 100644
--- a/src/main/python/main/ayab/signal_receiver.py
+++ b/src/main/python/main/ayab/signal_receiver.py
@@ -43,7 +43,7 @@ class SignalReceiver(QObject):
# signals are defined as class attributes which are
# over-ridden by instance attributes with the same name
start_row_updater = Signal(int)
- progress_bar_updater = Signal(int, int, int, str)
+ progress_bar_updater = Signal(Status)
knit_progress_updater = Signal(Status, int, int, bool)
notifier = Signal(str, bool)
# statusbar_updater = Signal('QString', bool)
diff --git a/src/main/python/main/ayab/signal_sender.py b/src/main/python/main/ayab/signal_sender.py
index 973eab38..064caf1e 100644
--- a/src/main/python/main/ayab/signal_sender.py
+++ b/src/main/python/main/ayab/signal_sender.py
@@ -50,10 +50,10 @@ def emit_start_row_updater(self, start_row: int) -> None:
self.__signal_receiver.start_row_updater.emit(start_row)
def emit_progress_bar_updater(
- self, row: int, total: int, repeats: int, color_symbol: ColorSymbolType
+ self, status: Status
) -> None:
self.__signal_receiver.progress_bar_updater.emit(
- row, total, repeats, color_symbol
+ status
)
def emit_knit_progress_updater(