diff --git a/BlocksScreen/helper_methods.py b/BlocksScreen/helper_methods.py index 0dd2b2d..8de5fc1 100644 --- a/BlocksScreen/helper_methods.py +++ b/BlocksScreen/helper_methods.py @@ -324,3 +324,32 @@ def check_file_on_path( """Check if file exists on path. Returns true if file exists on that specified directory""" _filepath = os.path.join(path, filename) return os.path.exists(_filepath) + + +def get_file_loc(filename) -> pathlib.Path: ... + + +def get_file_name(filename: typing.Optional[str]) -> str: + # If filename is None or empty, return empty string instead of None + if not filename: + return "" + # Remove trailing slashes or backslashes + filename = filename.rstrip("/\\") + + # Normalize Windows backslashes to forward slashes + filename = filename.replace("\\", "/") + + parts = filename.split("/") + + # Split and return the last path component + return parts[-1] if filename else "" + + +# def get_hash(data) -> hashlib._Hash: +# hash = hashlib.sha256() +# hash.update(data.encode()) +# hash.digest() +# return hash + + +def digest_hash() -> None: ... diff --git a/BlocksScreen/lib/panels/widgets/filesPage.py b/BlocksScreen/lib/panels/widgets/filesPage.py index fbfb337..f8fa490 100644 --- a/BlocksScreen/lib/panels/widgets/filesPage.py +++ b/BlocksScreen/lib/panels/widgets/filesPage.py @@ -5,9 +5,10 @@ import helper_methods from lib.utils.blocks_Scrollbar import CustomScrollBar from lib.utils.icon_button import IconButton -from lib.utils.list_button import ListCustomButton from PyQt6 import QtCore, QtGui, QtWidgets +from lib.utils.list_model import EntryDelegate, EntryListModel, ListItem + logger = logging.getLogger("logs/BlocksScreen.log") @@ -35,7 +36,9 @@ class FilesPage(QtWidgets.QWidget): directories: list = [] def __init__(self, parent) -> None: - super().__init__(parent) + super().__init__() + self.model = EntryListModel() + self.entry_delegate = EntryDelegate() self._setupUI() self.setMouseTracking(True) self.setAttribute(QtCore.Qt.WidgetAttribute.WA_AcceptTouchEvents, True) @@ -50,6 +53,33 @@ def __init__(self, parent) -> None: ) self.back_btn.clicked.connect(self.reset_dir) + self.entry_delegate.item_selected.connect(self._on_item_selected) + self._refresh_one_and_half_sec_timer = QtCore.QTimer() + self._refresh_one_and_half_sec_timer.timeout.connect( + lambda: self.request_dir_info[str].emit(self.curr_dir) + ) + self._refresh_one_and_half_sec_timer.start(1500) + + @QtCore.pyqtSlot(ListItem, name="on-item-selected") + def _on_item_selected(self, item: ListItem) -> None: + """Slot called when a list item is selected in the UI. + This method is connected to the `item_selected` signal of the entry delegate. + It handles the selection of a `ListItem` and process it accoding it with its type + Args: + item : ListItem The item that was selected by the user. + """ + if not item.left_icon: + filename = self.curr_dir + "/" + item.text + ".gcode" + self._fileItemClicked(filename) + else: + if item.text == "Go Back": + go_back_path = os.path.dirname(self.curr_dir) + if go_back_path == "/": + go_back_path = "" + self._on_goback_dir(go_back_path) + else: + self._dirItemClicked("/" + item.text) + @QtCore.pyqtSlot(name="reset-dir") def reset_dir(self) -> None: """Reset current directory""" @@ -76,7 +106,7 @@ def on_directories(self, directories_data: list) -> None: @QtCore.pyqtSlot(dict, name="on-fileinfo") def on_fileinfo(self, filedata: dict) -> None: - """Handle receive file information/metadata""" + """Method called per file to contruct file entry to the list""" if not filedata or not self.isVisible(): return filename = filedata.get("filename", "") @@ -104,53 +134,53 @@ def on_fileinfo(self, filedata: dict) -> None: else: time_str = f"{minutes}m" - list_items = [self.listWidget.item(i) for i in range(self.listWidget.count())] - if not list_items: - return - for list_item in list_items: - item_widget = self.listWidget.itemWidget(list_item) - if item_widget.text() in filename: - item_widget.setRightText(f"{filament_type} - {time_str}") + name = helper_methods.get_file_name(filename) + item = ListItem( + text=name[:-6], + right_text=f"{filament_type} - {time_str}", + right_icon=self.path.get("right_arrow"), + left_icon=None, + callback=None, + selected=False, + allow_check=False, + _lfontsize=17, + _rfontsize=12, + height=80, + notificate=False, + ) + + self.model.add_item(item) - @QtCore.pyqtSlot(QtWidgets.QListWidgetItem, name="file-item-clicked") - def _fileItemClicked(self, item: QtWidgets.QListWidgetItem) -> None: + @QtCore.pyqtSlot(str, name="file-item-clicked") + def _fileItemClicked(self, filename: str) -> None: """Slot for List Item clicked Args: - item (QListWidgetItem): Clicked item + filename (str): Clicked item path """ - if item: - widget = self.listWidget.itemWidget(item) - for file in self.file_list: - path = ( - file.get("path") if "path" in file.keys() else file.get("filename") - ) - if not path: - return - if widget.text() in path: - file_path = ( - path if not self.curr_dir else str(self.curr_dir + "/" + path) - ) - self.file_selected.emit( - str(file_path.removeprefix("/")), - self.files_data.get( - file_path.removeprefix("/") - ), # Defaults to Nothing - ) - - @QtCore.pyqtSlot(QtWidgets.QListWidgetItem, str, name="dir-item-clicked") - def _dirItemClicked(self, item: QtWidgets.QListWidgetItem, directory: str) -> None: + self.file_selected.emit( + str(filename.removeprefix("/")), + self.files_data.get(filename.removeprefix("/")), + ) + + def _dirItemClicked(self, directory: str) -> None: + """Method that changes the current view in the list""" self.curr_dir = self.curr_dir + directory self.request_dir_info[str].emit(self.curr_dir) def _build_file_list(self) -> None: """Inserts the currently available gcode files on the QListWidget""" self.listWidget.blockSignals(True) - self.listWidget.clear() - if not self.file_list and not self.directories: + self.model.clear() + self.entry_delegate.clear() + if ( + not self.file_list + and not self.directories + and os.path.islink(self.curr_dir) + ): self._add_placeholder() return - self.listWidget.setSpacing(35) + if self.directories or self.curr_dir != "": if self.curr_dir != "" and self.curr_dir != "/": self._add_back_folder_entry() @@ -161,51 +191,58 @@ def _build_file_list(self) -> None: sorted_list = sorted(self.file_list, key=lambda x: x["modified"], reverse=True) for item in sorted_list: self._add_file_list_item(item) - self._add_spacer() + self._setup_scrollbar() self.listWidget.blockSignals(False) - self.repaint() + self.listWidget.update() def _add_directory_list_item(self, dir_data: dict) -> None: + """Method that adds directories to the list""" dir_name = dir_data.get("dirname", "") if not dir_name: return - button = ListCustomButton() - button.setText(str(dir_data.get("dirname"))) - button.setSecondPixmap(QtGui.QPixmap(":/ui/media/btn_icons/folderIcon.svg")) - button.setMinimumSize(600, 80) - button.setMaximumSize(700, 80) - button.setLeftFontSize(17) - button.setRightFontSize(12) - list_item = QtWidgets.QListWidgetItem() - list_item.setSizeHint(button.sizeHint()) - self.listWidget.addItem(list_item) - self.listWidget.setItemWidget(list_item, button) - button.clicked.connect(lambda: self._dirItemClicked(list_item, "/" + dir_name)) + item = ListItem( + text=str(dir_name), + left_icon=self.path.get("folderIcon"), + right_text="", + selected=False, + callback=None, + allow_check=False, + _lfontsize=17, + _rfontsize=12, + height=80, + ) + self.model.add_item(item) def _add_back_folder_entry(self) -> None: - button = ListCustomButton() - button.setText("Go Back") - button.setSecondPixmap(QtGui.QPixmap(":/ui/media/btn_icons/back_folder.svg")) - button.setMinimumSize(600, 80) - button.setMaximumSize(700, 80) - button.setLeftFontSize(17) - button.setRightFontSize(12) - list_item = QtWidgets.QListWidgetItem() - list_item.setSizeHint(button.sizeHint()) - self.listWidget.addItem(list_item) - self.listWidget.setItemWidget(list_item, button) + """Method to insert in the list the "Go back" item""" go_back_path = os.path.dirname(self.curr_dir) if go_back_path == "/": go_back_path = "" - button.clicked.connect(lambda: (self._on_goback_dir(go_back_path))) + + item = ListItem( + text="Go Back", + right_text="", + right_icon=None, + left_icon=self.path.get("back_folder"), + callback=None, + selected=False, + allow_check=False, + _lfontsize=17, + _rfontsize=12, + height=80, + notificate=False, + ) + self.model.add_item(item) @QtCore.pyqtSlot(str, str, name="on-goback-dir") def _on_goback_dir(self, directory) -> None: + """Go back behaviour""" self.request_dir_info[str].emit(directory) self.curr_dir = directory def _add_file_list_item(self, file_data_item) -> None: + """Request file information and metadata to create filelist""" if not file_data_item: return @@ -215,61 +252,28 @@ def _add_file_list_item(self, file_data_item) -> None: else file_data_item["filename"] ) if not name.endswith(".gcode"): - # Only list .gcode files, all else ignore return - button = ListCustomButton() - button.setText(name[:-6]) - button.setPixmap(QtGui.QPixmap(":/arrow_icons/media/btn_icons/right_arrow.svg")) - button.setMinimumSize(600, 80) - button.setMaximumSize(700, 80) - button.setLeftFontSize(17) - button.setRightFontSize(12) - list_item = QtWidgets.QListWidgetItem() - list_item.setSizeHint(button.sizeHint()) - self.listWidget.addItem(list_item) - self.listWidget.setItemWidget(list_item, button) - button.clicked.connect(lambda: self._fileItemClicked(list_item)) file_path = ( name if not self.curr_dir else str(self.curr_dir + "/" + name) ).removeprefix("/") + self.request_file_metadata.emit(file_path.removeprefix("/")) self.request_file_info.emit(file_path.removeprefix("/")) - def _add_spacer(self) -> None: - spacer_item = QtWidgets.QListWidgetItem() - spacer_widget = QtWidgets.QWidget() - spacer_widget.setFixedHeight(10) - spacer_item.setSizeHint(spacer_widget.sizeHint()) - self.listWidget.addItem(spacer_item) - def _add_placeholder(self) -> None: - self.listWidget.setSpacing(-1) - placeholder_label = QtWidgets.QLabel("No Files found") - font = QtGui.QFont() - font.setPointSize(25) - placeholder_label.setFont(font) - placeholder_label.setStyleSheet("color: gray;") - placeholder_label.setAlignment( - QtCore.Qt.AlignmentFlag.AlignHCenter | QtCore.Qt.AlignmentFlag.AlignVCenter - ) - placeholder_label.setMinimumSize( - QtCore.QSize(self.listWidget.width(), self.listWidget.height()) - ) + """Shows placeholder when no items exist""" self.scrollbar.hide() - placeholder_item = QtWidgets.QListWidgetItem() - placeholder_item.setSizeHint( - QtCore.QSize(self.listWidget.width(), self.listWidget.height()) - ) - self.listWidget.addItem(placeholder_item) - self.listWidget.setItemWidget(placeholder_item, placeholder_label) + self.listWidget.hide() + self.label.show() def _handle_scrollbar(self, value): - # Block signals to avoid recursion + """Updates scrollbar value""" self.scrollbar.blockSignals(True) self.scrollbar.setValue(value) self.scrollbar.blockSignals(False) def _setup_scrollbar(self) -> None: + """Syncs the scrollbar with the list size""" self.scrollbar.setMinimum(self.listWidget.verticalScrollBar().minimum()) self.scrollbar.setMaximum(self.listWidget.verticalScrollBar().maximum()) self.scrollbar.setPageStep(self.listWidget.verticalScrollBar().pageStep()) @@ -327,7 +331,10 @@ def _setupUI(self): self.fp_content_layout = QtWidgets.QHBoxLayout() self.fp_content_layout.setContentsMargins(0, 0, 0, 0) self.fp_content_layout.setObjectName("fp_content_layout") - self.listWidget = QtWidgets.QListWidget(parent=self) + self.listWidget = QtWidgets.QListView(parent=self) + self.listWidget.setModel(self.model) + self.listWidget.setItemDelegate(self.entry_delegate) + self.listWidget.setSpacing(5) self.listWidget.setProperty("showDropIndicator", False) self.listWidget.setProperty("selectionMode", "NoSelection") self.listWidget.setStyleSheet("background: transparent;") @@ -335,11 +342,10 @@ def _setupUI(self): self.listWidget.setUniformItemSizes(True) self.listWidget.setObjectName("listWidget") self.listWidget.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) - self.listWidget.setDefaultDropAction(QtCore.Qt.DropAction.IgnoreAction) self.listWidget.setSelectionBehavior( QtWidgets.QAbstractItemView.SelectionBehavior.SelectItems ) - self.listWidget.setHorizontalScrollBarPolicy( # No horizontal scroll + self.listWidget.setHorizontalScrollBarPolicy( QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff ) self.listWidget.setVerticalScrollMode( @@ -364,26 +370,42 @@ def _setupUI(self): scroller_props = scroller_instance.scrollerProperties() scroller_props.setScrollMetric( QtWidgets.QScrollerProperties.ScrollMetric.DragVelocitySmoothingFactor, - 0.05, # Lower = more responsive + 0.05, ) scroller_props.setScrollMetric( QtWidgets.QScrollerProperties.ScrollMetric.DecelerationFactor, - 0.4, # higher = less inertia + 0.4, ) QtWidgets.QScroller.scroller(self.listWidget).setScrollerProperties( scroller_props ) + font = QtGui.QFont() font.setPointSize(25) - placeholder_item = QtWidgets.QListWidgetItem() - placeholder_item.setSizeHint( + self.label = QtWidgets.QLabel("No Files found") + self.label.setFont(font) + self.label.setStyleSheet("color: gray;") + self.label.setMinimumSize( QtCore.QSize(self.listWidget.width(), self.listWidget.height()) ) - self.fp_content_layout.addWidget(self.listWidget) + self.scrollbar = CustomScrollBar() + + self.fp_content_layout.addWidget( + self.label, + alignment=QtCore.Qt.AlignmentFlag.AlignHCenter + | QtCore.Qt.AlignmentFlag.AlignVCenter, + ) + self.fp_content_layout.addWidget(self.listWidget) self.fp_content_layout.addWidget(self.scrollbar) self.verticalLayout_5.addLayout(self.fp_content_layout) - self.scrollbar.setAttribute( - QtCore.Qt.WidgetAttribute.WA_TransparentForMouseEvents, True - ) - self.scroller = QtWidgets.QScroller.scroller(self.listWidget) + self.scrollbar.show() + self.label.hide() + + self.path = { + "back_folder": QtGui.QPixmap(":/ui/media/btn_icons/back_folder.svg"), + "folderIcon": QtGui.QPixmap(":/ui/media/btn_icons/folderIcon.svg"), + "right_arrow": QtGui.QPixmap( + ":/arrow_icons/media/btn_icons/right_arrow.svg" + ), + }