class View(QWidget): def __init__(self): super().__init__() self.layout = QHBoxLayout(self) self.selected = None self.pixmap_cache = {} # The split view at first shows the list of manga items and the default item view. # After a manga has been selected, there will be a total of three widgets: # - Manga item list # - Manga description # - Manga info (incl. cover) self.layout.addWidget(MANGA_ITEMS) self.layout.addWidget(self.default()) MANGA_ITEMS.itemClicked.connect(self.onSelectedManga) def default(self): label = QLabel("Select a manga from the side bar, or add a new one.") label.setAlignment(Qt.AlignCenter) return label def deleteLast(self): while self.layout.count() != 1: delete(self.layout.takeAt(1)) def onSelectedManga(self, manga_item): self.deleteLast() self.selected = manga_item manga = _load_manga(manga_item.meta.hash) summary = SummaryView(manga) self.layout.addWidget(summary) infobox = ItemInfoBox(manga_item) scroll = QScrollArea() scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) scroll.setFrameShape(scroll.NoFrame) scroll.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) scroll.setWidget(infobox) self.layout.addWidget(scroll) def onDeletedManga(self): self.deleteLast() self.layout.addWidget(self.default()) def reload(self): if self.selected is not None: self.onSelectedManga(self.selected) else: # no manga selected yet. self.onDeletedManga()
class BanBoard(QWidget): """The main board of xBan""" def __init__(self, filepath, file_config, parent=None): super().__init__(parent) self.filepath = filepath self.file_config = file_config self.draw_board(file_config) self.setAcceptDrops(True) gui_logger.info("Main xban board created") def draw_board(self, file_config): """Initiate UI The UI consists of 2 parts, top is the board title and info and button is the subbords with tiles in each ones the subboard is drawn based on file_configuration """ config, content = file_config mainlayout = QVBoxLayout() mainlayout.setContentsMargins(20, 20, 20, 20) title_edit = QLineEdit( config["xban_config"]["title"], objectName="windowEdit_title", parent=self, ) title_edit.setPlaceholderText("Enter title here ...") info_edit = NoteTile(config["xban_config"]["description"], "windowEdit_text", self) info_edit.setPlaceholderText("Enter description here ...") mainlayout.addWidget(title_edit) mainlayout.addWidget(info_edit) self.sublayout = QHBoxLayout() color = config["xban_config"]["board_color"] self.sublayout.setContentsMargins(10, 10, 10, 10) self.sublayout.setSpacing(20) add_btn = BanButton( "+", clicked=self.insert_board, toolTip="add board", objectName="windowBtn_add", ) shadow = QGraphicsDropShadowEffect(self, blurRadius=10, offset=5, color=QColor("lightgrey")) add_btn.setGraphicsEffect(shadow) self.sublayout.addWidget(add_btn) mainlayout.addLayout(self.sublayout) self.setLayout(mainlayout) for i, tile_contents in enumerate(content.items()): # insert the boards self.insert_board(tile_contents, color[i]) def insert_board(self, content=("", ()), color="black"): """Insert a board into the layout""" new_board = SubBoard(content, color, self) new_board.delBoardSig.connect(partial(self.delete_board, new_board)) new_board.listwidget.itemSelectionChanged.connect( partial(self.single_selection, new_board.listwidget)) # insert second to last self.sublayout.insertWidget(self.sublayout.count() - 1, new_board) def delete_board(self, board): """Delete the board""" self.sublayout.removeWidget(board) board.deleteLater() def parse_board(self): """Parse the board to the correct yaml files Note this function will rewrite the file """ color = [] content = {} title = self.layout().itemAt(0).widget().text() description = self.layout().itemAt(1).widget().toPlainText() sublayout = self.layout().itemAt(2) # exclude the plus button for i in range(sublayout.count() - 1): subboard = sublayout.itemAt(i).widget() color.append(subboard.color) sub_content = subboard.parse() content.update({sub_content[0]: sub_content[1]}) config = { "xban_config": { "title": title, "description": description, "board_color": color, } } return [config, content] def get_index(self, pos): """Get index of the subboard layout based on the mouse position""" sublayout = self.layout().itemAt(2) for i in range(sublayout.count()): if sublayout.itemAt(i).geometry().contains(pos): return i return -1 def dragEnterEvent(self, event): """Drag enter event Only accept the drag event when the moving widget is SubBoard instance The accepted drap event will proceed to dropEvent mouseMoveEvent needs to be defined for the child class """ if isinstance(event.source(), SubBoard): event.accept() def dropEvent(self, event): """Drop Event When the widget is dropped, determine the current layout index of the cursor and insert widget in the layout Note the last widget of the layout is the plus button, hence never insert at the end """ position = event.pos() widget = event.source() sublayout = self.layout().itemAt(2) index_new = self.get_index(position) if index_new >= 0: index = min(index_new, sublayout.count() - 1) sublayout.insertWidget(index, widget) event.setDropAction(Qt.MoveAction) event.accept() def save_board(self): """Save the board to yaml file""" xban_content = self.parse_board() save_yaml(self.filepath, xban_content) gui_logger.info(f"Saved to {self.filepath}") def single_selection(self, selected_board): """ensure that only single tile from a board is selected This is achieved by emit and received every time there is a selection made. The has selection check makes sure that it does not go into a recursion, since clear selection also triggers the signal """ if selected_board.selectionModel().hasSelection(): for i in range(self.sublayout.count() - 1): subboard = self.sublayout.itemAt(i).widget().listwidget if subboard is not selected_board: subboard.clearSelection()