diff --git a/opencodeblocks/graphics/view.py b/opencodeblocks/graphics/view.py index ea8801f9..4623687b 100644 --- a/opencodeblocks/graphics/view.py +++ b/opencodeblocks/graphics/view.py @@ -146,19 +146,25 @@ def centerView(self, x: float, y: float): hsb.setValue(x * self.zoom - self.width() / 2) vsb.setValue(y * self.zoom - self.height() / 2) - def moveToGlobalView(self) -> bool: - """ - OCBView reaction to the space bar being pressed. - Returns True if the event was handled. + def moveToItems(self) -> bool: + """Ajust zoom and position to make selected items visible. + + If no item is selected, make the whole graph visible instead. + + Returns: + True if the event was handled, False otherwise. """ # The focusItem has priority for this event if self.scene().focusItem() is not None: return False - if len(self.scene().selectedItems()) > 0: - return False items = self.scene().items() + + # If items are selected, overwride the behvaior + if len(self.scene().selectedItems()) > 0: + items = self.scene().selectedItems() + code_blocks: List[OCBBlock] = [i for i in items if isinstance(i, OCBBlock)] if len(code_blocks) == 0: @@ -263,10 +269,6 @@ def keyPressEvent(self, event: QKeyEvent): if self.moveViewOnArrow(event): return - if key_id == Qt.Key.Key_Space: - if self.moveToGlobalView(): - return - super().keyPressEvent(event) def retreiveBlockTypes(self) -> List[Tuple[str]]: diff --git a/opencodeblocks/graphics/widget.py b/opencodeblocks/graphics/widget.py index 8d79f871..19cdc5ce 100644 --- a/opencodeblocks/graphics/widget.py +++ b/opencodeblocks/graphics/widget.py @@ -65,6 +65,9 @@ def load(self, filepath: str): self.scene.load(filepath) self.savepath = filepath - def moveToGlobalView(self): - """Center the view to see the hole graph""" - self.view.moveToGlobalView() + def moveToItems(self): + """ + Ajust zoom and position to make the whole graph visible. + If items are selected, then make all the selected items visible instead + """ + self.view.moveToItems() diff --git a/opencodeblocks/graphics/window.py b/opencodeblocks/graphics/window.py index 6823f3fa..e5fb673c 100644 --- a/opencodeblocks/graphics/window.py +++ b/opencodeblocks/graphics/window.py @@ -8,8 +8,16 @@ from PyQt5.QtCore import QPoint, QSettings, QSize, Qt, QSignalMapper from PyQt5.QtGui import QCloseEvent, QKeySequence -from PyQt5.QtWidgets import QDockWidget, QListWidget, QWidget, QAction, QFileDialog, QMainWindow,\ - QMessageBox, QMdiArea +from PyQt5.QtWidgets import ( + QDockWidget, + QListWidget, + QWidget, + QAction, + QFileDialog, + QMainWindow, + QMessageBox, + QMdiArea, +) from opencodeblocks.graphics.widget import OCBWidget from opencodeblocks.graphics.theme_manager import theme_manager @@ -19,23 +27,24 @@ class OCBWindow(QMainWindow): - """ Main window of the OpenCodeBlocks Qt-based application. """ + """Main window of the OpenCodeBlocks Qt-based application.""" def __init__(self): super().__init__() self.stylesheet_filename = os.path.join( - os.path.dirname(__file__),'..', 'qss', 'ocb.qss') - loadStylesheets(( - os.path.join(os.path.dirname(__file__),'..', 'qss', 'ocb_dark.qss'), - self.stylesheet_filename - )) + os.path.dirname(__file__), "..", "qss", "ocb.qss" + ) + loadStylesheets( + ( + os.path.join(os.path.dirname(__file__), "..", "qss", "ocb_dark.qss"), + self.stylesheet_filename, + ) + ) self.mdiArea = QMdiArea() - self.mdiArea.setHorizontalScrollBarPolicy( - Qt.ScrollBarPolicy.ScrollBarAsNeeded) - self.mdiArea.setVerticalScrollBarPolicy( - Qt.ScrollBarPolicy.ScrollBarAsNeeded) + self.mdiArea.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded) + self.mdiArea.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded) self.mdiArea.setViewMode(QMdiArea.ViewMode.TabbedView) self.mdiArea.setDocumentMode(True) self.mdiArea.setTabsMovable(True) @@ -91,63 +100,130 @@ def updateMenus(self): pass def createActions(self): - """ Create all menu actions. """ + """Create all menu actions.""" # File - self._actNew = QAction('&New', statusTip='Create new ipygraph', - shortcut='Ctrl+N', triggered=self.onFileNew) - self._actOpen = QAction('&Open', statusTip='Open an ipygraph', - shortcut='Ctrl+O', triggered=self.onFileOpen) - self._actSave = QAction('&Save', statusTip='Save the ipygraph', - shortcut='Ctrl+S', triggered=self.onFileSave) - self._actSaveAs = QAction('Save &As...', statusTip='Save the ipygraph as...', - shortcut='Ctrl+Shift+S', triggered=self.onFileSaveAs) - self._actQuit = QAction('&Quit', statusTip='Save and Quit the application', - shortcut='Ctrl+Q', triggered=self.close) + self._actNew = QAction( + "&New", + statusTip="Create new ipygraph", + shortcut="Ctrl+N", + triggered=self.onFileNew, + ) + self._actOpen = QAction( + "&Open", + statusTip="Open an ipygraph", + shortcut="Ctrl+O", + triggered=self.onFileOpen, + ) + self._actSave = QAction( + "&Save", + statusTip="Save the ipygraph", + shortcut="Ctrl+S", + triggered=self.onFileSave, + ) + self._actSaveAs = QAction( + "Save &As...", + statusTip="Save the ipygraph as...", + shortcut="Ctrl+Shift+S", + triggered=self.onFileSaveAs, + ) + self._actQuit = QAction( + "&Quit", + statusTip="Save and Quit the application", + shortcut="Ctrl+Q", + triggered=self.close, + ) # Edit - self._actUndo = QAction('&Undo', statusTip='Undo last operation', - shortcut='Ctrl+Z', triggered=self.onEditUndo) - self._actRedo = QAction('&Redo', statusTip='Redo last operation', - shortcut='Ctrl+Y', triggered=self.onEditRedo) - self._actCut = QAction('Cu&t', statusTip='Cut to clipboard', - shortcut='Ctrl+X', triggered=self.onEditCut) - self._actCopy = QAction('&Copy', statusTip='Copy to clipboard', - shortcut='Ctrl+C', triggered=self.onEditCopy) - self._actPaste = QAction('&Paste', statusTip='Paste from clipboard', - shortcut='Ctrl+V', triggered=self.onEditPaste) - self._actDel = QAction('&Del', statusTip='Delete selected items', - shortcut='Del', triggered=self.onEditDelete) + self._actUndo = QAction( + "&Undo", + statusTip="Undo last operation", + shortcut="Ctrl+Z", + triggered=self.onEditUndo, + ) + self._actRedo = QAction( + "&Redo", + statusTip="Redo last operation", + shortcut="Ctrl+Y", + triggered=self.onEditRedo, + ) + self._actCut = QAction( + "Cu&t", + statusTip="Cut to clipboard", + shortcut="Ctrl+X", + triggered=self.onEditCut, + ) + self._actCopy = QAction( + "&Copy", + statusTip="Copy to clipboard", + shortcut="Ctrl+C", + triggered=self.onEditCopy, + ) + self._actPaste = QAction( + "&Paste", + statusTip="Paste from clipboard", + shortcut="Ctrl+V", + triggered=self.onEditPaste, + ) + self._actDel = QAction( + "&Del", + statusTip="Delete selected items", + shortcut="Del", + triggered=self.onEditDelete, + ) # View - self._actGlobal = QAction('Global View', statusTip='View the hole graph', - shortcut=' ', triggered=self.onViewGlobal) + self._actViewItems = QAction( + "See All Blocks", + statusTip="See all selected blocks. If none are selected, view all blocks", + shortcut=" ", + triggered=self.onMoveToItems, + ) # Window - self._actClose = QAction("Cl&ose", self, - statusTip="Close the active window", - triggered=self.mdiArea.closeActiveSubWindow) - self._actCloseAll = QAction("Close &All", self, - statusTip="Close all the windows", - triggered=self.mdiArea.closeAllSubWindows) - self._actTile = QAction("&Tile", self, statusTip="Tile the windows", - triggered=self.mdiArea.tileSubWindows) - self._actCascade = QAction("&Cascade", self, - statusTip="Cascade the windows", - triggered=self.mdiArea.cascadeSubWindows) - self._actNext = QAction("Ne&xt", self, - shortcut=QKeySequence.StandardKey.NextChild, - statusTip="Move the focus to the next window", - triggered=self.mdiArea.activateNextSubWindow) - self._actPrevious = QAction("Pre&vious", self, - shortcut=QKeySequence.StandardKey.PreviousChild, - statusTip="Move the focus to the previous window", - triggered=self.mdiArea.activatePreviousSubWindow) + self._actClose = QAction( + "Cl&ose", + self, + statusTip="Close the active window", + triggered=self.mdiArea.closeActiveSubWindow, + ) + self._actCloseAll = QAction( + "Close &All", + self, + statusTip="Close all the windows", + triggered=self.mdiArea.closeAllSubWindows, + ) + self._actTile = QAction( + "&Tile", + self, + statusTip="Tile the windows", + triggered=self.mdiArea.tileSubWindows, + ) + self._actCascade = QAction( + "&Cascade", + self, + statusTip="Cascade the windows", + triggered=self.mdiArea.cascadeSubWindows, + ) + self._actNext = QAction( + "Ne&xt", + self, + shortcut=QKeySequence.StandardKey.NextChild, + statusTip="Move the focus to the next window", + triggered=self.mdiArea.activateNextSubWindow, + ) + self._actPrevious = QAction( + "Pre&vious", + self, + shortcut=QKeySequence.StandardKey.PreviousChild, + statusTip="Move the focus to the previous window", + triggered=self.mdiArea.activatePreviousSubWindow, + ) self._actSeparator = QAction(self) self._actSeparator.setSeparator(True) def createMenus(self): - """ Create the File menu with linked shortcuts. """ - self.filemenu = self.menuBar().addMenu('&File') + """Create the File menu with linked shortcuts.""" + self.filemenu = self.menuBar().addMenu("&File") self.filemenu.addAction(self._actNew) self.filemenu.addAction(self._actOpen) self.filemenu.addSeparator() @@ -156,7 +232,7 @@ def createMenus(self): self.filemenu.addSeparator() self.filemenu.addAction(self._actQuit) - self.editmenu = self.menuBar().addMenu('&Edit') + self.editmenu = self.menuBar().addMenu("&Edit") self.editmenu.addAction(self._actUndo) self.editmenu.addAction(self._actRedo) self.editmenu.addSeparator() @@ -166,10 +242,10 @@ def createMenus(self): self.editmenu.addSeparator() self.editmenu.addAction(self._actDel) - self.viewmenu = self.menuBar().addMenu('&View') - self.thememenu = self.viewmenu.addMenu('Theme') + self.viewmenu = self.menuBar().addMenu("&View") + self.thememenu = self.viewmenu.addMenu("Theme") self.thememenu.aboutToShow.connect(self.updateThemeMenu) - self.viewmenu.addAction(self._actGlobal) + self.viewmenu.addAction(self._actViewItems) self.windowMenu = self.menuBar().addMenu("&Window") self.updateWindowMenu() @@ -207,7 +283,7 @@ def updateWindowMenu(self): text = f"{i + 1} {child.windowTitle()}" if i < 9: - text = '&' + text + text = "&" + text action = self.windowMenu.addAction(text) action.setCheckable(True) @@ -216,7 +292,7 @@ def updateWindowMenu(self): self.windowMapper.setMapping(action, window) def createNewMdiChild(self, filename: str = None): - """ Create a new graph subwindow loading a file if a path is given. """ + """Create a new graph subwindow loading a file if a path is given.""" ocb_widget = OCBWidget() if filename is not None: ocb_widget.scene.load(filename) @@ -224,15 +300,14 @@ def createNewMdiChild(self, filename: str = None): return self.mdiArea.addSubWindow(ocb_widget) def onFileNew(self): - """ Create a new file. """ + """Create a new file.""" subwnd = self.createNewMdiChild() subwnd.show() def onFileOpen(self): - """ Open a file. """ - filename, _ = QFileDialog.getOpenFileName( - self, 'Open ipygraph from file') - if filename == '': + """Open a file.""" + filename, _ = QFileDialog.getOpenFileName(self, "Open ipygraph from file") + if filename == "": return if os.path.isfile(filename): subwnd = self.createNewMdiChild(filename) @@ -240,7 +315,7 @@ def onFileOpen(self): self.statusbar.showMessage(f"Successfully loaded {filename}", 2000) def onFileSave(self) -> bool: - """ Save file. + """Save file. Returns: True if the file was successfully saved, False otherwise. @@ -252,11 +327,12 @@ def onFileSave(self) -> bool: return self.onFileSaveAs() current_window.save() self.statusbar.showMessage( - f"Successfully saved ipygraph at {current_window.savepath}", 2000) + f"Successfully saved ipygraph at {current_window.savepath}", 2000 + ) return True def onFileSaveAs(self) -> bool: - """ Save file in a given directory, caching savepath for quick save. + """Save file in a given directory, caching savepath for quick save. Returns: True if the file was successfully saved, False otherwise. @@ -264,9 +340,8 @@ def onFileSaveAs(self) -> bool: """ current_window = self.activeMdiChild() if current_window is not None: - filename, _ = QFileDialog.getSaveFileName( - self, 'Save ipygraph to file') - if filename == '': + filename, _ = QFileDialog.getSaveFileName(self, "Save ipygraph to file") + if filename == "": return False current_window.savepath = filename self.onFileSave() @@ -275,41 +350,41 @@ def onFileSaveAs(self) -> bool: @staticmethod def is_not_editing(current_window: OCBWidget): - """ True if current_window exists and is not in editing mode. """ - return current_window is not None and not current_window.view.is_mode('EDITING') + """True if current_window exists and is not in editing mode.""" + return current_window is not None and not current_window.view.is_mode("EDITING") def onEditUndo(self): - """ Undo last operation if not in edit mode. """ + """Undo last operation if not in edit mode.""" current_window = self.activeMdiChild() if self.is_not_editing(current_window): current_window.scene.history.undo() def onEditRedo(self): - """ Redo last operation if not in edit mode. """ + """Redo last operation if not in edit mode.""" current_window = self.activeMdiChild() if self.is_not_editing(current_window): current_window.scene.history.redo() def onEditCut(self): - """ Cut the selected items if not in edit mode. """ + """Cut the selected items if not in edit mode.""" current_window = self.activeMdiChild() if self.is_not_editing(current_window): current_window.scene.clipboard.cut() def onEditCopy(self): - """ Copy the selected items if not in edit mode. """ + """Copy the selected items if not in edit mode.""" current_window = self.activeMdiChild() if self.is_not_editing(current_window): current_window.scene.clipboard.copy() def onEditPaste(self): - """ Paste the selected items if not in edit mode. """ + """Paste the selected items if not in edit mode.""" current_window = self.activeMdiChild() if self.is_not_editing(current_window): current_window.scene.clipboard.paste() def onEditDelete(self): - """ Delete the selected items if not in edit mode. """ + """Delete the selected items if not in edit mode.""" current_window = self.activeMdiChild() if self.is_not_editing(current_window): current_window.view.deleteSelected() @@ -322,7 +397,7 @@ def onEditDelete(self): # event.ignore() def closeEvent(self, event: QCloseEvent): - """ Save and quit the application. """ + """Save and quit the application.""" self.mdiArea.closeAllSubWindows() if self.mdiArea.currentSubWindow(): event.ignore() @@ -331,7 +406,7 @@ def closeEvent(self, event: QCloseEvent): event.accept() def maybeSave(self) -> bool: - """ Ask for save and returns if the file should be closed. + """Ask for save and returns if the file should be closed. Returns: True if the file should be closed, False otherwise. @@ -340,13 +415,14 @@ def maybeSave(self) -> bool: if not self.isModified(): return True - answer = QMessageBox.warning(self, "About to loose you work?", - "The file has been modified.\n" - "Do you want to save your changes?", - QMessageBox.StandardButton.Save | - QMessageBox.StandardButton.Discard | - QMessageBox.StandardButton.Cancel - ) + answer = QMessageBox.warning( + self, + "About to loose you work?", + "The file has been modified.\n" "Do you want to save your changes?", + QMessageBox.StandardButton.Save + | QMessageBox.StandardButton.Discard + | QMessageBox.StandardButton.Cancel, + ) if answer == QMessageBox.StandardButton.Save: return self.onFileSave() @@ -355,36 +431,39 @@ def maybeSave(self) -> bool: return False def activeMdiChild(self) -> OCBWidget: - """ Get the active OCBWidget if existing. """ + """Get the active OCBWidget if existing.""" activeSubWindow = self.mdiArea.activeSubWindow() if activeSubWindow is not None: return activeSubWindow.widget() return None def readSettings(self): - settings = QSettings('AutopIA', 'OpenCodeBlocks') - pos = settings.value('pos', QPoint(200, 200)) - size = settings.value('size', QSize(400, 400)) + settings = QSettings("AutopIA", "OpenCodeBlocks") + pos = settings.value("pos", QPoint(200, 200)) + size = settings.value("size", QSize(400, 400)) self.move(pos) self.resize(size) - if settings.value('isMaximized', False) == 'true': + if settings.value("isMaximized", False) == "true": self.showMaximized() def writeSettings(self): - settings = QSettings('AutopIA', 'OpenCodeBlocks') - settings.setValue('pos', self.pos()) - settings.setValue('size', self.size()) - settings.setValue('isMaximized', self.isMaximized()) + settings = QSettings("AutopIA", "OpenCodeBlocks") + settings.setValue("pos", self.pos()) + settings.setValue("size", self.size()) + settings.setValue("isMaximized", self.isMaximized()) def setActiveSubWindow(self, window): if window: self.mdiArea.setActiveSubWindow(window) - def onViewGlobal(self): - """ Center the view to see the hole graph """ + def onMoveToItems(self): + """ + Ajust zoom and position to make the whole graph in the current window visible. + If items are selected, then make all the selected items visible instead + """ current_window = self.activeMdiChild() if current_window is not None and isinstance(current_window, OCBWidget): - current_window.moveToGlobalView() + current_window.moveToItems() def setTheme(self, theme_index): theme_manager().selected_theme_index = theme_index