Implemented history back and forward navigation

This commit is contained in:
Arne Sostack 2023-05-13 23:34:22 +02:00
parent 8e298c0788
commit b05377b417
7 changed files with 339 additions and 5 deletions

View file

@ -0,0 +1,56 @@
from manuskript.functions.history.NavigatedEvent import NavigatedEvent
from manuskript.functions.history.Signal import Signal
class History():
def __init__(self) -> None:
self._entries = []
self._position = 0
self.navigated = Signal()
self._navigating = False
def next(self, entry):
if self._navigating:
return
while self._position < len(self._entries) - 1:
self._entries.pop()
self._entries.append(entry)
self._position = len(self._entries) - 1
self._navigating = True
self.navigated.fire(NavigatedEvent(self._position, len(self._entries), entry))
self._navigating = False
def replace(self, entry):
if self._navigating:
return
while self._position < len(self._entries):
self._entries.pop()
self._entries.append(entry)
self._position = len(self._entries) - 1
self._navigating = True
self.navigated.fire(NavigatedEvent(self._position, len(self._entries), entry))
self._navigating = False
def forward(self):
if self._position < len(self._entries) - 1:
self._position += 1
self._navigating = True
self.navigated.fire(NavigatedEvent(self._position, len(self._entries), self._entries[self._position]))
self._navigating = False
def back(self):
if self._position > 0:
self._position -= 1
self._navigating = True
self.navigated.fire(NavigatedEvent(self._position, len(self._entries), self._entries[self._position]))
self._navigating = False
def reset(self):
self._entries.clear()
self._position = 0
self.navigated.fire(NavigatedEvent(self._position, len(self._entries), None))

View file

@ -0,0 +1,5 @@
class NavigatedEvent():
def __init__(self, position, count, entry) -> None:
self.position = position
self.count = count
self.entry = entry

View file

@ -0,0 +1,23 @@
class Signal():
def __init__(self) -> None:
self._methods = []
def connect(self, func):
self._methods.append(func)
def disconnect(self, func):
try:
self._methods.remove(func)
except ValueError:
raise TypeError
def disconnect(self):
if len(self._methods) == 0:
raise TypeError
self._methods.pop()
def fire(self, data):
for m in self._methods:
m(data)

View file

@ -16,6 +16,7 @@ from manuskript.enums import Character, PlotStep, Plot, World, Outline
from manuskript.functions import wordCount, appPath, findWidgetsOfClass, openURL, showInFolder
import manuskript.functions as F
from manuskript import loadSave
from manuskript.functions.history.History import History
from manuskript.logging import getLogFilePath
from manuskript.models.characterModel import characterModel
from manuskript.models import outlineModel
@ -73,6 +74,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# value. In manuskript.main.
self._autoLoadProject = None # Used to load a command line project
self.sessionStartWordCount = 0 # Used to track session targets
self.history = History()
self._previousSelectionEmpty = True
self.readSettings()
@ -104,7 +107,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# Main Menu
for i in [self.actSave, self.actSaveAs, self.actCloseProject,
self.menuEdit, self.menuView, self.menuOrganize,
self.menuTools, self.menuHelp, self.actImport,
self.menuNavigate, self.menuTools, self.menuHelp, self.actImport,
self.actCompile, self.actSettings]:
i.setEnabled(False)
@ -158,6 +161,10 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.actSplitCursor.triggered.connect(self.documentsSplitCursor)
self.actMerge.triggered.connect(self.documentsMerge)
# Main menu:: Navigate
self.actBack.triggered.connect(self.navigateBack)
self.actForward.triggered.connect(self.navigateForward)
# Main Menu:: view
self.generateViewMenu()
self.actModeGroup = QActionGroup(self)
@ -256,6 +263,58 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.actDelete,
self.actRename]:
i.setEnabled(tabIsEditor)
match self.tabMain.currentIndex():
case self.TabPersos:
selectedCharacters = self.lstCharacters.currentCharacters()
characterSelectionIsEmpty = not any(selectedCharacters)
if characterSelectionIsEmpty:
self.pushHistory(("character", None))
self._previousSelectionEmpty = True
else:
character = selectedCharacters[0]
self.pushHistory(("character", character.ID()))
self._previousSelectionEmpty = False
case self.TabPlots:
id = self.lstPlots.currentPlotID()
self.pushHistory(("plot", id))
self._previousSelectionEmpty = id is None
case self.TabWorld:
index = self.mdlWorld.selectedIndex()
if index.isValid():
id = self.mdlWorld.ID(index)
self.pushHistory(("world", id))
self._previousSelectionEmpty = id is not None
else:
self.pushHistory(("world", None))
self._previousSelectionEmpty = True
case self.TabOutline:
index = self.treeOutlineOutline.selectionModel().currentIndex()
if index.isValid():
id = self.mdlOutline.ID(index)
self.pushHistory(("outline", id))
self._previousSelectionEmpty = id is not None
else:
self.pushHistory(("outline", None))
self._previousSelectionEmpty = False
case self.TabRedac:
index = self.treeRedacOutline.selectionModel().currentIndex()
if index.isValid():
id = self.mdlOutline.ID(index)
self.pushHistory(("redac", id))
self._previousSelectionEmpty = id is not None
else:
self.pushHistory(("redac", None))
self._previousSelectionEmpty = False
case _:
self.pushHistory(("main", self.tabMain.currentIndex()))
self._previousSelectionEmpty = False
def focusChanged(self, old, new):
"""
@ -295,6 +354,17 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# OUTLINE
###############################################################################
def outlineChanged(self, selected, deselected):
index = self.treeOutlineOutline.selectionModel().currentIndex()
if not index.isValid():
self.pushHistory(("outline", None))
self._previousSelectionEmpty = True
return
self.pushHistory(("outline", self.mdlOutline.ID(index)))
self._previousSelectionEmpty = False
def outlineRemoveItemsRedac(self):
self.treeRedacOutline.delete()
@ -428,16 +498,23 @@ class MainWindow(QMainWindow, Ui_MainWindow):
widget = tabData['widget']
title = tabData['title']
self.tabPersos.addTab(widget, title)
def handleCharacterSelectionChanged(self):
selectedCharacters = self.lstCharacters.currentCharacters()
characterSelectionIsEmpty = not any(selectedCharacters)
if characterSelectionIsEmpty:
self.pushHistory(("character", None))
self.tabPersos.setEnabled(False)
self._previousSelectionEmpty = True
return
cList = list(filter(None, self.lstCharacters.currentCharacters())) #cList contains all valid characters
character = cList[0]
self.changeCurrentCharacter(character)
self.pushHistory(("character", character.ID()))
self._previousSelectionEmpty = False
if len(selectedCharacters) > 1:
self.setPersoBulkMode(True)
else:
@ -464,7 +541,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
for character in self.lstCharacters.currentCharacters():
self.bulkAffectedCharacters.append(character.name())
def changeCurrentCharacter(self, character, trash=None):
def changeCurrentCharacter(self, character):
if character is None:
return
@ -545,11 +622,17 @@ class MainWindow(QMainWindow, Ui_MainWindow):
def changeCurrentPlot(self):
index = self.lstPlots.currentPlotIndex()
id = self.lstPlots.currentPlotID()
if not index.isValid():
self.tabPlot.setEnabled(False)
self.pushHistory(("plot", None))
self._previousSelectionEmpty = True
return
self.pushHistory(("plot", id))
self._previousSelectionEmpty = False
self.tabPlot.setEnabled(True)
self.txtPlotName.setCurrentModelIndex(index)
self.txtPlotDescription.setCurrentModelIndex(index)
@ -618,8 +701,13 @@ class MainWindow(QMainWindow, Ui_MainWindow):
if not index.isValid():
self.tabWorld.setEnabled(False)
self.pushHistory(("world", None))
self._previousSelectionEmpty = True
return
self.pushHistory(("world", self.mdlWorld.ID(index)))
self._previousSelectionEmpty = False
self.tabWorld.setEnabled(True)
self.txtWorldName.setCurrentModelIndex(index)
self.txtWorldDescription.setCurrentModelIndex(index)
@ -630,6 +718,16 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# EDITOR
###############################################################################
def redacOutlineChanged(self):
index = self.treeRedacOutline.selectionModel().currentIndex()
if not index.isValid():
self.pushHistory(("redac", None))
self._previousSelectionEmpty = True
return
self.pushHistory(("redac", self.mdlOutline.ID(index)))
self._previousSelectionEmpty = False
def openIndex(self, index):
self.treeRedacOutline.setCurrentIndex(index)
@ -727,6 +825,94 @@ class MainWindow(QMainWindow, Ui_MainWindow):
"Merges selected item(s)."
if self._lastFocus: self._lastFocus.merge()
# Navigate
def navigateBack(self):
self.history.back()
def navigateForward(self):
self.history.forward()
def pushHistory(self, entry):
if self._previousSelectionEmpty:
self.history.replace(entry)
else:
self.history.next(entry)
def navigated(self, event):
if event.entry:
match event.entry[0]:
case "character":
if self.tabMain.currentIndex() != self.TabPersos:
self.tabMain.setCurrentIndex(self.TabPersos)
if event.entry[1] is None:
self.lstCharacters.setCurrentItem(None)
self.lstCharacters.clearSelection()
else:
if self.lstCharacters.currentCharacterID() != event.entry[1]:
char = self.lstCharacters.getItemByID(event.entry[1])
if char != None:
self.lstCharacters.clearSelection()
self.lstCharacters.setCurrentItem(char)
case "plot":
if self.tabMain.currentIndex() != self.TabPlots:
self.tabMain.setCurrentIndex(self.TabPlots)
if event.entry[1] is None:
self.lstPlots.setCurrentItem(None)
else:
index = self.lstPlots.currentPlotIndex()
if index and index.row() != event.entry[1]:
plot = self.lstPlots.getItemByID(event.entry[1])
if plot != None:
self.lstPlots.setCurrentItem(plot)
case "world":
if self.tabMain.currentIndex() != self.TabWorld:
self.tabMain.setCurrentIndex(self.TabWorld)
if event.entry[1] is None:
self.treeWorld.selectionModel().clear()
else:
index = self.mdlWorld.selectedIndex()
if index and self.mdlWorld.ID(index) != event.entry[1]:
world = self.mdlWorld.indexByID(event.entry[1])
if world != None:
self.treeWorld.setCurrentIndex(world)
case "outline":
if self.tabMain.currentIndex() != self.TabOutline:
self.tabMain.setCurrentIndex(self.TabOutline)
if event.entry[1] is None:
self.treeOutlineOutline.selectionModel().clear()
else:
index = self.treeOutlineOutline.selectionModel().currentIndex()
if index and self.mdlOutline.ID(index) != event.entry[1]:
outline = self.mdlOutline.getIndexByID(event.entry[1])
if outline is not None:
self.treeOutlineOutline.setCurrentIndex(outline)
case "redac":
if self.tabMain.currentIndex() != self.TabRedac:
self.tabMain.setCurrentIndex(self.TabRedac)
if event.entry[1] is None:
self.treeRedacOutline.selectionModel().clear()
else:
index = self.treeRedacOutline.selectionModel().currentIndex()
if index and self.mdlOutline.ID(index) != event.entry[1]:
outline = self.mdlOutline.getIndexByID(event.entry[1])
if outline is not None:
self.treeRedacOutline.setCurrentIndex(outline)
case "main":
if self.tabMain.currentIndex() != event.entry[1]:
self.lstTabs.setCurrentRow(event.entry[1])
self.actBack.setEnabled(event.position > 0)
self.actForward.setEnabled(event.position < event.count - 1)
###############################################################################
# LOAD AND SAVE
###############################################################################
@ -803,6 +989,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
i.setEnabled(False)
for i in [self.actSave, self.actSaveAs, self.actCloseProject,
self.menuEdit, self.menuView, self.menuOrganize,
self.menuNavigate,
self.menuTools, self.menuHelp, self.actImport,
self.actCompile, self.actSettings]:
i.setEnabled(True)
@ -820,6 +1007,9 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# Add project name to Window's name
self.setWindowTitle(self.projectName() + " - " + self.tr("Manuskript"))
# Reset history
self.history.reset()
# Show main Window
self.switchToProject()
@ -1080,6 +1270,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.tabMain.currentChanged.connect(self.toolbar.setCurrentGroup)
self.tabMain.currentChanged.connect(self.tabMainChanged)
self.history.navigated.connect(self.navigated)
qApp.focusChanged.connect(self.focusChanged)
def makeConnections(self):
@ -1218,10 +1410,12 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# self.redacEditor.setModel(self.mdlOutline)
self.storylineView.setModels(self.mdlOutline, self.mdlCharacter, self.mdlPlots)
self.treeOutlineOutline.selectionModel().selectionChanged.connect(self.outlineChanged, F.AUC)
self.treeOutlineOutline.selectionModel().selectionChanged.connect(self.outlineItemEditor.selectionChanged, F.AUC)
self.treeOutlineOutline.clicked.connect(self.outlineItemEditor.selectionChanged, F.AUC)
# Sync selection
self.treeRedacOutline.selectionModel().selectionChanged.connect(self.redacOutlineChanged, F.AUC)
self.treeRedacOutline.selectionModel().selectionChanged.connect(self.redacMetadata.selectionChanged, F.AUC)
self.treeRedacOutline.clicked.connect(self.redacMetadata.selectionChanged, F.AUC)
@ -1329,6 +1523,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
lambda: self.tblDebugSubPlots.setRootIndex(self.mdlPlots.index(
self.tblDebugPlots.selectionModel().currentIndex().row(),
Plot.steps)))
self.disconnectAll(self.history.navigated)
###############################################################################
# HELP

View file

@ -1016,6 +1016,8 @@ class Ui_MainWindow(object):
self.menuMode.setObjectName("menuMode")
self.menuOrganize = QtWidgets.QMenu(self.menubar)
self.menuOrganize.setObjectName("menuOrganize")
self.menuNavigate = QtWidgets.QMenu(self.menubar)
self.menuNavigate.setObjectName("menuNavigate")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
@ -1200,6 +1202,16 @@ class Ui_MainWindow(object):
self.actMoveDown.setIcon(icon)
self.actMoveDown.setShortcut("Ctrl+Shift+Down")
self.actMoveDown.setObjectName("actMoveDown")
self.actBack = QtWidgets.QAction(MainWindow)
icon = QtGui.QIcon.fromTheme("arrow-left")
self.actBack.setIcon(icon)
self.actBack.setShortcut("Ctrl+<")
self.actBack.setObjectName("actBack")
self.actForward = QtWidgets.QAction(MainWindow)
icon = QtGui.QIcon.fromTheme("arrow-right")
self.actForward.setIcon(icon)
self.actForward.setShortcut("Ctrl+>")
self.actForward.setObjectName("actForward")
self.actRename = QtWidgets.QAction(MainWindow)
icon = QtGui.QIcon.fromTheme("edit-rename")
self.actRename.setIcon(icon)
@ -1355,9 +1367,12 @@ class Ui_MainWindow(object):
self.menuOrganize.addAction(self.actMerge)
self.menuOrganize.addAction(self.actSplitDialog)
self.menuOrganize.addAction(self.actSplitCursor)
self.menuNavigate.addAction(self.actBack)
self.menuNavigate.addAction(self.actForward)
self.menubar.addAction(self.menuFile.menuAction())
self.menubar.addAction(self.menuEdit.menuAction())
self.menubar.addAction(self.menuOrganize.menuAction())
self.menubar.addAction(self.menuNavigate.menuAction())
self.menubar.addAction(self.menuView.menuAction())
self.menubar.addAction(self.menuTools.menuAction())
self.menubar.addAction(self.menuHelp.menuAction())
@ -1559,6 +1574,7 @@ class Ui_MainWindow(object):
self.menuView.setTitle(_translate("MainWindow", "&View"))
self.menuMode.setTitle(_translate("MainWindow", "&Mode"))
self.menuOrganize.setTitle(_translate("MainWindow", "Organi&ze"))
self.menuNavigate.setTitle(_translate("MainWindow", "&Navigate"))
self.dckCheatSheet.setWindowTitle(_translate("MainWindow", "Cheat Sheet"))
self.dckSearch.setWindowTitle(_translate("MainWindow", "Search"))
self.dckNavigation.setWindowTitle(_translate("MainWindow", "&Navigation"))
@ -1592,6 +1608,8 @@ class Ui_MainWindow(object):
self.actDelete.setText(_translate("MainWindow", "&Delete"))
self.actMoveUp.setText(_translate("MainWindow", "&Move Up"))
self.actMoveDown.setText(_translate("MainWindow", "M&ove Down"))
self.actBack.setText(_translate("MainWindow", "Go &back"))
self.actForward.setText(_translate("MainWindow", "Go &forward"))
self.actRename.setText(_translate("MainWindow", "&Rename"))
self.actHeaderSetextL1.setText(_translate("MainWindow", "&Level 1 (setext)"))
self.actHeaderSetextL2.setText(_translate("MainWindow", "Level &2"))

View file

@ -2124,9 +2124,17 @@
<addaction name="actSplitDialog"/>
<addaction name="actSplitCursor"/>
</widget>
<widget class="QMenu" name="menuNavigate">
<property name="title">
<string>&amp;Navigate</string>
</property>
<addaction name="actBack"/>
<addaction name="actForward"/>
</widget>
<addaction name="menuFile"/>
<addaction name="menuEdit"/>
<addaction name="menuOrganize"/>
<addaction name="menuNavigate"/>
<addaction name="menuView"/>
<addaction name="menuTools"/>
<addaction name="menuHelp"/>
@ -2568,6 +2576,30 @@
<string notr="true">Ctrl+Shift+Down</string>
</property>
</action>
<action name="actBack">
<property name="icon">
<iconset theme="arrow-left">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>Go &amp;back</string>
</property>
<property name="shortcut">
<string notr="true">Ctrl+&lt;</string>
</property>
</action>
<action name="actForward">
<property name="icon">
<iconset theme="arrow-right">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>Go &amp;forward</string>
</property>
<property name="shortcut">
<string notr="true">Ctrl+&gt;</string>
</property>
</action>
<action name="actRename">
<property name="icon">
<iconset theme="edit-rename">

View file

@ -65,13 +65,17 @@ class plotTreeView(QTreeWidget):
return find(self.invisibleRootItem(), ID)
def currentPlotIndex(self):
"Returns index of the current item in plot model."
"Returns index of the current item in plot model."
return self._model.getIndexFromID(self.currentPlotID())
def currentPlotID(self):
"Returns ID of the current item in plot model."
ID = None
if self.currentItem():
ID = self.currentItem().data(0, Qt.UserRole)
return self._model.getIndexFromID(ID)
return ID
###############################################################################
# UPDATES
###############################################################################