2015-05-28 13:32:09 +12:00
|
|
|
#!/usr/bin/env python
|
2016-02-06 00:25:25 +13:00
|
|
|
# --!-- coding: utf8 --!--
|
2021-04-10 11:19:03 +12:00
|
|
|
import importlib
|
2016-02-07 00:34:22 +13:00
|
|
|
import os
|
2019-09-13 05:54:53 +12:00
|
|
|
import re
|
2016-02-07 00:34:22 +13:00
|
|
|
|
2019-09-13 05:54:53 +12:00
|
|
|
from PyQt5.Qt import qVersion, PYQT_VERSION_STR
|
2019-05-10 06:15:16 +12:00
|
|
|
from PyQt5.QtCore import (pyqtSignal, QSignalMapper, QTimer, QSettings, Qt, QPoint,
|
2017-12-05 01:27:57 +13:00
|
|
|
QRegExp, QUrl, QSize, QModelIndex)
|
2023-03-07 09:43:31 +13:00
|
|
|
from PyQt5.QtGui import QStandardItemModel, QIcon, QColor, QStandardItem
|
2016-02-07 06:36:02 +13:00
|
|
|
from PyQt5.QtWidgets import QMainWindow, QHeaderView, qApp, QMenu, QActionGroup, QAction, QStyle, QListWidgetItem, \
|
2023-03-08 06:13:36 +13:00
|
|
|
QLabel, QDockWidget, QWidget, QMessageBox, QLineEdit, QTextEdit, QTreeView, QDialog, QTableView
|
2016-02-07 00:34:22 +13:00
|
|
|
|
|
|
|
from manuskript import settings
|
2016-03-25 01:42:47 +13:00
|
|
|
from manuskript.enums import Character, PlotStep, Plot, World, Outline
|
2021-04-13 23:32:46 +12:00
|
|
|
from manuskript.functions import wordCount, appPath, findWidgetsOfClass, openURL, showInFolder
|
2017-10-16 21:48:04 +13:00
|
|
|
import manuskript.functions as F
|
2016-03-12 03:45:51 +13:00
|
|
|
from manuskript import loadSave
|
2021-04-13 23:32:46 +12:00
|
|
|
from manuskript.logging import getLogFilePath
|
2016-03-04 04:38:38 +13:00
|
|
|
from manuskript.models.characterModel import characterModel
|
2017-11-16 08:33:27 +13:00
|
|
|
from manuskript.models import outlineModel
|
2016-02-07 00:34:22 +13:00
|
|
|
from manuskript.models.plotModel import plotModel
|
|
|
|
from manuskript.models.worldModel import worldModel
|
|
|
|
from manuskript.settingsWindow import settingsWindow
|
2016-04-09 20:50:55 +12:00
|
|
|
from manuskript.ui import style
|
2023-03-07 09:43:31 +13:00
|
|
|
from manuskript.ui import characterInfoDialog
|
2017-09-29 08:36:06 +13:00
|
|
|
from manuskript.ui.about import aboutDialog
|
2016-02-07 00:34:22 +13:00
|
|
|
from manuskript.ui.collapsibleDockWidgets import collapsibleDockWidgets
|
2017-11-06 21:16:44 +13:00
|
|
|
from manuskript.ui.importers.importer import importerDialog
|
2016-04-02 06:01:27 +13:00
|
|
|
from manuskript.ui.exporters.exporter import exporterDialog
|
2016-02-07 00:34:22 +13:00
|
|
|
from manuskript.ui.helpLabel import helpLabel
|
|
|
|
from manuskript.ui.mainWindow import Ui_MainWindow
|
2016-02-09 01:50:35 +13:00
|
|
|
from manuskript.ui.tools.frequencyAnalyzer import frequencyAnalyzer
|
2022-12-11 06:05:41 +13:00
|
|
|
from manuskript.ui.tools.targets import TargetsDialog
|
2016-03-06 06:28:29 +13:00
|
|
|
from manuskript.ui.views.outlineDelegates import outlineCharacterDelegate
|
2016-02-07 00:34:22 +13:00
|
|
|
from manuskript.ui.views.plotDelegate import plotDelegate
|
2017-12-01 01:12:55 +13:00
|
|
|
from manuskript.ui.views.MDEditView import MDEditView
|
2018-01-23 06:19:22 +13:00
|
|
|
from manuskript.ui.statusLabel import statusLabel
|
2023-03-07 09:43:31 +13:00
|
|
|
from manuskript.ui.bulkInfoManager import Ui_BulkInfoManager
|
2015-05-28 13:32:09 +12:00
|
|
|
|
2015-07-08 04:28:02 +12:00
|
|
|
# Spellcheck support
|
2016-02-07 00:34:22 +13:00
|
|
|
from manuskript.ui.views.textEditView import textEditView
|
2019-02-22 09:32:34 +13:00
|
|
|
from manuskript.functions import Spellchecker
|
2016-02-07 00:34:22 +13:00
|
|
|
|
2019-10-15 01:36:06 +13:00
|
|
|
import logging
|
|
|
|
LOGGER = logging.getLogger(__name__)
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2015-05-28 13:32:09 +12:00
|
|
|
class MainWindow(QMainWindow, Ui_MainWindow):
|
2019-02-22 09:32:34 +13:00
|
|
|
# dictChanged = pyqtSignal(str)
|
2016-02-06 00:25:25 +13:00
|
|
|
|
2015-07-10 01:30:59 +12:00
|
|
|
# Tab indexes
|
2015-07-10 01:01:07 +12:00
|
|
|
TabInfos = 0
|
|
|
|
TabSummary = 1
|
|
|
|
TabPersos = 2
|
|
|
|
TabPlots = 3
|
|
|
|
TabWorld = 4
|
|
|
|
TabOutline = 5
|
|
|
|
TabRedac = 6
|
2017-12-05 02:12:00 +13:00
|
|
|
TabDebug = 7
|
|
|
|
|
|
|
|
SHOW_DEBUG_TAB = False
|
2015-07-10 01:01:07 +12:00
|
|
|
|
2015-05-28 13:32:09 +12:00
|
|
|
def __init__(self):
|
|
|
|
QMainWindow.__init__(self)
|
|
|
|
self.setupUi(self)
|
2017-11-10 11:01:42 +13:00
|
|
|
|
|
|
|
# Var
|
2015-06-23 10:19:40 +12:00
|
|
|
self.currentProject = None
|
Track dirty state and have the UI respect it
Intending to learn more about the way Manuskript goes about saving the
project in order to figure out how to tackle some recent saving-related
issues, I stumbled into learning that Manuskript likes to save data a
whole lot. Too much, in fact. When I close the project with unsaved
changes, I expected those changes to not be saved... but they were. This
completely subverts my expectations of a program using typical
file-based operations involving opening, saving and closing files.
There are three more settings that influence when the program saves, and
I personally consider them a bit overkill or even detrimental to the
stated purpose. What if Manuskript forces a save when nothing was
changed and something goes wrong? Saving too much can in fact be
dangerous!
For now, I have left existing functionality as-is, but I would prefer to
respect the dirty flag I have introduced in this commit for at least the
'save-on-quit' and 'save every X minutes' features. (The third is
smarter and only triggers after noticing changes, so it is less
important.)
Making sure the dirty flag works as expected is the first step in making
such changes in the future.
UI-wise, this commit now offers the user the opportunity to save their
changes, discard them, or outright cancel their action entirely when
performing a destructive action on a dirty project. As of this commit, I
have identified two of such scenarios:
1) closing the project,
2) closing the window with save-on-quit turned off.
If I missed any, do let me know. But for now, maybe now I can finally
start digging into those issues that sent me down this rabbit hole...
2019-04-25 09:02:37 +12:00
|
|
|
self.projectDirty = None # has the user made any unsaved changes ?
|
2017-11-10 11:01:42 +13:00
|
|
|
self._lastFocus = None
|
2017-12-01 01:12:55 +13:00
|
|
|
self._lastMDEditView = None
|
2019-09-18 01:07:08 +12:00
|
|
|
self._defaultCursorFlashTime = 1000 # Overridden at startup with system
|
2017-11-21 03:42:30 +13:00
|
|
|
# value. In manuskript.main.
|
2017-12-01 05:47:23 +13:00
|
|
|
self._autoLoadProject = None # Used to load a command line project
|
2019-01-22 18:37:22 +13:00
|
|
|
self.sessionStartWordCount = 0 # Used to track session targets
|
2015-07-02 20:08:20 +12:00
|
|
|
|
2015-05-31 16:03:07 +12:00
|
|
|
self.readSettings()
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2015-05-29 05:15:57 +12:00
|
|
|
# UI
|
2015-06-04 05:25:03 +12:00
|
|
|
self.setupMoreUi()
|
2018-01-23 06:19:22 +13:00
|
|
|
self.statusLabel = statusLabel(parent=self)
|
2017-12-08 22:01:58 +13:00
|
|
|
self.statusLabel.setAutoFillBackground(True)
|
|
|
|
self.statusLabel.hide()
|
2015-07-02 20:08:20 +12:00
|
|
|
|
2015-06-23 10:19:40 +12:00
|
|
|
# Welcome
|
2015-06-24 04:22:39 +12:00
|
|
|
self.welcome.updateValues()
|
2017-10-20 09:21:15 +13:00
|
|
|
self.switchToWelcome()
|
2015-07-02 20:08:20 +12:00
|
|
|
|
2015-05-31 16:03:07 +12:00
|
|
|
# Word count
|
|
|
|
self.mprWordCount = QSignalMapper(self)
|
2015-05-28 13:32:09 +12:00
|
|
|
for t, i in [
|
2016-03-02 11:41:55 +13:00
|
|
|
(self.txtSummarySentence, 0),
|
2015-05-28 13:32:09 +12:00
|
|
|
(self.txtSummaryPara, 1),
|
|
|
|
(self.txtSummaryPage, 2),
|
|
|
|
(self.txtSummaryFull, 3)
|
2016-02-06 00:25:25 +13:00
|
|
|
]:
|
2015-05-31 16:03:07 +12:00
|
|
|
t.textChanged.connect(self.mprWordCount.map)
|
|
|
|
self.mprWordCount.setMapping(t, i)
|
|
|
|
self.mprWordCount.mapped.connect(self.wordCount)
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2015-06-21 21:29:35 +12:00
|
|
|
self.cmbSummary.setCurrentIndex(0)
|
|
|
|
self.cmbSummary.currentIndexChanged.emit(0)
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2015-06-10 10:20:32 +12:00
|
|
|
# Main Menu
|
2015-06-23 10:19:40 +12:00
|
|
|
for i in [self.actSave, self.actSaveAs, self.actCloseProject,
|
2017-11-27 20:05:53 +13:00
|
|
|
self.menuEdit, self.menuView, self.menuOrganize,
|
2017-11-25 06:19:50 +13:00
|
|
|
self.menuTools, self.menuHelp, self.actImport,
|
|
|
|
self.actCompile, self.actSettings]:
|
2015-06-23 10:19:40 +12:00
|
|
|
i.setEnabled(False)
|
2015-07-02 20:08:20 +12:00
|
|
|
|
2017-11-10 11:01:42 +13:00
|
|
|
# Main Menu:: File
|
2015-06-24 04:22:39 +12:00
|
|
|
self.actOpen.triggered.connect(self.welcome.openFile)
|
2015-06-17 22:00:03 +12:00
|
|
|
self.actSave.triggered.connect(self.saveDatas)
|
2015-06-24 04:22:39 +12:00
|
|
|
self.actSaveAs.triggered.connect(self.welcome.saveAsFile)
|
2017-11-06 21:16:44 +13:00
|
|
|
self.actImport.triggered.connect(self.doImport)
|
2015-07-01 23:14:03 +12:00
|
|
|
self.actCompile.triggered.connect(self.doCompile)
|
2015-06-24 04:22:39 +12:00
|
|
|
self.actCloseProject.triggered.connect(self.closeProject)
|
2015-06-10 10:20:32 +12:00
|
|
|
self.actQuit.triggered.connect(self.close)
|
2015-07-02 20:08:20 +12:00
|
|
|
|
2017-12-01 01:12:55 +13:00
|
|
|
# Main menu:: Edit
|
2017-11-10 11:01:42 +13:00
|
|
|
self.actCopy.triggered.connect(self.documentsCopy)
|
|
|
|
self.actCut.triggered.connect(self.documentsCut)
|
|
|
|
self.actPaste.triggered.connect(self.documentsPaste)
|
2019-12-22 04:42:49 +13:00
|
|
|
self.actSearch.triggered.connect(self.doSearch)
|
2017-11-25 08:50:18 +13:00
|
|
|
self.actRename.triggered.connect(self.documentsRename)
|
2017-11-10 11:01:42 +13:00
|
|
|
self.actDuplicate.triggered.connect(self.documentsDuplicate)
|
|
|
|
self.actDelete.triggered.connect(self.documentsDelete)
|
2017-12-01 01:12:55 +13:00
|
|
|
self.actLabels.triggered.connect(self.settingsLabel)
|
|
|
|
self.actStatus.triggered.connect(self.settingsStatus)
|
|
|
|
self.actSettings.triggered.connect(self.settingsWindow)
|
|
|
|
|
|
|
|
# Main menu:: Edit:: Format
|
|
|
|
self.actHeaderSetextL1.triggered.connect(self.formatSetext1)
|
|
|
|
self.actHeaderSetextL2.triggered.connect(self.formatSetext2)
|
|
|
|
self.actHeaderAtxL1.triggered.connect(self.formatAtx1)
|
|
|
|
self.actHeaderAtxL2.triggered.connect(self.formatAtx2)
|
|
|
|
self.actHeaderAtxL3.triggered.connect(self.formatAtx3)
|
|
|
|
self.actHeaderAtxL4.triggered.connect(self.formatAtx4)
|
|
|
|
self.actHeaderAtxL5.triggered.connect(self.formatAtx5)
|
|
|
|
self.actHeaderAtxL6.triggered.connect(self.formatAtx6)
|
|
|
|
self.actFormatBold.triggered.connect(self.formatBold)
|
|
|
|
self.actFormatItalic.triggered.connect(self.formatItalic)
|
|
|
|
self.actFormatStrike.triggered.connect(self.formatStrike)
|
|
|
|
self.actFormatVerbatim.triggered.connect(self.formatVerbatim)
|
|
|
|
self.actFormatSuperscript.triggered.connect(self.formatSuperscript)
|
|
|
|
self.actFormatSubscript.triggered.connect(self.formatSubscript)
|
|
|
|
self.actFormatCommentLines.triggered.connect(self.formatCommentLines)
|
|
|
|
self.actFormatList.triggered.connect(self.formatList)
|
|
|
|
self.actFormatOrderedList.triggered.connect(self.formatOrderedList)
|
|
|
|
self.actFormatBlockquote.triggered.connect(self.formatBlockquote)
|
|
|
|
self.actFormatCommentBlock.triggered.connect(self.formatCommentBlock)
|
|
|
|
self.actFormatClear.triggered.connect(self.formatClear)
|
|
|
|
|
|
|
|
# Main menu:: Organize
|
2017-11-10 11:01:42 +13:00
|
|
|
self.actMoveUp.triggered.connect(self.documentsMoveUp)
|
|
|
|
self.actMoveDown.triggered.connect(self.documentsMoveDown)
|
|
|
|
self.actSplitDialog.triggered.connect(self.documentsSplitDialog)
|
|
|
|
self.actSplitCursor.triggered.connect(self.documentsSplitCursor)
|
|
|
|
self.actMerge.triggered.connect(self.documentsMerge)
|
|
|
|
|
|
|
|
# Main Menu:: view
|
|
|
|
self.generateViewMenu()
|
2016-03-25 01:42:47 +13:00
|
|
|
self.actModeGroup = QActionGroup(self)
|
|
|
|
self.actModeSimple.setActionGroup(self.actModeGroup)
|
|
|
|
self.actModeFiction.setActionGroup(self.actModeGroup)
|
|
|
|
self.actModeSimple.triggered.connect(self.setViewModeSimple)
|
|
|
|
self.actModeFiction.triggered.connect(self.setViewModeFiction)
|
|
|
|
|
2017-11-10 11:01:42 +13:00
|
|
|
# Main Menu:: Tool
|
|
|
|
self.actToolFrequency.triggered.connect(self.frequencyAnalyzer)
|
2019-01-22 18:37:22 +13:00
|
|
|
self.actToolTargets.triggered.connect(self.sessionTargets)
|
2021-04-13 23:32:46 +12:00
|
|
|
self.actSupport.triggered.connect(self.support)
|
|
|
|
self.actLocateLog.triggered.connect(self.locateLogFile)
|
2017-11-10 11:01:42 +13:00
|
|
|
self.actAbout.triggered.connect(self.about)
|
|
|
|
|
2015-06-25 01:43:35 +12:00
|
|
|
self.makeUIConnections()
|
2015-07-02 20:08:20 +12:00
|
|
|
|
2019-01-22 18:37:22 +13:00
|
|
|
# Tools non-modal windows
|
|
|
|
self.td = None # Targets Dialog
|
|
|
|
self.fw = None # Frequency Window
|
|
|
|
|
2016-02-06 00:25:25 +13:00
|
|
|
# self.loadProject(os.path.join(appPath(), "test_project.zip"))
|
2017-10-24 01:40:55 +13:00
|
|
|
|
2023-03-07 09:43:31 +13:00
|
|
|
# Bulk Character Info Management
|
2023-03-08 06:13:36 +13:00
|
|
|
self.tabsData = self.saveCharacterTabs() # Used for restoring tabsData with loadCharacterTabs() methods.
|
|
|
|
self.BulkManageUi = None
|
2023-03-07 09:43:31 +13:00
|
|
|
self.bulkAffectedCharacters = []
|
2023-03-16 09:06:31 +13:00
|
|
|
self.isPersoBulkModeEnabled = False
|
2023-03-07 09:43:31 +13:00
|
|
|
|
2017-10-20 09:21:15 +13:00
|
|
|
def updateDockVisibility(self, restore=False):
|
|
|
|
"""
|
2017-10-24 01:40:55 +13:00
|
|
|
Saves the state of the docks visibility. Or if `restore` is True,
|
2017-10-20 09:21:15 +13:00
|
|
|
restores from `self._dckVisibility`. This allows to hide the docks
|
|
|
|
while showing the welcome screen, and then restore them as they
|
|
|
|
were.
|
2017-10-24 01:40:55 +13:00
|
|
|
|
2017-10-20 09:21:15 +13:00
|
|
|
If `self._dckVisibility` contains "LOCK", then we don't override values
|
|
|
|
with current visibility state. This is used the first time we load.
|
|
|
|
"LOCK" is then removed.
|
|
|
|
"""
|
|
|
|
docks = [
|
|
|
|
self.dckCheatSheet,
|
|
|
|
self.dckNavigation,
|
|
|
|
self.dckSearch,
|
|
|
|
]
|
2017-10-24 01:40:55 +13:00
|
|
|
|
2017-10-20 09:21:15 +13:00
|
|
|
for d in docks:
|
|
|
|
if not restore:
|
|
|
|
# We store the values, but only if "LOCK" is not present
|
|
|
|
if not "LOCK" in self._dckVisibility:
|
|
|
|
self._dckVisibility[d.objectName()] = d.isVisible()
|
|
|
|
# Hide the dock
|
|
|
|
d.setVisible(False)
|
|
|
|
else:
|
2018-01-07 06:48:40 +13:00
|
|
|
# Restore the dock's visibility based on stored value
|
2017-10-20 09:21:15 +13:00
|
|
|
d.setVisible(self._dckVisibility[d.objectName()])
|
2017-10-24 01:40:55 +13:00
|
|
|
|
2017-10-20 09:21:15 +13:00
|
|
|
# Lock is used only once, at start up. We can remove it
|
|
|
|
if "LOCK" in self._dckVisibility:
|
|
|
|
self._dckVisibility.pop("LOCK")
|
2017-10-24 01:40:55 +13:00
|
|
|
|
2017-10-20 09:21:15 +13:00
|
|
|
def switchToWelcome(self):
|
|
|
|
"""
|
|
|
|
While switching to welcome screen, we have to hide all the docks.
|
|
|
|
Otherwise one could use the search dock, and manuskript would crash.
|
2018-01-07 06:48:40 +13:00
|
|
|
Plus it's unnecessary distraction.
|
2017-10-20 09:21:15 +13:00
|
|
|
But we also want to restore them to their visibility prior to switching,
|
|
|
|
so we store states.
|
|
|
|
"""
|
|
|
|
# Stores the state of docks
|
|
|
|
self.updateDockVisibility()
|
|
|
|
# Hides the toolbar
|
|
|
|
self.toolbar.setVisible(False)
|
|
|
|
# Switch to welcome screen
|
|
|
|
self.stack.setCurrentIndex(0)
|
2017-10-24 01:40:55 +13:00
|
|
|
|
2017-10-20 09:21:15 +13:00
|
|
|
def switchToProject(self):
|
|
|
|
"""Restores docks and toolbar visibility, and switch to project."""
|
|
|
|
# Restores the docks visibility
|
|
|
|
self.updateDockVisibility(restore=True)
|
|
|
|
# Show the toolbar
|
|
|
|
self.toolbar.setVisible(True)
|
|
|
|
self.stack.setCurrentIndex(1)
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2017-11-10 11:01:42 +13:00
|
|
|
###############################################################################
|
|
|
|
# GENERAL / UI STUFF
|
|
|
|
###############################################################################
|
|
|
|
|
|
|
|
def tabMainChanged(self):
|
|
|
|
"Called when main tab changes."
|
2017-11-27 20:05:53 +13:00
|
|
|
tabIsEditor = self.tabMain.currentIndex() == self.TabRedac
|
|
|
|
self.menuOrganize.menuAction().setEnabled(tabIsEditor)
|
|
|
|
for i in [self.actCut,
|
|
|
|
self.actCopy,
|
|
|
|
self.actPaste,
|
|
|
|
self.actDelete,
|
|
|
|
self.actRename]:
|
|
|
|
i.setEnabled(tabIsEditor)
|
2017-11-10 11:01:42 +13:00
|
|
|
|
|
|
|
def focusChanged(self, old, new):
|
|
|
|
"""
|
|
|
|
We get notified by qApp when focus changes, from old to new widget.
|
|
|
|
"""
|
|
|
|
|
2017-12-01 01:12:55 +13:00
|
|
|
# If new is a MDEditView, we keep it in memory
|
|
|
|
if issubclass(type(new), MDEditView):
|
|
|
|
self._lastMDEditView = new
|
|
|
|
else:
|
|
|
|
self._lastMDEditView = None
|
|
|
|
|
2017-11-11 05:21:02 +13:00
|
|
|
# Determine which view had focus last, to send the keyboard shortcuts
|
2017-11-10 11:01:42 +13:00
|
|
|
# to the right place
|
|
|
|
|
|
|
|
targets = [
|
|
|
|
self.treeRedacOutline,
|
|
|
|
self.mainEditor
|
2023-02-10 10:13:01 +13:00
|
|
|
]
|
2017-11-10 11:01:42 +13:00
|
|
|
|
2023-02-10 10:13:01 +13:00
|
|
|
while new is not None:
|
2017-11-10 11:01:42 +13:00
|
|
|
if new in targets:
|
|
|
|
self._lastFocus = new
|
|
|
|
break
|
|
|
|
new = new.parent()
|
|
|
|
|
Track dirty state and have the UI respect it
Intending to learn more about the way Manuskript goes about saving the
project in order to figure out how to tackle some recent saving-related
issues, I stumbled into learning that Manuskript likes to save data a
whole lot. Too much, in fact. When I close the project with unsaved
changes, I expected those changes to not be saved... but they were. This
completely subverts my expectations of a program using typical
file-based operations involving opening, saving and closing files.
There are three more settings that influence when the program saves, and
I personally consider them a bit overkill or even detrimental to the
stated purpose. What if Manuskript forces a save when nothing was
changed and something goes wrong? Saving too much can in fact be
dangerous!
For now, I have left existing functionality as-is, but I would prefer to
respect the dirty flag I have introduced in this commit for at least the
'save-on-quit' and 'save every X minutes' features. (The third is
smarter and only triggers after noticing changes, so it is less
important.)
Making sure the dirty flag works as expected is the first step in making
such changes in the future.
UI-wise, this commit now offers the user the opportunity to save their
changes, discard them, or outright cancel their action entirely when
performing a destructive action on a dirty project. As of this commit, I
have identified two of such scenarios:
1) closing the project,
2) closing the window with save-on-quit turned off.
If I missed any, do let me know. But for now, maybe now I can finally
start digging into those issues that sent me down this rabbit hole...
2019-04-25 09:02:37 +12:00
|
|
|
def projectName(self):
|
|
|
|
"""
|
|
|
|
Returns a user-friendly name for the loaded project.
|
|
|
|
"""
|
|
|
|
pName = os.path.split(self.currentProject)[1]
|
|
|
|
if pName.endswith('.msk'):
|
|
|
|
pName=pName[:-4]
|
|
|
|
return pName
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2016-02-06 00:25:25 +13:00
|
|
|
###############################################################################
|
|
|
|
# OUTLINE
|
|
|
|
###############################################################################
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2015-07-03 06:10:25 +12:00
|
|
|
def outlineRemoveItemsRedac(self):
|
|
|
|
self.treeRedacOutline.delete()
|
2016-02-06 00:25:25 +13:00
|
|
|
|
2015-07-03 06:10:25 +12:00
|
|
|
def outlineRemoveItemsOutline(self):
|
|
|
|
self.treeOutlineOutline.delete()
|
2016-02-06 00:25:25 +13:00
|
|
|
|
|
|
|
###############################################################################
|
2016-03-24 23:17:48 +13:00
|
|
|
# CHARACTERS
|
2016-02-06 00:25:25 +13:00
|
|
|
###############################################################################
|
2015-06-01 08:41:32 +12:00
|
|
|
|
2023-03-07 09:43:31 +13:00
|
|
|
def setPersoBulkMode(self, enabled: bool):
|
2023-03-08 06:13:36 +13:00
|
|
|
if enabled and self.BulkManageUi is None: # Delete all tabs and create the manager one
|
|
|
|
# Create the widget
|
2023-03-07 09:43:31 +13:00
|
|
|
bulkPersoInfoManager = QWidget()
|
|
|
|
bulkPersoInfoManagerUi = Ui_BulkInfoManager()
|
|
|
|
bulkPersoInfoManagerUi.setupUi(bulkPersoInfoManager)
|
2023-03-08 06:13:36 +13:00
|
|
|
|
|
|
|
self.BulkManageUi = bulkPersoInfoManagerUi # for global use
|
|
|
|
|
|
|
|
model = QStandardItemModel()
|
|
|
|
|
|
|
|
# Set the column headers
|
|
|
|
model.setColumnCount(2)
|
2023-03-16 09:06:31 +13:00
|
|
|
model.setHorizontalHeaderLabels([self.tr("Name"), self.tr("Value")])
|
2023-03-08 06:13:36 +13:00
|
|
|
|
|
|
|
# Set the width
|
2023-03-16 09:06:31 +13:00
|
|
|
self.updatePersoInfoView(bulkPersoInfoManagerUi.tableView)
|
2023-03-08 06:13:36 +13:00
|
|
|
|
|
|
|
bulkPersoInfoManagerUi.tableView.setModel(model) # Set the model of tableView
|
|
|
|
|
2023-03-07 09:43:31 +13:00
|
|
|
self.tabPersos.clear()
|
2023-03-16 09:06:31 +13:00
|
|
|
self.tabPersos.addTab(bulkPersoInfoManager, self.tr("Bulk Info Manager"))
|
2023-03-07 09:43:31 +13:00
|
|
|
self.isPersoBulkModeEnabled = True
|
2023-03-08 06:13:36 +13:00
|
|
|
self.refreshBulkAffectedCharacters()
|
2023-03-07 09:43:31 +13:00
|
|
|
|
|
|
|
# Showing the character names on the label
|
2023-03-10 04:24:35 +13:00
|
|
|
labelText = self.createCharacterSelectionString()
|
2023-03-07 09:43:31 +13:00
|
|
|
bulkPersoInfoManagerUi.lblCharactersDynamic.setText(labelText)
|
|
|
|
|
|
|
|
# Making the connections
|
2023-03-08 08:27:16 +13:00
|
|
|
self.makeBulkInfoConnections(bulkPersoInfoManagerUi)
|
2023-03-07 09:43:31 +13:00
|
|
|
|
2023-03-08 06:13:36 +13:00
|
|
|
elif enabled and self.BulkManageUi is not None: # If yet another character is selected, refresh the label
|
2023-03-10 04:24:35 +13:00
|
|
|
labelText = self.createCharacterSelectionString()
|
2023-03-08 06:13:36 +13:00
|
|
|
self.BulkManageUi.lblCharactersDynamic.setText(labelText)
|
|
|
|
|
|
|
|
else: # Delete manager tab and restore the others
|
|
|
|
if self.BulkManageUi is not None:
|
2023-03-07 09:43:31 +13:00
|
|
|
self.tabPersos.clear()
|
|
|
|
self.loadCharacterTabs()
|
2023-03-08 06:13:36 +13:00
|
|
|
self.BulkManageUi = None
|
2023-03-07 09:43:31 +13:00
|
|
|
self.bulkAffectedCharacters.clear()
|
|
|
|
|
2023-03-10 04:24:35 +13:00
|
|
|
def createCharacterSelectionString(self):
|
|
|
|
self.refreshBulkAffectedCharacters()
|
|
|
|
labelText = ""
|
|
|
|
length = len(self.bulkAffectedCharacters)
|
|
|
|
for i in range(length-1):
|
|
|
|
labelText += '"' + self.bulkAffectedCharacters[i] + '"' + ", "
|
|
|
|
|
|
|
|
labelText += '"' + self.bulkAffectedCharacters[length-1] + '"'
|
|
|
|
|
|
|
|
return labelText
|
|
|
|
|
2023-03-08 08:27:16 +13:00
|
|
|
def makeBulkInfoConnections(self, bulkUi):
|
2023-03-08 06:13:36 +13:00
|
|
|
# A lambda has to be used to pass in the argument
|
2023-03-07 09:43:31 +13:00
|
|
|
bulkUi.btnPersoBulkAddInfo.clicked.connect(lambda: self.addBulkInfo(bulkUi))
|
2023-03-08 06:13:36 +13:00
|
|
|
bulkUi.btnPersoBulkRmInfo.clicked.connect(lambda: self.removeBulkInfo(bulkUi))
|
2023-03-08 08:27:16 +13:00
|
|
|
bulkUi.btnPersoBulkApply.clicked.connect(lambda: self.applyBulkInfo(bulkUi))
|
|
|
|
|
|
|
|
def applyBulkInfo(self, bulkUi):
|
|
|
|
selectedItems = self.lstCharacters.currentCharacterIDs()
|
|
|
|
|
|
|
|
# Get the data from the tableview
|
|
|
|
model = bulkUi.tableView.model()
|
|
|
|
if model.rowCount() == 0:
|
2023-03-16 09:06:31 +13:00
|
|
|
QMessageBox.warning(self, self.tr("No Entries!"),
|
|
|
|
self.tr("Please add entries to apply to the selected characters."))
|
2023-03-08 08:27:16 +13:00
|
|
|
return
|
|
|
|
|
|
|
|
# Loop through each selected character and add the bulk info to them
|
|
|
|
for ID in selectedItems:
|
|
|
|
for row in range(model.rowCount()):
|
|
|
|
description = model.item(row, 0).text()
|
|
|
|
value = model.item(row, 1).text()
|
|
|
|
self.lstCharacters._model.addCharacterInfo(ID, description, value)
|
|
|
|
|
2023-03-16 09:06:31 +13:00
|
|
|
QMessageBox.information(self, self.tr("Bulk Info Applied"),
|
|
|
|
self.tr("The bulk info has been applied to the selected characters."))
|
2023-03-08 08:27:16 +13:00
|
|
|
|
2023-03-16 09:06:31 +13:00
|
|
|
# Remove all rows from the table
|
2023-03-08 08:27:16 +13:00
|
|
|
model.removeRows(0, model.rowCount())
|
2023-03-07 09:43:31 +13:00
|
|
|
|
|
|
|
def addBulkInfo(self, bulkUi): # Adds an item to the list
|
|
|
|
charInfoDialog = QDialog()
|
|
|
|
charInfoUi = characterInfoDialog.Ui_characterInfoDialog()
|
|
|
|
charInfoUi.setupUi(charInfoDialog)
|
|
|
|
|
|
|
|
if charInfoDialog.exec_() == QDialog.Accepted:
|
|
|
|
# User clicked OK, get the input values
|
|
|
|
description = charInfoUi.descriptionLineEdit.text()
|
|
|
|
value = charInfoUi.valueLineEdit.text()
|
|
|
|
|
|
|
|
# Add a new row to the model with the description and value
|
|
|
|
row = [QStandardItem(description), QStandardItem(value)]
|
|
|
|
|
|
|
|
bulkUi.tableView.model().appendRow(row)
|
|
|
|
|
|
|
|
bulkUi.tableView.update()
|
2023-03-16 09:06:31 +13:00
|
|
|
|
2023-03-08 06:13:36 +13:00
|
|
|
def removeBulkInfo(self, bulkUi):
|
|
|
|
# Get the selected rows
|
|
|
|
selection = bulkUi.tableView.selectionModel().selectedRows()
|
|
|
|
|
2023-03-08 08:27:16 +13:00
|
|
|
# Iterate over the rows and remove them (reversed, so the iteration is not affected)
|
2023-03-08 06:13:36 +13:00
|
|
|
for index in reversed(selection):
|
|
|
|
bulkUi.tableView.model().removeRow(index.row())
|
2023-03-07 09:43:31 +13:00
|
|
|
|
|
|
|
def saveCharacterTabs(self):
|
|
|
|
tabsData = []
|
|
|
|
for i in range(self.tabPersos.count()):
|
|
|
|
tabData = {}
|
|
|
|
widget = self.tabPersos.widget(i)
|
|
|
|
tabData['widget'] = widget
|
|
|
|
tabData['title'] = self.tabPersos.tabText(i)
|
|
|
|
tabsData.append(tabData)
|
|
|
|
return tabsData
|
|
|
|
|
|
|
|
def loadCharacterTabs(self):
|
|
|
|
for tabData in self.tabsData:
|
|
|
|
widget = tabData['widget']
|
|
|
|
title = tabData['title']
|
|
|
|
self.tabPersos.addTab(widget, title)
|
2023-03-06 02:14:58 +13:00
|
|
|
def handleCharacterSelectionChanged(self):
|
|
|
|
selectedCharacters = self.lstCharacters.currentCharacters()
|
2023-03-06 03:04:56 +13:00
|
|
|
characterSelectionIsEmpty = not any(selectedCharacters)
|
2023-03-06 02:14:58 +13:00
|
|
|
if characterSelectionIsEmpty:
|
|
|
|
self.tabPersos.setEnabled(False)
|
2023-03-06 03:04:56 +13:00
|
|
|
return
|
2023-03-07 09:43:31 +13:00
|
|
|
cList = list(filter(None, self.lstCharacters.currentCharacters())) #cList contains all valid characters
|
2023-03-06 03:04:56 +13:00
|
|
|
character = cList[0]
|
|
|
|
self.changeCurrentCharacter(character)
|
|
|
|
|
|
|
|
if len(selectedCharacters) > 1:
|
2023-03-07 09:43:31 +13:00
|
|
|
self.setPersoBulkMode(True)
|
|
|
|
else:
|
2023-03-10 04:24:35 +13:00
|
|
|
if self.BulkManageUi is not None:
|
|
|
|
self.refreshBulkAffectedCharacters()
|
|
|
|
self.BulkManageUi.lblCharactersDynamic.setText( self.createCharacterSelectionString() )
|
|
|
|
|
|
|
|
tableview_model = self.BulkManageUi.tableView.model()
|
|
|
|
if tableview_model.rowCount() > 0:
|
|
|
|
confirm = QMessageBox.warning(
|
|
|
|
self, "Un-applied data!",
|
|
|
|
"There are un-applied entries in this tab. Discard them?",
|
|
|
|
QMessageBox.Yes | QMessageBox.No,
|
|
|
|
defaultButton = QMessageBox.No
|
|
|
|
)
|
|
|
|
if confirm != QMessageBox.Yes:
|
|
|
|
return
|
2023-03-07 09:43:31 +13:00
|
|
|
|
2023-03-10 04:24:35 +13:00
|
|
|
self.setPersoBulkMode(False)
|
2023-03-06 03:04:56 +13:00
|
|
|
self.tabPersos.setEnabled(True)
|
|
|
|
|
2023-03-08 06:13:36 +13:00
|
|
|
def refreshBulkAffectedCharacters(self): #Characters affected by a potential bulk-info modification
|
|
|
|
self.bulkAffectedCharacters = []
|
2023-03-07 09:43:31 +13:00
|
|
|
for character in self.lstCharacters.currentCharacters():
|
|
|
|
self.bulkAffectedCharacters.append(character.name())
|
|
|
|
|
2023-03-06 03:04:56 +13:00
|
|
|
def changeCurrentCharacter(self, character, trash=None):
|
|
|
|
if character is None:
|
2015-06-29 09:46:51 +12:00
|
|
|
return
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2023-03-06 03:04:56 +13:00
|
|
|
index = character.index()
|
2015-07-02 20:08:20 +12:00
|
|
|
|
2015-06-29 20:22:18 +12:00
|
|
|
for w in [
|
|
|
|
self.txtPersoName,
|
|
|
|
self.sldPersoImportance,
|
|
|
|
self.txtPersoMotivation,
|
|
|
|
self.txtPersoGoal,
|
|
|
|
self.txtPersoConflict,
|
|
|
|
self.txtPersoEpiphany,
|
2016-03-02 11:41:55 +13:00
|
|
|
self.txtPersoSummarySentence,
|
2015-06-29 20:22:18 +12:00
|
|
|
self.txtPersoSummaryPara,
|
|
|
|
self.txtPersoSummaryFull,
|
|
|
|
self.txtPersoNotes,
|
2016-02-06 00:25:25 +13:00
|
|
|
]:
|
2015-06-29 20:22:18 +12:00
|
|
|
w.setCurrentModelIndex(index)
|
2015-07-02 20:08:20 +12:00
|
|
|
|
2015-06-29 21:28:34 +12:00
|
|
|
# Button color
|
2023-03-06 03:04:56 +13:00
|
|
|
self.updateCharacterColor(character.ID())
|
2015-07-02 20:08:20 +12:00
|
|
|
|
2017-06-23 05:50:17 +12:00
|
|
|
# Slider importance
|
2023-03-06 03:04:56 +13:00
|
|
|
self.updateCharacterImportance(character.ID())
|
2017-06-23 05:50:17 +12:00
|
|
|
|
2020-03-31 16:55:30 +13:00
|
|
|
# POV state
|
2023-03-06 03:04:56 +13:00
|
|
|
self.updateCharacterPOVState(character.ID())
|
2020-03-31 16:55:30 +13:00
|
|
|
|
2016-03-04 04:38:38 +13:00
|
|
|
# Character Infos
|
2015-06-29 21:28:34 +12:00
|
|
|
self.tblPersoInfos.setRootIndex(index)
|
2015-07-02 20:08:20 +12:00
|
|
|
|
2016-03-04 04:38:38 +13:00
|
|
|
if self.mdlCharacter.rowCount(index):
|
2023-03-16 09:06:31 +13:00
|
|
|
self.updatePersoInfoView(self.tblPersoInfos)
|
2023-03-06 03:04:56 +13:00
|
|
|
|
2023-03-16 09:06:31 +13:00
|
|
|
def updatePersoInfoView(self, infoView):
|
|
|
|
infoView.horizontalHeader().setStretchLastSection(True)
|
|
|
|
infoView.horizontalHeader().setMinimumSectionSize(20)
|
|
|
|
infoView.horizontalHeader().setMaximumSectionSize(500)
|
|
|
|
infoView.verticalHeader().hide()
|
2015-06-29 21:28:34 +12:00
|
|
|
|
2016-03-04 04:38:38 +13:00
|
|
|
def updateCharacterColor(self, ID):
|
|
|
|
c = self.mdlCharacter.getCharacterByID(ID)
|
|
|
|
color = c.color().name()
|
|
|
|
self.btnPersoColor.setStyleSheet("background:{};".format(color))
|
|
|
|
|
2017-06-23 05:50:17 +12:00
|
|
|
def updateCharacterImportance(self, ID):
|
|
|
|
c = self.mdlCharacter.getCharacterByID(ID)
|
|
|
|
self.sldPersoImportance.setValue(int(c.importance()))
|
|
|
|
|
2020-03-31 16:55:30 +13:00
|
|
|
def updateCharacterPOVState(self, ID):
|
|
|
|
c = self.mdlCharacter.getCharacterByID(ID)
|
|
|
|
self.disconnectAll(self.chkPersoPOV.stateChanged, self.lstCharacters.changeCharacterPOVState)
|
|
|
|
|
|
|
|
if c.pov():
|
|
|
|
self.chkPersoPOV.setCheckState(Qt.Checked)
|
|
|
|
else:
|
|
|
|
self.chkPersoPOV.setCheckState(Qt.Unchecked)
|
|
|
|
|
2021-02-22 13:58:28 +13:00
|
|
|
try:
|
|
|
|
self.chkPersoPOV.stateChanged.connect(self.lstCharacters.changeCharacterPOVState, F.AUC)
|
|
|
|
self.chkPersoPOV.setEnabled(len(self.mdlOutline.findItemsByPOV(ID)) == 0)
|
|
|
|
except TypeError:
|
|
|
|
#don't know what's up with this
|
|
|
|
pass
|
2020-03-31 16:55:30 +13:00
|
|
|
|
2021-12-01 11:40:31 +13:00
|
|
|
def deleteCharacter(self):
|
2023-03-08 08:56:54 +13:00
|
|
|
ID = self.lstCharacters.removeCharacters()
|
2021-12-01 11:40:31 +13:00
|
|
|
if ID is None:
|
|
|
|
return
|
|
|
|
for itemID in self.mdlOutline.findItemsByPOV(ID):
|
|
|
|
item = self.mdlOutline.getItemByID(itemID)
|
|
|
|
if item:
|
|
|
|
item.resetPOV()
|
|
|
|
|
2016-02-06 00:25:25 +13:00
|
|
|
###############################################################################
|
|
|
|
# PLOTS
|
|
|
|
###############################################################################
|
2015-06-22 23:11:45 +12:00
|
|
|
|
|
|
|
def changeCurrentPlot(self):
|
2015-06-23 06:30:43 +12:00
|
|
|
index = self.lstPlots.currentPlotIndex()
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2015-06-22 23:11:45 +12:00
|
|
|
if not index.isValid():
|
|
|
|
self.tabPlot.setEnabled(False)
|
|
|
|
return
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2015-06-22 23:11:45 +12:00
|
|
|
self.tabPlot.setEnabled(True)
|
|
|
|
self.txtPlotName.setCurrentModelIndex(index)
|
|
|
|
self.txtPlotDescription.setCurrentModelIndex(index)
|
|
|
|
self.txtPlotResult.setCurrentModelIndex(index)
|
|
|
|
self.sldPlotImportance.setCurrentModelIndex(index)
|
2015-06-23 07:34:11 +12:00
|
|
|
self.lstPlotPerso.setRootIndex(index.sibling(index.row(),
|
2017-11-16 09:05:48 +13:00
|
|
|
Plot.characters))
|
2017-07-02 04:09:08 +12:00
|
|
|
|
|
|
|
# Slider importance
|
|
|
|
self.updatePlotImportance(index.row())
|
|
|
|
|
2017-11-16 09:05:48 +13:00
|
|
|
subplotindex = index.sibling(index.row(), Plot.steps)
|
2015-07-07 01:00:22 +12:00
|
|
|
self.lstSubPlots.setRootIndex(subplotindex)
|
|
|
|
if self.mdlPlots.rowCount(subplotindex):
|
|
|
|
self.updateSubPlotView()
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2017-12-05 01:27:57 +13:00
|
|
|
self.txtSubPlotSummary.setCurrentModelIndex(QModelIndex())
|
2015-07-06 20:07:05 +12:00
|
|
|
self.lstPlotPerso.selectionModel().clear()
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2015-07-07 01:00:22 +12:00
|
|
|
def updateSubPlotView(self):
|
|
|
|
# Hide columns
|
2017-10-29 22:21:28 +13:00
|
|
|
# FIXME: when columns are hidden, and drag and drop InternalMove is enabled
|
|
|
|
# as well as selectionBehavior=SelectRows, then when moving a row
|
|
|
|
# hidden cells (here: summary and ID) are deleted...
|
|
|
|
# So instead we set their width to 0.
|
|
|
|
#for i in range(self.mdlPlots.columnCount()):
|
|
|
|
#self.lstSubPlots.hideColumn(i)
|
2017-11-16 09:05:48 +13:00
|
|
|
#self.lstSubPlots.showColumn(PlotStep.name)
|
|
|
|
#self.lstSubPlots.showColumn(PlotStep.meta)
|
2017-10-29 22:21:28 +13:00
|
|
|
|
|
|
|
self.lstSubPlots.horizontalHeader().setSectionResizeMode(
|
2017-11-16 09:05:48 +13:00
|
|
|
PlotStep.ID, QHeaderView.Fixed)
|
2017-10-29 22:21:28 +13:00
|
|
|
self.lstSubPlots.horizontalHeader().setSectionResizeMode(
|
2017-11-16 09:05:48 +13:00
|
|
|
PlotStep.summary, QHeaderView.Fixed)
|
2017-10-29 22:21:28 +13:00
|
|
|
self.lstSubPlots.horizontalHeader().resizeSection(
|
2017-11-16 09:05:48 +13:00
|
|
|
PlotStep.ID, 0)
|
2017-10-29 22:21:28 +13:00
|
|
|
self.lstSubPlots.horizontalHeader().resizeSection(
|
2017-11-16 09:05:48 +13:00
|
|
|
PlotStep.summary, 0)
|
2015-07-07 01:00:22 +12:00
|
|
|
|
|
|
|
self.lstSubPlots.horizontalHeader().setSectionResizeMode(
|
2017-11-16 09:05:48 +13:00
|
|
|
PlotStep.name, QHeaderView.Stretch)
|
2015-07-07 01:00:22 +12:00
|
|
|
self.lstSubPlots.horizontalHeader().setSectionResizeMode(
|
2017-11-16 09:05:48 +13:00
|
|
|
PlotStep.meta, QHeaderView.ResizeToContents)
|
2015-07-07 01:00:22 +12:00
|
|
|
self.lstSubPlots.verticalHeader().hide()
|
2016-02-06 00:25:25 +13:00
|
|
|
|
2018-11-05 13:28:51 +13:00
|
|
|
def updatePlotImportance(self, row):
|
|
|
|
imp = self.mdlPlots.getPlotImportanceByRow(row)
|
2017-07-02 04:09:08 +12:00
|
|
|
self.sldPlotImportance.setValue(int(imp))
|
|
|
|
|
2015-06-22 23:11:45 +12:00
|
|
|
def changeCurrentSubPlot(self, index):
|
2017-11-16 09:05:48 +13:00
|
|
|
index = index.sibling(index.row(), PlotStep.summary)
|
2017-12-05 01:27:57 +13:00
|
|
|
self.txtSubPlotSummary.setColumn(PlotStep.summary)
|
|
|
|
self.txtSubPlotSummary.setCurrentModelIndex(index)
|
2016-02-06 00:25:25 +13:00
|
|
|
|
2015-07-06 20:07:05 +12:00
|
|
|
def plotPersoSelectionChanged(self):
|
|
|
|
"Enables or disables remove plot perso button."
|
|
|
|
self.btnRmPlotPerso.setEnabled(
|
2016-02-06 00:25:25 +13:00
|
|
|
len(self.lstPlotPerso.selectedIndexes()) != 0)
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2016-02-06 00:25:25 +13:00
|
|
|
###############################################################################
|
|
|
|
# WORLD
|
|
|
|
###############################################################################
|
2015-07-09 08:06:00 +12:00
|
|
|
|
|
|
|
def changeCurrentWorld(self):
|
|
|
|
index = self.mdlWorld.selectedIndex()
|
|
|
|
|
|
|
|
if not index.isValid():
|
|
|
|
self.tabWorld.setEnabled(False)
|
|
|
|
return
|
|
|
|
|
|
|
|
self.tabWorld.setEnabled(True)
|
|
|
|
self.txtWorldName.setCurrentModelIndex(index)
|
|
|
|
self.txtWorldDescription.setCurrentModelIndex(index)
|
|
|
|
self.txtWorldPassion.setCurrentModelIndex(index)
|
2017-05-08 07:47:31 +12:00
|
|
|
self.txtWorldConflict.setCurrentModelIndex(index)
|
|
|
|
|
2017-10-15 08:39:16 +13:00
|
|
|
###############################################################################
|
|
|
|
# EDITOR
|
|
|
|
###############################################################################
|
|
|
|
|
|
|
|
def openIndex(self, index):
|
|
|
|
self.treeRedacOutline.setCurrentIndex(index)
|
|
|
|
|
2017-11-08 00:02:02 +13:00
|
|
|
def openIndexes(self, indexes, newTab=True):
|
|
|
|
self.mainEditor.openIndexes(indexes, newTab=True)
|
|
|
|
|
2017-12-01 01:12:55 +13:00
|
|
|
# Menu #############################################################
|
2017-11-10 11:01:42 +13:00
|
|
|
|
2017-12-01 01:12:55 +13:00
|
|
|
# Functions called by the menus
|
2017-11-10 11:01:42 +13:00
|
|
|
# self._lastFocus is the last editor that had focus (either treeView or
|
|
|
|
# mainEditor). So we just pass along the signal.
|
|
|
|
|
2017-12-01 01:12:55 +13:00
|
|
|
# Edit
|
|
|
|
|
2017-11-10 11:01:42 +13:00
|
|
|
def documentsCopy(self):
|
2017-11-11 04:26:23 +13:00
|
|
|
"Copy selected item(s)."
|
2017-11-10 11:01:42 +13:00
|
|
|
if self._lastFocus: self._lastFocus.copy()
|
|
|
|
def documentsCut(self):
|
2017-11-11 04:26:23 +13:00
|
|
|
"Cut selected item(s)."
|
2017-11-10 11:01:42 +13:00
|
|
|
if self._lastFocus: self._lastFocus.cut()
|
|
|
|
def documentsPaste(self):
|
2017-11-11 04:26:23 +13:00
|
|
|
"Paste clipboard item(s) into selected item."
|
2017-11-10 11:01:42 +13:00
|
|
|
if self._lastFocus: self._lastFocus.paste()
|
2019-12-22 04:42:49 +13:00
|
|
|
def doSearch(self):
|
|
|
|
"Do a global search."
|
|
|
|
self.dckSearch.show()
|
|
|
|
self.dckSearch.activateWindow()
|
|
|
|
searchTextInput = self.dckSearch.findChild(QLineEdit, 'searchTextInput')
|
|
|
|
searchTextInput.setFocus()
|
|
|
|
searchTextInput.selectAll()
|
2017-11-25 08:50:18 +13:00
|
|
|
def documentsRename(self):
|
|
|
|
"Rename selected item."
|
|
|
|
if self._lastFocus: self._lastFocus.rename()
|
2017-11-10 11:01:42 +13:00
|
|
|
def documentsDuplicate(self):
|
2017-11-11 04:26:23 +13:00
|
|
|
"Duplicate selected item(s)."
|
2017-11-10 11:01:42 +13:00
|
|
|
if self._lastFocus: self._lastFocus.duplicate()
|
|
|
|
def documentsDelete(self):
|
2017-11-11 04:26:23 +13:00
|
|
|
"Delete selected item(s)."
|
2017-11-10 11:01:42 +13:00
|
|
|
if self._lastFocus: self._lastFocus.delete()
|
2017-12-01 01:12:55 +13:00
|
|
|
|
|
|
|
# Formats
|
|
|
|
def callLastMDEditView(self, functionName, param=[]):
|
|
|
|
"""
|
|
|
|
If last focused widget was MDEditView, call the given function.
|
|
|
|
"""
|
|
|
|
if self._lastMDEditView:
|
|
|
|
function = getattr(self._lastMDEditView, functionName)
|
|
|
|
function(*param)
|
|
|
|
def formatSetext1(self): self.callLastMDEditView("titleSetext", [1])
|
|
|
|
def formatSetext2(self): self.callLastMDEditView("titleSetext", [2])
|
|
|
|
def formatAtx1(self): self.callLastMDEditView("titleATX", [1])
|
|
|
|
def formatAtx2(self): self.callLastMDEditView("titleATX", [2])
|
|
|
|
def formatAtx3(self): self.callLastMDEditView("titleATX", [3])
|
|
|
|
def formatAtx4(self): self.callLastMDEditView("titleATX", [4])
|
|
|
|
def formatAtx5(self): self.callLastMDEditView("titleATX", [5])
|
|
|
|
def formatAtx6(self): self.callLastMDEditView("titleATX", [6])
|
|
|
|
def formatBold(self): self.callLastMDEditView("bold")
|
|
|
|
def formatItalic(self): self.callLastMDEditView("italic")
|
|
|
|
def formatStrike(self): self.callLastMDEditView("strike")
|
|
|
|
def formatVerbatim(self): self.callLastMDEditView("verbatim")
|
|
|
|
def formatSuperscript(self): self.callLastMDEditView("superscript")
|
|
|
|
def formatSubscript(self): self.callLastMDEditView("subscript")
|
|
|
|
def formatCommentLines(self): self.callLastMDEditView("commentLine")
|
|
|
|
def formatList(self): self.callLastMDEditView("unorderedList")
|
|
|
|
def formatOrderedList(self): self.callLastMDEditView("orderedList")
|
|
|
|
def formatBlockquote(self): self.callLastMDEditView("blockquote")
|
|
|
|
def formatCommentBlock(self): self.callLastMDEditView("comment")
|
|
|
|
def formatClear(self): self.callLastMDEditView("clearFormat")
|
|
|
|
|
|
|
|
# Organize
|
|
|
|
|
2017-11-10 11:01:42 +13:00
|
|
|
def documentsMoveUp(self):
|
2017-11-11 04:26:23 +13:00
|
|
|
"Move up selected item(s)."
|
2017-11-10 11:01:42 +13:00
|
|
|
if self._lastFocus: self._lastFocus.moveUp()
|
|
|
|
def documentsMoveDown(self):
|
2017-11-11 04:26:23 +13:00
|
|
|
"Move Down selected item(s)."
|
2017-11-10 11:01:42 +13:00
|
|
|
if self._lastFocus: self._lastFocus.moveDown()
|
2017-11-11 04:26:23 +13:00
|
|
|
|
2017-11-10 11:01:42 +13:00
|
|
|
def documentsSplitDialog(self):
|
2017-11-11 04:26:23 +13:00
|
|
|
"Opens a dialog to split selected items."
|
|
|
|
if self._lastFocus: self._lastFocus.splitDialog()
|
|
|
|
# current items or selected items?
|
|
|
|
pass
|
|
|
|
# use outlineBasics, to do that on all selected items.
|
|
|
|
# use editorWidget to do that on selected text.
|
|
|
|
|
2017-11-10 11:01:42 +13:00
|
|
|
def documentsSplitCursor(self):
|
2017-11-11 04:26:23 +13:00
|
|
|
"""
|
|
|
|
Split current item (open in text editor) at cursor position. If there is
|
|
|
|
a text selection, that selection becomes the title of the new scene.
|
|
|
|
"""
|
|
|
|
if self._lastFocus and self._lastFocus == self.mainEditor:
|
|
|
|
self.mainEditor.splitCursor()
|
2017-11-10 11:01:42 +13:00
|
|
|
def documentsMerge(self):
|
2017-11-11 05:21:02 +13:00
|
|
|
"Merges selected item(s)."
|
|
|
|
if self._lastFocus: self._lastFocus.merge()
|
2017-11-10 11:01:42 +13:00
|
|
|
|
2017-10-15 08:39:16 +13:00
|
|
|
###############################################################################
|
|
|
|
# LOAD AND SAVE
|
|
|
|
###############################################################################
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2015-06-24 04:22:39 +12:00
|
|
|
def loadProject(self, project, loadFromFile=True):
|
|
|
|
"""Loads the project ``project``.
|
2015-07-02 20:08:20 +12:00
|
|
|
|
2015-06-24 04:22:39 +12:00
|
|
|
If ``loadFromFile`` is False, then it does not load datas from file.
|
|
|
|
It assumes that the datas have been populated in a different way."""
|
|
|
|
if loadFromFile and not os.path.exists(project):
|
2019-10-15 01:36:06 +13:00
|
|
|
LOGGER.warning("The file {} does not exist. Has it been moved or deleted?".format(project))
|
2017-11-28 03:09:07 +13:00
|
|
|
F.statusMessage(
|
2018-09-18 18:32:10 +12:00
|
|
|
self.tr("The file {} does not exist. Has it been moved or deleted?").format(project), importance=3)
|
2015-06-24 04:22:39 +12:00
|
|
|
return
|
2015-07-02 20:08:20 +12:00
|
|
|
|
2015-06-24 04:22:39 +12:00
|
|
|
if loadFromFile:
|
2015-06-25 20:01:28 +12:00
|
|
|
# Load empty settings
|
2021-04-10 11:19:03 +12:00
|
|
|
importlib.reload(settings)
|
2017-11-14 22:50:32 +13:00
|
|
|
settings.initDefaultValues()
|
2015-07-02 20:08:20 +12:00
|
|
|
|
|
|
|
# Load data
|
2015-06-24 04:22:39 +12:00
|
|
|
self.loadEmptyDatas()
|
2015-07-07 01:00:22 +12:00
|
|
|
self.loadDatas(project)
|
2015-07-02 20:08:20 +12:00
|
|
|
|
2015-06-22 06:00:03 +12:00
|
|
|
self.makeConnections()
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2015-06-17 22:00:03 +12:00
|
|
|
# Load settings
|
2017-11-09 22:40:54 +13:00
|
|
|
if settings.openIndexes and settings.openIndexes != [""]:
|
2016-04-11 03:29:27 +12:00
|
|
|
self.mainEditor.tabSplitter.restoreOpenIndexes(settings.openIndexes)
|
2015-06-16 06:09:16 +12:00
|
|
|
self.generateViewMenu()
|
2015-06-28 00:06:35 +12:00
|
|
|
self.mainEditor.sldCorkSizeFactor.setValue(settings.corkSizeFactor)
|
2015-06-16 06:09:16 +12:00
|
|
|
self.actSpellcheck.setChecked(settings.spellcheck)
|
2015-06-30 00:21:57 +12:00
|
|
|
self.toggleSpellcheck(settings.spellcheck)
|
2015-06-16 06:09:16 +12:00
|
|
|
self.updateMenuDict()
|
|
|
|
self.setDictionary()
|
2015-07-02 20:08:20 +12:00
|
|
|
|
2017-10-24 01:40:55 +13:00
|
|
|
iconSize = settings.viewSettings["Tree"]["iconSize"]
|
|
|
|
self.treeRedacOutline.setIconSize(QSize(iconSize, iconSize))
|
2015-06-28 00:06:35 +12:00
|
|
|
self.mainEditor.setFolderView(settings.folderView)
|
2015-06-30 00:21:57 +12:00
|
|
|
self.mainEditor.updateFolderViewButtons(settings.folderView)
|
2016-04-12 01:14:24 +12:00
|
|
|
self.mainEditor.tabSplitter.updateStyleSheet()
|
2015-06-16 06:30:18 +12:00
|
|
|
self.tabMain.setCurrentIndex(settings.lastTab)
|
2015-06-28 00:06:35 +12:00
|
|
|
self.mainEditor.updateCorkBackground()
|
2016-03-25 01:42:47 +13:00
|
|
|
if settings.viewMode == "simple":
|
|
|
|
self.setViewModeSimple()
|
|
|
|
else:
|
|
|
|
self.setViewModeFiction()
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2015-06-17 22:00:03 +12:00
|
|
|
# Set autosave
|
2015-06-18 04:40:55 +12:00
|
|
|
self.saveTimer = QTimer()
|
|
|
|
self.saveTimer.setInterval(settings.autoSaveDelay * 60 * 1000)
|
|
|
|
self.saveTimer.setSingleShot(False)
|
|
|
|
self.saveTimer.timeout.connect(self.saveDatas)
|
2015-06-17 22:00:03 +12:00
|
|
|
if settings.autoSave:
|
|
|
|
self.saveTimer.start()
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2015-06-18 04:40:55 +12:00
|
|
|
# Set autosave if no changes
|
|
|
|
self.saveTimerNoChanges = QTimer()
|
2016-02-06 00:25:25 +13:00
|
|
|
self.saveTimerNoChanges.setInterval(settings.autoSaveNoChangesDelay * 1000)
|
2015-06-18 04:40:55 +12:00
|
|
|
self.saveTimerNoChanges.setSingleShot(True)
|
2015-06-24 04:22:39 +12:00
|
|
|
self.mdlFlatData.dataChanged.connect(self.startTimerNoChanges)
|
2015-06-18 04:40:55 +12:00
|
|
|
self.mdlOutline.dataChanged.connect(self.startTimerNoChanges)
|
2016-03-04 04:38:38 +13:00
|
|
|
self.mdlCharacter.dataChanged.connect(self.startTimerNoChanges)
|
2015-07-03 03:45:27 +12:00
|
|
|
self.mdlPlots.dataChanged.connect(self.startTimerNoChanges)
|
2015-07-09 08:06:00 +12:00
|
|
|
self.mdlWorld.dataChanged.connect(self.startTimerNoChanges)
|
2015-06-18 06:48:20 +12:00
|
|
|
self.mdlStatus.dataChanged.connect(self.startTimerNoChanges)
|
|
|
|
self.mdlLabels.dataChanged.connect(self.startTimerNoChanges)
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2015-06-18 04:40:55 +12:00
|
|
|
self.saveTimerNoChanges.timeout.connect(self.saveDatas)
|
|
|
|
self.saveTimerNoChanges.stop()
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2015-06-17 22:00:03 +12:00
|
|
|
# UI
|
2017-05-22 05:33:07 +12:00
|
|
|
for i in [self.actOpen, self.menuRecents]:
|
|
|
|
i.setEnabled(False)
|
2015-06-23 10:19:40 +12:00
|
|
|
for i in [self.actSave, self.actSaveAs, self.actCloseProject,
|
2017-11-27 20:05:53 +13:00
|
|
|
self.menuEdit, self.menuView, self.menuOrganize,
|
2017-11-25 06:19:50 +13:00
|
|
|
self.menuTools, self.menuHelp, self.actImport,
|
|
|
|
self.actCompile, self.actSettings]:
|
2015-06-23 10:19:40 +12:00
|
|
|
i.setEnabled(True)
|
2017-12-06 20:55:52 +13:00
|
|
|
# We force to emit even if it opens on the current tab
|
|
|
|
self.tabMain.currentChanged.emit(settings.lastTab)
|
2017-05-23 04:48:20 +12:00
|
|
|
|
Track dirty state and have the UI respect it
Intending to learn more about the way Manuskript goes about saving the
project in order to figure out how to tackle some recent saving-related
issues, I stumbled into learning that Manuskript likes to save data a
whole lot. Too much, in fact. When I close the project with unsaved
changes, I expected those changes to not be saved... but they were. This
completely subverts my expectations of a program using typical
file-based operations involving opening, saving and closing files.
There are three more settings that influence when the program saves, and
I personally consider them a bit overkill or even detrimental to the
stated purpose. What if Manuskript forces a save when nothing was
changed and something goes wrong? Saving too much can in fact be
dangerous!
For now, I have left existing functionality as-is, but I would prefer to
respect the dirty flag I have introduced in this commit for at least the
'save-on-quit' and 'save every X minutes' features. (The third is
smarter and only triggers after noticing changes, so it is less
important.)
Making sure the dirty flag works as expected is the first step in making
such changes in the future.
UI-wise, this commit now offers the user the opportunity to save their
changes, discard them, or outright cancel their action entirely when
performing a destructive action on a dirty project. As of this commit, I
have identified two of such scenarios:
1) closing the project,
2) closing the window with save-on-quit turned off.
If I missed any, do let me know. But for now, maybe now I can finally
start digging into those issues that sent me down this rabbit hole...
2019-04-25 09:02:37 +12:00
|
|
|
# Make sure we can update the window title later.
|
2015-07-07 01:00:22 +12:00
|
|
|
self.currentProject = project
|
Track dirty state and have the UI respect it
Intending to learn more about the way Manuskript goes about saving the
project in order to figure out how to tackle some recent saving-related
issues, I stumbled into learning that Manuskript likes to save data a
whole lot. Too much, in fact. When I close the project with unsaved
changes, I expected those changes to not be saved... but they were. This
completely subverts my expectations of a program using typical
file-based operations involving opening, saving and closing files.
There are three more settings that influence when the program saves, and
I personally consider them a bit overkill or even detrimental to the
stated purpose. What if Manuskript forces a save when nothing was
changed and something goes wrong? Saving too much can in fact be
dangerous!
For now, I have left existing functionality as-is, but I would prefer to
respect the dirty flag I have introduced in this commit for at least the
'save-on-quit' and 'save every X minutes' features. (The third is
smarter and only triggers after noticing changes, so it is less
important.)
Making sure the dirty flag works as expected is the first step in making
such changes in the future.
UI-wise, this commit now offers the user the opportunity to save their
changes, discard them, or outright cancel their action entirely when
performing a destructive action on a dirty project. As of this commit, I
have identified two of such scenarios:
1) closing the project,
2) closing the window with save-on-quit turned off.
If I missed any, do let me know. But for now, maybe now I can finally
start digging into those issues that sent me down this rabbit hole...
2019-04-25 09:02:37 +12:00
|
|
|
self.projectDirty = False
|
2015-07-07 01:00:22 +12:00
|
|
|
QSettings().setValue("lastProject", project)
|
|
|
|
|
2019-01-22 18:37:22 +13:00
|
|
|
item = self.mdlOutline.rootItem
|
|
|
|
wc = item.data(Outline.wordCount)
|
2022-12-11 06:05:41 +13:00
|
|
|
self.sessionStartWordCount = int(wc) if wc != "" else 0
|
2017-05-23 04:48:20 +12:00
|
|
|
# Add project name to Window's name
|
Track dirty state and have the UI respect it
Intending to learn more about the way Manuskript goes about saving the
project in order to figure out how to tackle some recent saving-related
issues, I stumbled into learning that Manuskript likes to save data a
whole lot. Too much, in fact. When I close the project with unsaved
changes, I expected those changes to not be saved... but they were. This
completely subverts my expectations of a program using typical
file-based operations involving opening, saving and closing files.
There are three more settings that influence when the program saves, and
I personally consider them a bit overkill or even detrimental to the
stated purpose. What if Manuskript forces a save when nothing was
changed and something goes wrong? Saving too much can in fact be
dangerous!
For now, I have left existing functionality as-is, but I would prefer to
respect the dirty flag I have introduced in this commit for at least the
'save-on-quit' and 'save every X minutes' features. (The third is
smarter and only triggers after noticing changes, so it is less
important.)
Making sure the dirty flag works as expected is the first step in making
such changes in the future.
UI-wise, this commit now offers the user the opportunity to save their
changes, discard them, or outright cancel their action entirely when
performing a destructive action on a dirty project. As of this commit, I
have identified two of such scenarios:
1) closing the project,
2) closing the window with save-on-quit turned off.
If I missed any, do let me know. But for now, maybe now I can finally
start digging into those issues that sent me down this rabbit hole...
2019-04-25 09:02:37 +12:00
|
|
|
self.setWindowTitle(self.projectName() + " - " + self.tr("Manuskript"))
|
2019-01-22 18:37:22 +13:00
|
|
|
|
2015-06-24 04:22:39 +12:00
|
|
|
# Show main Window
|
2017-10-20 09:21:15 +13:00
|
|
|
self.switchToProject()
|
2015-07-02 20:08:20 +12:00
|
|
|
|
Track dirty state and have the UI respect it
Intending to learn more about the way Manuskript goes about saving the
project in order to figure out how to tackle some recent saving-related
issues, I stumbled into learning that Manuskript likes to save data a
whole lot. Too much, in fact. When I close the project with unsaved
changes, I expected those changes to not be saved... but they were. This
completely subverts my expectations of a program using typical
file-based operations involving opening, saving and closing files.
There are three more settings that influence when the program saves, and
I personally consider them a bit overkill or even detrimental to the
stated purpose. What if Manuskript forces a save when nothing was
changed and something goes wrong? Saving too much can in fact be
dangerous!
For now, I have left existing functionality as-is, but I would prefer to
respect the dirty flag I have introduced in this commit for at least the
'save-on-quit' and 'save every X minutes' features. (The third is
smarter and only triggers after noticing changes, so it is less
important.)
Making sure the dirty flag works as expected is the first step in making
such changes in the future.
UI-wise, this commit now offers the user the opportunity to save their
changes, discard them, or outright cancel their action entirely when
performing a destructive action on a dirty project. As of this commit, I
have identified two of such scenarios:
1) closing the project,
2) closing the window with save-on-quit turned off.
If I missed any, do let me know. But for now, maybe now I can finally
start digging into those issues that sent me down this rabbit hole...
2019-04-25 09:02:37 +12:00
|
|
|
def handleUnsavedChanges(self):
|
|
|
|
"""
|
|
|
|
There may be some currently unsaved changes, but the action the user triggered
|
|
|
|
will result in the project or application being closed. To save, or not to save?
|
|
|
|
|
|
|
|
Or just bail out entirely?
|
|
|
|
|
|
|
|
Sometimes it is best to just ask.
|
|
|
|
"""
|
|
|
|
|
|
|
|
if not self.projectDirty:
|
|
|
|
return True # no unsaved changes, all is good
|
|
|
|
|
|
|
|
msg = QMessageBox(QMessageBox.Question,
|
|
|
|
self.tr("Save project?"),
|
|
|
|
"<p><b>" +
|
|
|
|
self.tr("Save changes to project \"{}\" before closing?").format(self.projectName()) +
|
|
|
|
"</b></p>" +
|
|
|
|
"<p>" +
|
|
|
|
self.tr("Your changes will be lost if you don't save them.") +
|
|
|
|
"</p>",
|
|
|
|
QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel)
|
|
|
|
|
|
|
|
ret = msg.exec()
|
|
|
|
|
|
|
|
if ret == QMessageBox.Cancel:
|
|
|
|
return False # the situation has not been handled, cancel action
|
|
|
|
|
|
|
|
if ret == QMessageBox.Save:
|
|
|
|
self.saveDatas()
|
|
|
|
|
|
|
|
return True # the situation has been handled
|
|
|
|
|
|
|
|
|
2015-06-24 04:22:39 +12:00
|
|
|
def closeProject(self):
|
2016-03-30 20:43:18 +13:00
|
|
|
|
2016-03-31 21:50:20 +13:00
|
|
|
if not self.currentProject:
|
|
|
|
return
|
|
|
|
|
Track dirty state and have the UI respect it
Intending to learn more about the way Manuskript goes about saving the
project in order to figure out how to tackle some recent saving-related
issues, I stumbled into learning that Manuskript likes to save data a
whole lot. Too much, in fact. When I close the project with unsaved
changes, I expected those changes to not be saved... but they were. This
completely subverts my expectations of a program using typical
file-based operations involving opening, saving and closing files.
There are three more settings that influence when the program saves, and
I personally consider them a bit overkill or even detrimental to the
stated purpose. What if Manuskript forces a save when nothing was
changed and something goes wrong? Saving too much can in fact be
dangerous!
For now, I have left existing functionality as-is, but I would prefer to
respect the dirty flag I have introduced in this commit for at least the
'save-on-quit' and 'save every X minutes' features. (The third is
smarter and only triggers after noticing changes, so it is less
important.)
Making sure the dirty flag works as expected is the first step in making
such changes in the future.
UI-wise, this commit now offers the user the opportunity to save their
changes, discard them, or outright cancel their action entirely when
performing a destructive action on a dirty project. As of this commit, I
have identified two of such scenarios:
1) closing the project,
2) closing the window with save-on-quit turned off.
If I missed any, do let me know. But for now, maybe now I can finally
start digging into those issues that sent me down this rabbit hole...
2019-04-25 09:02:37 +12:00
|
|
|
# Make sure data is saved.
|
Do not prompt "Save project?" when _Save on quit_ setting enabled
See PR #615
The Travis CI tests began failing after merging Pull Request #583.
Log snippet:
----------------------------------------------------------------------
...
Ref not implemented
PASSED
manuskript/tests/ui/test_welcome.py::test_autoLoad QXcbConnection: XCB error: 8 (BadMatch), sequence: 613, resource id: 2097162, major code: 42 (SetInputFocus), minor code: 0
QXcbConnection: XCB error: 8 (BadMatch), sequence: 619, resource id: 2097168, major code: 42 (SetInputFocus), minor code: 0
QXcbConnection: XCB error: 8 (BadMatch), sequence: 625, resource id: 2097171, major code: 42 (SetInputFocus), minor code: 0
----------------------------------------------------------------------
When running "pytest -vs" locally, which is a command used in our
.travis.yml file, a dialog to "Save project?" is displayed. Because
the test scripts use the "saveOnQuit" default setting of *enabled*,
the "Save project?" dialog should not be displayed.
In other words when a call is made to close the project, a "Save
project?" dialog is incorrectly displayed because the dirtyProject
flag is set, but so too is saveOnQuit set to True. What should happen
is an automatic save with no prompt. This PR fixes this logic so that
the Travis CI test suite completes successfully.
2019-08-15 05:36:42 +12:00
|
|
|
if (self.projectDirty and settings.saveOnQuit == True):
|
|
|
|
self.saveDatas()
|
|
|
|
elif not self.handleUnsavedChanges():
|
|
|
|
return # user cancelled action
|
Track dirty state and have the UI respect it
Intending to learn more about the way Manuskript goes about saving the
project in order to figure out how to tackle some recent saving-related
issues, I stumbled into learning that Manuskript likes to save data a
whole lot. Too much, in fact. When I close the project with unsaved
changes, I expected those changes to not be saved... but they were. This
completely subverts my expectations of a program using typical
file-based operations involving opening, saving and closing files.
There are three more settings that influence when the program saves, and
I personally consider them a bit overkill or even detrimental to the
stated purpose. What if Manuskript forces a save when nothing was
changed and something goes wrong? Saving too much can in fact be
dangerous!
For now, I have left existing functionality as-is, but I would prefer to
respect the dirty flag I have introduced in this commit for at least the
'save-on-quit' and 'save every X minutes' features. (The third is
smarter and only triggers after noticing changes, so it is less
important.)
Making sure the dirty flag works as expected is the first step in making
such changes in the future.
UI-wise, this commit now offers the user the opportunity to save their
changes, discard them, or outright cancel their action entirely when
performing a destructive action on a dirty project. As of this commit, I
have identified two of such scenarios:
1) closing the project,
2) closing the window with save-on-quit turned off.
If I missed any, do let me know. But for now, maybe now I can finally
start digging into those issues that sent me down this rabbit hole...
2019-04-25 09:02:37 +12:00
|
|
|
|
2016-03-30 20:43:18 +13:00
|
|
|
# Close open tabs in editor
|
|
|
|
self.mainEditor.closeAllTabs()
|
|
|
|
|
2015-06-24 04:22:39 +12:00
|
|
|
self.currentProject = None
|
Track dirty state and have the UI respect it
Intending to learn more about the way Manuskript goes about saving the
project in order to figure out how to tackle some recent saving-related
issues, I stumbled into learning that Manuskript likes to save data a
whole lot. Too much, in fact. When I close the project with unsaved
changes, I expected those changes to not be saved... but they were. This
completely subverts my expectations of a program using typical
file-based operations involving opening, saving and closing files.
There are three more settings that influence when the program saves, and
I personally consider them a bit overkill or even detrimental to the
stated purpose. What if Manuskript forces a save when nothing was
changed and something goes wrong? Saving too much can in fact be
dangerous!
For now, I have left existing functionality as-is, but I would prefer to
respect the dirty flag I have introduced in this commit for at least the
'save-on-quit' and 'save every X minutes' features. (The third is
smarter and only triggers after noticing changes, so it is less
important.)
Making sure the dirty flag works as expected is the first step in making
such changes in the future.
UI-wise, this commit now offers the user the opportunity to save their
changes, discard them, or outright cancel their action entirely when
performing a destructive action on a dirty project. As of this commit, I
have identified two of such scenarios:
1) closing the project,
2) closing the window with save-on-quit turned off.
If I missed any, do let me know. But for now, maybe now I can finally
start digging into those issues that sent me down this rabbit hole...
2019-04-25 09:02:37 +12:00
|
|
|
self.projectDirty = None
|
2015-06-24 04:22:39 +12:00
|
|
|
QSettings().setValue("lastProject", "")
|
2015-07-02 20:08:20 +12:00
|
|
|
|
2015-06-24 04:22:39 +12:00
|
|
|
# Clear datas
|
|
|
|
self.loadEmptyDatas()
|
|
|
|
self.saveTimer.stop()
|
2019-08-07 10:44:41 +12:00
|
|
|
self.saveTimerNoChanges.stop()
|
2016-03-12 03:45:51 +13:00
|
|
|
loadSave.clearSaveCache()
|
2015-07-02 20:08:20 +12:00
|
|
|
|
2017-05-22 05:33:07 +12:00
|
|
|
self.breakConnections()
|
|
|
|
|
2015-06-24 04:22:39 +12:00
|
|
|
# UI
|
2017-05-22 05:33:07 +12:00
|
|
|
for i in [self.actOpen, self.menuRecents]:
|
|
|
|
i.setEnabled(True)
|
2015-06-24 04:22:39 +12:00
|
|
|
for i in [self.actSave, self.actSaveAs, self.actCloseProject,
|
2017-11-27 20:05:53 +13:00
|
|
|
self.menuEdit, self.menuView, self.menuOrganize,
|
2017-11-25 06:19:50 +13:00
|
|
|
self.menuTools, self.menuHelp, self.actImport,
|
|
|
|
self.actCompile, self.actSettings]:
|
2015-06-24 04:22:39 +12:00
|
|
|
i.setEnabled(False)
|
2015-07-02 20:08:20 +12:00
|
|
|
|
2017-05-23 04:48:20 +12:00
|
|
|
# Set Window's name - no project loaded
|
|
|
|
self.setWindowTitle(self.tr("Manuskript"))
|
|
|
|
|
2015-06-24 04:22:39 +12:00
|
|
|
# Reload recent files
|
|
|
|
self.welcome.updateValues()
|
2015-07-02 20:08:20 +12:00
|
|
|
|
2015-06-24 04:22:39 +12:00
|
|
|
# Show welcome dialog
|
2017-10-20 09:21:15 +13:00
|
|
|
self.switchToWelcome()
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2015-06-24 04:22:39 +12:00
|
|
|
def readSettings(self):
|
|
|
|
# Load State and geometry
|
|
|
|
sttgns = QSettings(qApp.organizationName(), qApp.applicationName())
|
|
|
|
if sttgns.contains("geometry"):
|
|
|
|
self.restoreGeometry(sttgns.value("geometry"))
|
|
|
|
if sttgns.contains("windowState"):
|
|
|
|
self.restoreState(sttgns.value("windowState"))
|
2017-10-20 09:21:15 +13:00
|
|
|
|
|
|
|
if sttgns.contains("docks"):
|
|
|
|
self._dckVisibility = {}
|
|
|
|
vals = sttgns.value("docks")
|
|
|
|
for name in vals:
|
|
|
|
self._dckVisibility[name] = vals[name]
|
2016-02-09 03:11:49 +13:00
|
|
|
else:
|
2017-10-20 09:21:15 +13:00
|
|
|
# Create default settings
|
|
|
|
self._dckVisibility = {
|
|
|
|
self.dckNavigation.objectName() : True,
|
|
|
|
self.dckCheatSheet.objectName() : False,
|
|
|
|
self.dckSearch.objectName() : False,
|
|
|
|
}
|
2018-01-07 06:48:40 +13:00
|
|
|
self._dckVisibility["LOCK"] = True # prevent overriding loaded values
|
2017-10-20 09:21:15 +13:00
|
|
|
|
2015-07-04 19:59:35 +12:00
|
|
|
if sttgns.contains("metadataState"):
|
|
|
|
state = [False if v == "false" else True for v in sttgns.value("metadataState")]
|
|
|
|
self.redacMetadata.restoreState(state)
|
2015-07-04 20:54:06 +12:00
|
|
|
if sttgns.contains("revisionsState"):
|
|
|
|
state = [False if v == "false" else True for v in sttgns.value("revisionsState")]
|
|
|
|
self.redacMetadata.revisions.restoreState(state)
|
2016-02-29 01:54:11 +13:00
|
|
|
if sttgns.contains("splitterRedacH"):
|
|
|
|
self.splitterRedacH.restoreState(sttgns.value("splitterRedacH"))
|
|
|
|
if sttgns.contains("splitterRedacV"):
|
|
|
|
self.splitterRedacV.restoreState(sttgns.value("splitterRedacV"))
|
|
|
|
if sttgns.contains("toolbar"):
|
2018-01-07 06:48:40 +13:00
|
|
|
# self.toolbar is not initialized yet, so we just store value
|
2016-02-29 01:54:11 +13:00
|
|
|
self._toolbarState = sttgns.value("toolbar")
|
|
|
|
else:
|
|
|
|
self._toolbarState = ""
|
|
|
|
|
2015-06-24 04:22:39 +12:00
|
|
|
def closeEvent(self, event):
|
|
|
|
# Specific settings to save before quitting
|
|
|
|
settings.lastTab = self.tabMain.currentIndex()
|
2015-07-02 20:08:20 +12:00
|
|
|
|
2015-06-24 04:22:39 +12:00
|
|
|
if self.currentProject:
|
2016-03-10 01:19:03 +13:00
|
|
|
# Remembering the current items (stores outlineItem's ID)
|
2016-04-11 03:29:27 +12:00
|
|
|
settings.openIndexes = self.mainEditor.tabSplitter.openIndexes()
|
2015-06-24 04:22:39 +12:00
|
|
|
|
2021-04-10 02:03:31 +12:00
|
|
|
# Call close on the main window to clean children widgets
|
|
|
|
if self.mainEditor:
|
|
|
|
self.mainEditor.close()
|
|
|
|
|
Track dirty state and have the UI respect it
Intending to learn more about the way Manuskript goes about saving the
project in order to figure out how to tackle some recent saving-related
issues, I stumbled into learning that Manuskript likes to save data a
whole lot. Too much, in fact. When I close the project with unsaved
changes, I expected those changes to not be saved... but they were. This
completely subverts my expectations of a program using typical
file-based operations involving opening, saving and closing files.
There are three more settings that influence when the program saves, and
I personally consider them a bit overkill or even detrimental to the
stated purpose. What if Manuskript forces a save when nothing was
changed and something goes wrong? Saving too much can in fact be
dangerous!
For now, I have left existing functionality as-is, but I would prefer to
respect the dirty flag I have introduced in this commit for at least the
'save-on-quit' and 'save every X minutes' features. (The third is
smarter and only triggers after noticing changes, so it is less
important.)
Making sure the dirty flag works as expected is the first step in making
such changes in the future.
UI-wise, this commit now offers the user the opportunity to save their
changes, discard them, or outright cancel their action entirely when
performing a destructive action on a dirty project. As of this commit, I
have identified two of such scenarios:
1) closing the project,
2) closing the window with save-on-quit turned off.
If I missed any, do let me know. But for now, maybe now I can finally
start digging into those issues that sent me down this rabbit hole...
2019-04-25 09:02:37 +12:00
|
|
|
# Save data from models
|
|
|
|
if settings.saveOnQuit:
|
|
|
|
self.saveDatas()
|
|
|
|
elif not self.handleUnsavedChanges():
|
|
|
|
event.ignore() # user opted to cancel the close action
|
2015-06-24 04:22:39 +12:00
|
|
|
|
2016-02-06 00:25:25 +13:00
|
|
|
# closeEvent
|
2018-01-07 06:48:40 +13:00
|
|
|
# QMainWindow.closeEvent(self, event) # Causing segfaults?
|
2015-06-24 04:22:39 +12:00
|
|
|
|
2019-01-22 18:37:22 +13:00
|
|
|
# Close non-modal windows if they are open.
|
|
|
|
if self.td:
|
|
|
|
self.td.close()
|
|
|
|
if self.fw:
|
|
|
|
self.fw.close()
|
|
|
|
|
Track dirty state and have the UI respect it
Intending to learn more about the way Manuskript goes about saving the
project in order to figure out how to tackle some recent saving-related
issues, I stumbled into learning that Manuskript likes to save data a
whole lot. Too much, in fact. When I close the project with unsaved
changes, I expected those changes to not be saved... but they were. This
completely subverts my expectations of a program using typical
file-based operations involving opening, saving and closing files.
There are three more settings that influence when the program saves, and
I personally consider them a bit overkill or even detrimental to the
stated purpose. What if Manuskript forces a save when nothing was
changed and something goes wrong? Saving too much can in fact be
dangerous!
For now, I have left existing functionality as-is, but I would prefer to
respect the dirty flag I have introduced in this commit for at least the
'save-on-quit' and 'save every X minutes' features. (The third is
smarter and only triggers after noticing changes, so it is less
important.)
Making sure the dirty flag works as expected is the first step in making
such changes in the future.
UI-wise, this commit now offers the user the opportunity to save their
changes, discard them, or outright cancel their action entirely when
performing a destructive action on a dirty project. As of this commit, I
have identified two of such scenarios:
1) closing the project,
2) closing the window with save-on-quit turned off.
If I missed any, do let me know. But for now, maybe now I can finally
start digging into those issues that sent me down this rabbit hole...
2019-04-25 09:02:37 +12:00
|
|
|
# User may have canceled close event, so make sure we indeed want to close.
|
|
|
|
# This is necessary because self.updateDockVisibility() hides UI elements.
|
|
|
|
if event.isAccepted():
|
|
|
|
# Save State and geometry and other things
|
|
|
|
appSettings = QSettings(qApp.organizationName(), qApp.applicationName())
|
|
|
|
appSettings.setValue("geometry", self.saveGeometry())
|
|
|
|
appSettings.setValue("windowState", self.saveState())
|
|
|
|
appSettings.setValue("metadataState", self.redacMetadata.saveState())
|
|
|
|
appSettings.setValue("revisionsState", self.redacMetadata.revisions.saveState())
|
|
|
|
appSettings.setValue("splitterRedacH", self.splitterRedacH.saveState())
|
|
|
|
appSettings.setValue("splitterRedacV", self.splitterRedacV.saveState())
|
|
|
|
appSettings.setValue("toolbar", self.toolbar.saveState())
|
|
|
|
|
|
|
|
# If we are not in the welcome window, we update the visibility
|
|
|
|
# of the docks widgets
|
|
|
|
if self.stack.currentIndex() == 1:
|
|
|
|
self.updateDockVisibility()
|
|
|
|
|
|
|
|
# Storing the visibility of docks to restore it on restart
|
|
|
|
appSettings.setValue("docks", self._dckVisibility)
|
|
|
|
|
2015-06-24 04:22:39 +12:00
|
|
|
def startTimerNoChanges(self):
|
Track dirty state and have the UI respect it
Intending to learn more about the way Manuskript goes about saving the
project in order to figure out how to tackle some recent saving-related
issues, I stumbled into learning that Manuskript likes to save data a
whole lot. Too much, in fact. When I close the project with unsaved
changes, I expected those changes to not be saved... but they were. This
completely subverts my expectations of a program using typical
file-based operations involving opening, saving and closing files.
There are three more settings that influence when the program saves, and
I personally consider them a bit overkill or even detrimental to the
stated purpose. What if Manuskript forces a save when nothing was
changed and something goes wrong? Saving too much can in fact be
dangerous!
For now, I have left existing functionality as-is, but I would prefer to
respect the dirty flag I have introduced in this commit for at least the
'save-on-quit' and 'save every X minutes' features. (The third is
smarter and only triggers after noticing changes, so it is less
important.)
Making sure the dirty flag works as expected is the first step in making
such changes in the future.
UI-wise, this commit now offers the user the opportunity to save their
changes, discard them, or outright cancel their action entirely when
performing a destructive action on a dirty project. As of this commit, I
have identified two of such scenarios:
1) closing the project,
2) closing the window with save-on-quit turned off.
If I missed any, do let me know. But for now, maybe now I can finally
start digging into those issues that sent me down this rabbit hole...
2019-04-25 09:02:37 +12:00
|
|
|
"""
|
|
|
|
Something changed in the project that requires auto-saving.
|
|
|
|
"""
|
|
|
|
self.projectDirty = True
|
|
|
|
|
2015-06-24 04:22:39 +12:00
|
|
|
if settings.autoSaveNoChanges:
|
|
|
|
self.saveTimerNoChanges.start()
|
|
|
|
|
|
|
|
def saveDatas(self, projectName=None):
|
|
|
|
"""Saves the current project (in self.currentProject).
|
2015-07-02 20:08:20 +12:00
|
|
|
|
2015-06-24 04:22:39 +12:00
|
|
|
If ``projectName`` is given, currentProject becomes projectName.
|
|
|
|
In other words, it "saves as...".
|
|
|
|
"""
|
2015-07-02 20:08:20 +12:00
|
|
|
|
2015-06-24 04:22:39 +12:00
|
|
|
if projectName:
|
|
|
|
self.currentProject = projectName
|
|
|
|
QSettings().setValue("lastProject", projectName)
|
2015-07-02 20:08:20 +12:00
|
|
|
|
Track dirty state and have the UI respect it
Intending to learn more about the way Manuskript goes about saving the
project in order to figure out how to tackle some recent saving-related
issues, I stumbled into learning that Manuskript likes to save data a
whole lot. Too much, in fact. When I close the project with unsaved
changes, I expected those changes to not be saved... but they were. This
completely subverts my expectations of a program using typical
file-based operations involving opening, saving and closing files.
There are three more settings that influence when the program saves, and
I personally consider them a bit overkill or even detrimental to the
stated purpose. What if Manuskript forces a save when nothing was
changed and something goes wrong? Saving too much can in fact be
dangerous!
For now, I have left existing functionality as-is, but I would prefer to
respect the dirty flag I have introduced in this commit for at least the
'save-on-quit' and 'save every X minutes' features. (The third is
smarter and only triggers after noticing changes, so it is less
important.)
Making sure the dirty flag works as expected is the first step in making
such changes in the future.
UI-wise, this commit now offers the user the opportunity to save their
changes, discard them, or outright cancel their action entirely when
performing a destructive action on a dirty project. As of this commit, I
have identified two of such scenarios:
1) closing the project,
2) closing the window with save-on-quit turned off.
If I missed any, do let me know. But for now, maybe now I can finally
start digging into those issues that sent me down this rabbit hole...
2019-04-25 09:02:37 +12:00
|
|
|
# Stop the timer before saving: if auto-saving fails (bugs out?) we don't want it
|
|
|
|
# to keep trying and continuously hitting the failure condition. Nor do we want to
|
|
|
|
# risk a scenario where the timer somehow triggers a new save while saving.
|
2016-03-10 01:19:03 +13:00
|
|
|
self.saveTimerNoChanges.stop()
|
2017-10-24 01:40:55 +13:00
|
|
|
|
2023-02-10 10:13:01 +13:00
|
|
|
if self.currentProject is None:
|
2019-08-07 10:44:41 +12:00
|
|
|
# No UI feedback here as this code path indicates a race condition that happens
|
|
|
|
# after the user has already closed the project through some way. But in that
|
|
|
|
# scenario, this code should not be reachable to begin with.
|
2019-10-15 01:36:06 +13:00
|
|
|
LOGGER.error("There is no current project to save.")
|
2019-08-07 10:44:41 +12:00
|
|
|
return
|
|
|
|
|
Track dirty state and have the UI respect it
Intending to learn more about the way Manuskript goes about saving the
project in order to figure out how to tackle some recent saving-related
issues, I stumbled into learning that Manuskript likes to save data a
whole lot. Too much, in fact. When I close the project with unsaved
changes, I expected those changes to not be saved... but they were. This
completely subverts my expectations of a program using typical
file-based operations involving opening, saving and closing files.
There are three more settings that influence when the program saves, and
I personally consider them a bit overkill or even detrimental to the
stated purpose. What if Manuskript forces a save when nothing was
changed and something goes wrong? Saving too much can in fact be
dangerous!
For now, I have left existing functionality as-is, but I would prefer to
respect the dirty flag I have introduced in this commit for at least the
'save-on-quit' and 'save every X minutes' features. (The third is
smarter and only triggers after noticing changes, so it is less
important.)
Making sure the dirty flag works as expected is the first step in making
such changes in the future.
UI-wise, this commit now offers the user the opportunity to save their
changes, discard them, or outright cancel their action entirely when
performing a destructive action on a dirty project. As of this commit, I
have identified two of such scenarios:
1) closing the project,
2) closing the window with save-on-quit turned off.
If I missed any, do let me know. But for now, maybe now I can finally
start digging into those issues that sent me down this rabbit hole...
2019-04-25 09:02:37 +12:00
|
|
|
r = loadSave.saveProject() # version=0
|
|
|
|
|
2017-12-08 22:01:58 +13:00
|
|
|
projectName = os.path.basename(self.currentProject)
|
2017-10-14 23:17:03 +13:00
|
|
|
if r:
|
Track dirty state and have the UI respect it
Intending to learn more about the way Manuskript goes about saving the
project in order to figure out how to tackle some recent saving-related
issues, I stumbled into learning that Manuskript likes to save data a
whole lot. Too much, in fact. When I close the project with unsaved
changes, I expected those changes to not be saved... but they were. This
completely subverts my expectations of a program using typical
file-based operations involving opening, saving and closing files.
There are three more settings that influence when the program saves, and
I personally consider them a bit overkill or even detrimental to the
stated purpose. What if Manuskript forces a save when nothing was
changed and something goes wrong? Saving too much can in fact be
dangerous!
For now, I have left existing functionality as-is, but I would prefer to
respect the dirty flag I have introduced in this commit for at least the
'save-on-quit' and 'save every X minutes' features. (The third is
smarter and only triggers after noticing changes, so it is less
important.)
Making sure the dirty flag works as expected is the first step in making
such changes in the future.
UI-wise, this commit now offers the user the opportunity to save their
changes, discard them, or outright cancel their action entirely when
performing a destructive action on a dirty project. As of this commit, I
have identified two of such scenarios:
1) closing the project,
2) closing the window with save-on-quit turned off.
If I missed any, do let me know. But for now, maybe now I can finally
start digging into those issues that sent me down this rabbit hole...
2019-04-25 09:02:37 +12:00
|
|
|
self.projectDirty = False # successful save, clear dirty flag
|
|
|
|
|
2017-12-08 22:01:58 +13:00
|
|
|
feedback = self.tr("Project {} saved.").format(projectName)
|
|
|
|
F.statusMessage(feedback, importance=0)
|
2019-10-15 01:36:06 +13:00
|
|
|
LOGGER.info("Project {} saved.".format(projectName))
|
2017-10-14 23:17:03 +13:00
|
|
|
else:
|
2017-12-08 22:01:58 +13:00
|
|
|
feedback = self.tr("WARNING: Project {} not saved.").format(projectName)
|
|
|
|
F.statusMessage(feedback, importance=3)
|
2019-10-15 01:36:06 +13:00
|
|
|
LOGGER.warning("Project {} not saved.".format(projectName))
|
2015-06-24 04:22:39 +12:00
|
|
|
|
|
|
|
def loadEmptyDatas(self):
|
2015-06-25 06:32:50 +12:00
|
|
|
self.mdlFlatData = QStandardItemModel(self)
|
2016-03-04 04:38:38 +13:00
|
|
|
self.mdlCharacter = characterModel(self)
|
2015-06-25 06:32:50 +12:00
|
|
|
self.mdlLabels = QStandardItemModel(self)
|
|
|
|
self.mdlStatus = QStandardItemModel(self)
|
|
|
|
self.mdlPlots = plotModel(self)
|
|
|
|
self.mdlOutline = outlineModel(self)
|
2015-07-09 08:06:00 +12:00
|
|
|
self.mdlWorld = worldModel(self)
|
2015-06-24 04:22:39 +12:00
|
|
|
|
2015-07-07 01:00:22 +12:00
|
|
|
def loadDatas(self, project):
|
2015-06-24 04:22:39 +12:00
|
|
|
|
2016-03-12 03:45:51 +13:00
|
|
|
errors = loadSave.loadProject(project)
|
2015-07-02 20:08:20 +12:00
|
|
|
|
2015-06-24 04:22:39 +12:00
|
|
|
# Giving some feedback
|
|
|
|
if not errors:
|
2019-10-15 01:36:06 +13:00
|
|
|
LOGGER.info("Project {} loaded.".format(project))
|
2017-11-28 03:09:07 +13:00
|
|
|
F.statusMessage(
|
2017-12-08 22:01:58 +13:00
|
|
|
self.tr("Project {} loaded.").format(project), 2000)
|
2015-06-24 04:22:39 +12:00
|
|
|
else:
|
2019-10-15 01:36:06 +13:00
|
|
|
LOGGER.error("Project {} loaded with some errors:".format(project))
|
2015-06-24 04:22:39 +12:00
|
|
|
for e in errors:
|
2019-10-15 01:36:06 +13:00
|
|
|
LOGGER.error(" * {} wasn't found in project file.".format(e))
|
2017-11-28 03:09:07 +13:00
|
|
|
F.statusMessage(
|
2017-12-08 22:01:58 +13:00
|
|
|
self.tr("Project {} loaded with some errors.").format(project), 5000, importance = 3)
|
2015-06-24 04:22:39 +12:00
|
|
|
|
2016-02-06 00:25:25 +13:00
|
|
|
###############################################################################
|
|
|
|
# MAIN CONNECTIONS
|
|
|
|
###############################################################################
|
2015-06-22 06:00:03 +12:00
|
|
|
|
2015-06-25 01:43:35 +12:00
|
|
|
def makeUIConnections(self):
|
2016-03-24 23:17:48 +13:00
|
|
|
"Connections that have to be made once only, even when a new project is loaded."
|
2023-03-06 02:14:58 +13:00
|
|
|
self.lstCharacters.itemSelectionChanged.connect(self.handleCharacterSelectionChanged, F.AUC)
|
2015-07-02 20:08:20 +12:00
|
|
|
|
2017-11-21 03:42:30 +13:00
|
|
|
self.txtPlotFilter.textChanged.connect(self.lstPlots.setFilter, F.AUC)
|
|
|
|
self.lstPlots.currentItemChanged.connect(self.changeCurrentPlot, F.AUC)
|
|
|
|
self.lstSubPlots.clicked.connect(self.changeCurrentSubPlot, F.AUC)
|
2015-07-02 20:08:20 +12:00
|
|
|
|
2017-11-21 03:42:30 +13:00
|
|
|
self.btnRedacAddFolder.clicked.connect(self.treeRedacOutline.addFolder, F.AUC)
|
|
|
|
self.btnOutlineAddFolder.clicked.connect(self.treeOutlineOutline.addFolder, F.AUC)
|
|
|
|
self.btnRedacAddText.clicked.connect(self.treeRedacOutline.addText, F.AUC)
|
|
|
|
self.btnOutlineAddText.clicked.connect(self.treeOutlineOutline.addText, F.AUC)
|
|
|
|
self.btnRedacRemoveItem.clicked.connect(self.outlineRemoveItemsRedac, F.AUC)
|
|
|
|
self.btnOutlineRemoveItem.clicked.connect(self.outlineRemoveItemsOutline, F.AUC)
|
2015-07-02 20:08:20 +12:00
|
|
|
|
2015-07-10 01:01:07 +12:00
|
|
|
self.tabMain.currentChanged.connect(self.toolbar.setCurrentGroup)
|
2017-11-10 11:01:42 +13:00
|
|
|
self.tabMain.currentChanged.connect(self.tabMainChanged)
|
|
|
|
|
|
|
|
qApp.focusChanged.connect(self.focusChanged)
|
2015-06-25 01:43:35 +12:00
|
|
|
|
2015-06-22 06:00:03 +12:00
|
|
|
def makeConnections(self):
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2015-06-22 06:00:03 +12:00
|
|
|
# Flat datas (Summary and general infos)
|
|
|
|
for widget, col in [
|
|
|
|
(self.txtSummarySituation, 0),
|
2016-03-02 11:41:55 +13:00
|
|
|
(self.txtSummarySentence, 1),
|
|
|
|
(self.txtSummarySentence_2, 1),
|
2015-06-22 06:00:03 +12:00
|
|
|
(self.txtSummaryPara, 2),
|
|
|
|
(self.txtSummaryPara_2, 2),
|
|
|
|
(self.txtPlotSummaryPara, 2),
|
|
|
|
(self.txtSummaryPage, 3),
|
|
|
|
(self.txtSummaryPage_2, 3),
|
|
|
|
(self.txtPlotSummaryPage, 3),
|
|
|
|
(self.txtSummaryFull, 4),
|
|
|
|
(self.txtPlotSummaryFull, 4),
|
2016-02-06 00:25:25 +13:00
|
|
|
]:
|
2015-06-22 06:00:03 +12:00
|
|
|
widget.setModel(self.mdlFlatData)
|
|
|
|
widget.setColumn(col)
|
|
|
|
widget.setCurrentModelIndex(self.mdlFlatData.index(1, col))
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2015-06-22 06:00:03 +12:00
|
|
|
for widget, col in [
|
|
|
|
(self.txtGeneralTitle, 0),
|
|
|
|
(self.txtGeneralSubtitle, 1),
|
|
|
|
(self.txtGeneralSerie, 2),
|
|
|
|
(self.txtGeneralVolume, 3),
|
|
|
|
(self.txtGeneralGenre, 4),
|
|
|
|
(self.txtGeneralLicense, 5),
|
|
|
|
(self.txtGeneralAuthor, 6),
|
|
|
|
(self.txtGeneralEmail, 7),
|
2016-02-06 00:25:25 +13:00
|
|
|
]:
|
2015-06-22 06:00:03 +12:00
|
|
|
widget.setModel(self.mdlFlatData)
|
|
|
|
widget.setColumn(col)
|
|
|
|
widget.setCurrentModelIndex(self.mdlFlatData.index(0, col))
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2016-03-24 23:17:48 +13:00
|
|
|
# Characters
|
2023-03-16 09:06:31 +13:00
|
|
|
self.updatePersoInfoView(self.tblPersoInfos)
|
2016-03-04 04:38:38 +13:00
|
|
|
self.lstCharacters.setCharactersModel(self.mdlCharacter)
|
|
|
|
self.tblPersoInfos.setModel(self.mdlCharacter)
|
2016-03-10 05:38:12 +13:00
|
|
|
try:
|
2021-02-23 09:18:56 +13:00
|
|
|
self.btnAddPerso.clicked.connect(self.lstCharacters.addCharacter, F.AUC)
|
2021-12-01 11:40:31 +13:00
|
|
|
self.btnRmPerso.clicked.connect(self.deleteCharacter, F.AUC)
|
2020-03-31 16:55:30 +13:00
|
|
|
|
2017-11-21 03:42:30 +13:00
|
|
|
self.btnPersoColor.clicked.connect(self.lstCharacters.choseCharacterColor, F.AUC)
|
2020-03-31 16:55:30 +13:00
|
|
|
self.chkPersoPOV.stateChanged.connect(self.lstCharacters.changeCharacterPOVState, F.AUC)
|
|
|
|
|
2017-11-21 03:42:30 +13:00
|
|
|
self.btnPersoAddInfo.clicked.connect(self.lstCharacters.addCharacterInfo, F.AUC)
|
|
|
|
self.btnPersoRmInfo.clicked.connect(self.lstCharacters.removeCharacterInfo, F.AUC)
|
2016-03-10 05:38:12 +13:00
|
|
|
except TypeError:
|
|
|
|
# Connection has already been made
|
|
|
|
pass
|
2015-07-02 20:08:20 +12:00
|
|
|
|
2015-06-29 20:22:18 +12:00
|
|
|
for w, c in [
|
2017-11-16 09:05:48 +13:00
|
|
|
(self.txtPersoName, Character.name),
|
|
|
|
(self.sldPersoImportance, Character.importance),
|
|
|
|
(self.txtPersoMotivation, Character.motivation),
|
|
|
|
(self.txtPersoGoal, Character.goal),
|
|
|
|
(self.txtPersoConflict, Character.conflict),
|
|
|
|
(self.txtPersoEpiphany, Character.epiphany),
|
|
|
|
(self.txtPersoSummarySentence, Character.summarySentence),
|
|
|
|
(self.txtPersoSummaryPara, Character.summaryPara),
|
|
|
|
(self.txtPersoSummaryFull, Character.summaryFull),
|
|
|
|
(self.txtPersoNotes, Character.notes)
|
2016-02-06 00:25:25 +13:00
|
|
|
]:
|
2016-03-04 04:38:38 +13:00
|
|
|
w.setModel(self.mdlCharacter)
|
2015-06-29 20:22:18 +12:00
|
|
|
w.setColumn(c)
|
|
|
|
self.tabPersos.setEnabled(False)
|
2015-07-02 20:08:20 +12:00
|
|
|
|
2015-06-22 06:00:03 +12:00
|
|
|
# Plots
|
2015-06-22 23:11:45 +12:00
|
|
|
self.lstSubPlots.setModel(self.mdlPlots)
|
2017-10-29 22:21:28 +13:00
|
|
|
self.lstPlotPerso.setModel(self.mdlPlots)
|
|
|
|
self.lstPlots.setPlotModel(self.mdlPlots)
|
2015-06-23 07:07:38 +12:00
|
|
|
self._updatingSubPlot = False
|
2017-11-21 03:42:30 +13:00
|
|
|
self.btnAddPlot.clicked.connect(self.mdlPlots.addPlot, F.AUC)
|
2015-06-23 07:34:11 +12:00
|
|
|
self.btnRmPlot.clicked.connect(lambda:
|
2017-11-21 03:42:30 +13:00
|
|
|
self.mdlPlots.removePlot(self.lstPlots.currentPlotIndex()), F.AUC)
|
|
|
|
self.btnAddSubPlot.clicked.connect(self.mdlPlots.addSubPlot, F.AUC)
|
|
|
|
self.btnAddSubPlot.clicked.connect(self.updateSubPlotView, F.AUC)
|
|
|
|
self.btnRmSubPlot.clicked.connect(self.mdlPlots.removeSubPlot, F.AUC)
|
2015-07-06 20:07:05 +12:00
|
|
|
self.lstPlotPerso.selectionModel().selectionChanged.connect(self.plotPersoSelectionChanged)
|
2017-11-21 03:42:30 +13:00
|
|
|
self.btnRmPlotPerso.clicked.connect(self.mdlPlots.removePlotPerso, F.AUC)
|
|
|
|
self.lstSubPlots.selectionModel().currentRowChanged.connect(self.changeCurrentSubPlot, F.AUC)
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2015-06-22 06:00:03 +12:00
|
|
|
for w, c in [
|
2017-11-16 09:05:48 +13:00
|
|
|
(self.txtPlotName, Plot.name),
|
|
|
|
(self.txtPlotDescription, Plot.description),
|
|
|
|
(self.txtPlotResult, Plot.result),
|
|
|
|
(self.sldPlotImportance, Plot.importance),
|
2016-02-06 00:25:25 +13:00
|
|
|
]:
|
2015-06-22 06:00:03 +12:00
|
|
|
w.setModel(self.mdlPlots)
|
|
|
|
w.setColumn(c)
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2015-06-22 06:00:03 +12:00
|
|
|
self.tabPlot.setEnabled(False)
|
2015-06-29 20:22:18 +12:00
|
|
|
self.mdlPlots.updatePlotPersoButton()
|
2016-03-04 04:38:38 +13:00
|
|
|
self.mdlCharacter.dataChanged.connect(self.mdlPlots.updatePlotPersoButton)
|
2015-06-23 06:30:43 +12:00
|
|
|
self.lstOutlinePlots.setPlotModel(self.mdlPlots)
|
|
|
|
self.lstOutlinePlots.setShowSubPlot(True)
|
2016-03-06 21:21:10 +13:00
|
|
|
self.plotCharacterDelegate = outlineCharacterDelegate(self.mdlCharacter, self)
|
|
|
|
self.lstPlotPerso.setItemDelegate(self.plotCharacterDelegate)
|
2015-07-07 01:00:22 +12:00
|
|
|
self.plotDelegate = plotDelegate(self)
|
2017-11-16 09:05:48 +13:00
|
|
|
self.lstSubPlots.setItemDelegateForColumn(PlotStep.meta, self.plotDelegate)
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2015-07-09 08:06:00 +12:00
|
|
|
# World
|
|
|
|
self.treeWorld.setModel(self.mdlWorld)
|
|
|
|
for i in range(self.mdlWorld.columnCount()):
|
|
|
|
self.treeWorld.hideColumn(i)
|
|
|
|
self.treeWorld.showColumn(0)
|
|
|
|
self.btnWorldEmptyData.setMenu(self.mdlWorld.emptyDataMenu())
|
2017-11-21 03:42:30 +13:00
|
|
|
self.treeWorld.selectionModel().selectionChanged.connect(self.changeCurrentWorld, F.AUC)
|
|
|
|
self.btnAddWorld.clicked.connect(self.mdlWorld.addItem, F.AUC)
|
|
|
|
self.btnRmWorld.clicked.connect(self.mdlWorld.removeItem, F.AUC)
|
2015-07-09 08:06:00 +12:00
|
|
|
for w, c in [
|
2017-11-16 09:05:48 +13:00
|
|
|
(self.txtWorldName, World.name),
|
|
|
|
(self.txtWorldDescription, World.description),
|
|
|
|
(self.txtWorldPassion, World.passion),
|
|
|
|
(self.txtWorldConflict, World.conflict),
|
2016-02-06 00:25:25 +13:00
|
|
|
]:
|
2015-07-09 08:06:00 +12:00
|
|
|
w.setModel(self.mdlWorld)
|
|
|
|
w.setColumn(c)
|
|
|
|
self.tabWorld.setEnabled(False)
|
|
|
|
self.treeWorld.expandAll()
|
2016-02-06 00:25:25 +13:00
|
|
|
|
2015-06-22 06:00:03 +12:00
|
|
|
# Outline
|
|
|
|
self.treeRedacOutline.setModel(self.mdlOutline)
|
2016-03-04 04:38:38 +13:00
|
|
|
self.treeOutlineOutline.setModelCharacters(self.mdlCharacter)
|
2015-07-03 06:10:25 +12:00
|
|
|
self.treeOutlineOutline.setModelLabels(self.mdlLabels)
|
|
|
|
self.treeOutlineOutline.setModelStatus(self.mdlStatus)
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2016-03-04 04:38:38 +13:00
|
|
|
self.redacMetadata.setModels(self.mdlOutline, self.mdlCharacter,
|
2015-06-23 07:34:11 +12:00
|
|
|
self.mdlLabels, self.mdlStatus)
|
2016-03-04 04:38:38 +13:00
|
|
|
self.outlineItemEditor.setModels(self.mdlOutline, self.mdlCharacter,
|
2015-06-23 07:34:11 +12:00
|
|
|
self.mdlLabels, self.mdlStatus)
|
|
|
|
|
2015-07-03 06:10:25 +12:00
|
|
|
self.treeOutlineOutline.setModel(self.mdlOutline)
|
2016-02-06 00:25:25 +13:00
|
|
|
# self.redacEditor.setModel(self.mdlOutline)
|
2016-03-04 04:38:38 +13:00
|
|
|
self.storylineView.setModels(self.mdlOutline, self.mdlCharacter, self.mdlPlots)
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2017-11-21 03:42:30 +13:00
|
|
|
self.treeOutlineOutline.selectionModel().selectionChanged.connect(self.outlineItemEditor.selectionChanged, F.AUC)
|
|
|
|
self.treeOutlineOutline.clicked.connect(self.outlineItemEditor.selectionChanged, F.AUC)
|
2015-07-02 20:08:20 +12:00
|
|
|
|
2015-06-30 00:21:57 +12:00
|
|
|
# Sync selection
|
2017-11-21 03:42:30 +13:00
|
|
|
self.treeRedacOutline.selectionModel().selectionChanged.connect(self.redacMetadata.selectionChanged, F.AUC)
|
|
|
|
self.treeRedacOutline.clicked.connect(self.redacMetadata.selectionChanged, F.AUC)
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2017-11-21 03:42:30 +13:00
|
|
|
self.treeRedacOutline.selectionModel().selectionChanged.connect(self.mainEditor.selectionChanged, F.AUC)
|
2015-07-02 20:08:20 +12:00
|
|
|
|
2015-06-30 22:27:43 +12:00
|
|
|
# Cheat Sheet
|
|
|
|
self.cheatSheet.setModels()
|
2015-07-02 20:08:20 +12:00
|
|
|
|
2016-02-06 00:25:25 +13:00
|
|
|
# Debug
|
2018-01-07 06:49:34 +13:00
|
|
|
self.mdlFlatData.setVerticalHeaderLabels(["General info", "Summary"])
|
2015-06-24 04:22:39 +12:00
|
|
|
self.tblDebugFlatData.setModel(self.mdlFlatData)
|
2016-03-04 04:38:38 +13:00
|
|
|
self.tblDebugPersos.setModel(self.mdlCharacter)
|
|
|
|
self.tblDebugPersosInfos.setModel(self.mdlCharacter)
|
2015-06-29 21:28:34 +12:00
|
|
|
self.tblDebugPersos.selectionModel().currentChanged.connect(
|
2016-03-04 04:38:38 +13:00
|
|
|
lambda: self.tblDebugPersosInfos.setRootIndex(self.mdlCharacter.index(
|
2016-02-06 00:25:25 +13:00
|
|
|
self.tblDebugPersos.selectionModel().currentIndex().row(),
|
2017-11-21 03:42:30 +13:00
|
|
|
Character.name)), F.AUC)
|
2015-07-02 20:08:20 +12:00
|
|
|
|
2015-06-24 04:22:39 +12:00
|
|
|
self.tblDebugPlots.setModel(self.mdlPlots)
|
|
|
|
self.tblDebugPlotsPersos.setModel(self.mdlPlots)
|
|
|
|
self.tblDebugSubPlots.setModel(self.mdlPlots)
|
|
|
|
self.tblDebugPlots.selectionModel().currentChanged.connect(
|
2016-02-06 00:25:25 +13:00
|
|
|
lambda: self.tblDebugPlotsPersos.setRootIndex(self.mdlPlots.index(
|
|
|
|
self.tblDebugPlots.selectionModel().currentIndex().row(),
|
2017-11-21 03:42:30 +13:00
|
|
|
Plot.characters)), F.AUC)
|
2015-06-24 04:22:39 +12:00
|
|
|
self.tblDebugPlots.selectionModel().currentChanged.connect(
|
2016-02-06 00:25:25 +13:00
|
|
|
lambda: self.tblDebugSubPlots.setRootIndex(self.mdlPlots.index(
|
|
|
|
self.tblDebugPlots.selectionModel().currentIndex().row(),
|
2017-11-21 03:42:30 +13:00
|
|
|
Plot.steps)), F.AUC)
|
2015-07-09 08:06:00 +12:00
|
|
|
self.treeDebugWorld.setModel(self.mdlWorld)
|
2015-06-24 04:22:39 +12:00
|
|
|
self.treeDebugOutline.setModel(self.mdlOutline)
|
|
|
|
self.lstDebugLabels.setModel(self.mdlLabels)
|
|
|
|
self.lstDebugStatus.setModel(self.mdlStatus)
|
2015-07-02 20:08:20 +12:00
|
|
|
|
2017-05-22 05:33:07 +12:00
|
|
|
def disconnectAll(self, signal, oldHandler=None):
|
|
|
|
# Disconnect all "oldHandler" slot connections for a signal
|
|
|
|
#
|
|
|
|
# Ref: PyQt Widget connect() and disconnect()
|
|
|
|
# https://stackoverflow.com/questions/21586643/pyqt-widget-connect-and-disconnect
|
|
|
|
#
|
|
|
|
# The loop is needed for safely disconnecting a specific handler,
|
|
|
|
# because it may have been connected multiple times, and
|
|
|
|
# disconnect only removes one connection at a time.
|
|
|
|
while True:
|
|
|
|
try:
|
2021-02-22 11:45:34 +13:00
|
|
|
if oldHandler != None:
|
2017-05-22 05:33:07 +12:00
|
|
|
signal.disconnect(oldHandler)
|
|
|
|
else:
|
|
|
|
signal.disconnect()
|
|
|
|
except TypeError:
|
|
|
|
break
|
|
|
|
|
|
|
|
def breakConnections(self):
|
|
|
|
# Break connections for UI elements that were connected in makeConnections()
|
|
|
|
|
|
|
|
# Characters
|
2021-02-23 09:18:56 +13:00
|
|
|
self.disconnectAll(self.btnAddPerso.clicked, self.lstCharacters.addCharacter)
|
2021-12-01 11:40:31 +13:00
|
|
|
self.disconnectAll(self.btnRmPerso.clicked, self.deleteCharacter)
|
2020-03-31 16:55:30 +13:00
|
|
|
|
2017-05-22 05:33:07 +12:00
|
|
|
self.disconnectAll(self.btnPersoColor.clicked, self.lstCharacters.choseCharacterColor)
|
2020-03-31 16:55:30 +13:00
|
|
|
self.disconnectAll(self.chkPersoPOV.stateChanged, self.lstCharacters.changeCharacterPOVState)
|
|
|
|
|
2017-05-22 05:33:07 +12:00
|
|
|
self.disconnectAll(self.btnPersoAddInfo.clicked, self.lstCharacters.addCharacterInfo)
|
|
|
|
self.disconnectAll(self.btnPersoRmInfo.clicked, self.lstCharacters.removeCharacterInfo)
|
|
|
|
|
|
|
|
# Plots
|
|
|
|
self._updatingSubPlot = False
|
|
|
|
self.disconnectAll(self.btnAddPlot.clicked, self.mdlPlots.addPlot)
|
|
|
|
self.disconnectAll(self.btnRmPlot.clicked, lambda:
|
|
|
|
self.mdlPlots.removePlot(self.lstPlots.currentPlotIndex()))
|
|
|
|
self.disconnectAll(self.btnAddSubPlot.clicked, self.mdlPlots.addSubPlot)
|
|
|
|
self.disconnectAll(self.btnAddSubPlot.clicked, self.updateSubPlotView)
|
|
|
|
self.disconnectAll(self.btnRmSubPlot.clicked, self.mdlPlots.removeSubPlot)
|
|
|
|
self.disconnectAll(self.lstPlotPerso.selectionModel().selectionChanged, self.plotPersoSelectionChanged)
|
2017-10-29 22:21:28 +13:00
|
|
|
self.disconnectAll(self.lstSubPlots.selectionModel().currentRowChanged, self.changeCurrentSubPlot)
|
2017-05-22 05:33:07 +12:00
|
|
|
self.disconnectAll(self.btnRmPlotPerso.clicked, self.mdlPlots.removePlotPerso)
|
|
|
|
|
|
|
|
self.disconnectAll(self.mdlCharacter.dataChanged, self.mdlPlots.updatePlotPersoButton)
|
|
|
|
|
|
|
|
# World
|
|
|
|
self.disconnectAll(self.treeWorld.selectionModel().selectionChanged, self.changeCurrentWorld)
|
|
|
|
self.disconnectAll(self.btnAddWorld.clicked, self.mdlWorld.addItem)
|
|
|
|
self.disconnectAll(self.btnRmWorld.clicked, self.mdlWorld.removeItem)
|
|
|
|
|
|
|
|
# Outline
|
|
|
|
self.disconnectAll(self.treeOutlineOutline.selectionModel().selectionChanged, self.outlineItemEditor.selectionChanged)
|
|
|
|
self.disconnectAll(self.treeOutlineOutline.clicked, self.outlineItemEditor.selectionChanged)
|
|
|
|
|
|
|
|
# Sync selection
|
|
|
|
self.disconnectAll(self.treeRedacOutline.selectionModel().selectionChanged, self.redacMetadata.selectionChanged)
|
|
|
|
self.disconnectAll(self.treeRedacOutline.clicked, self.redacMetadata.selectionChanged)
|
|
|
|
|
|
|
|
self.disconnectAll(self.treeRedacOutline.selectionModel().selectionChanged, self.mainEditor.selectionChanged)
|
|
|
|
|
|
|
|
# Debug
|
|
|
|
self.disconnectAll(self.tblDebugPersos.selectionModel().currentChanged,
|
|
|
|
lambda: self.tblDebugPersosInfos.setRootIndex(self.mdlCharacter.index(
|
|
|
|
self.tblDebugPersos.selectionModel().currentIndex().row(),
|
2017-11-16 09:05:48 +13:00
|
|
|
Character.name)))
|
2017-05-22 05:33:07 +12:00
|
|
|
self.disconnectAll(self.tblDebugPlots.selectionModel().currentChanged,
|
|
|
|
lambda: self.tblDebugPlotsPersos.setRootIndex(self.mdlPlots.index(
|
|
|
|
self.tblDebugPlots.selectionModel().currentIndex().row(),
|
2017-11-16 09:05:48 +13:00
|
|
|
Plot.characters)))
|
2017-05-22 05:33:07 +12:00
|
|
|
self.disconnectAll(self.tblDebugPlots.selectionModel().currentChanged,
|
|
|
|
lambda: self.tblDebugSubPlots.setRootIndex(self.mdlPlots.index(
|
|
|
|
self.tblDebugPlots.selectionModel().currentIndex().row(),
|
2017-11-16 09:05:48 +13:00
|
|
|
Plot.steps)))
|
2017-05-22 05:33:07 +12:00
|
|
|
|
2016-02-06 00:25:25 +13:00
|
|
|
###############################################################################
|
2017-09-29 08:36:06 +13:00
|
|
|
# HELP
|
|
|
|
###############################################################################
|
|
|
|
|
2019-05-10 06:15:16 +12:00
|
|
|
def centerChildWindow(self, win):
|
|
|
|
r = win.geometry()
|
|
|
|
r2 = self.geometry()
|
2021-12-14 02:27:59 +13:00
|
|
|
win.move(r2.center() - QPoint(int(r.width()/2), int(r.height()/2)))
|
2019-05-10 06:15:16 +12:00
|
|
|
|
2021-04-13 23:32:46 +12:00
|
|
|
def support(self):
|
|
|
|
openURL("https://github.com/olivierkes/manuskript/wiki/Technical-Support")
|
|
|
|
|
|
|
|
def locateLogFile(self):
|
|
|
|
logfile = getLogFilePath()
|
|
|
|
|
|
|
|
# Make sure we are even logging to a file.
|
|
|
|
if not logfile:
|
|
|
|
QMessageBox(QMessageBox.Information,
|
|
|
|
self.tr("Sorry!"),
|
|
|
|
"<p><b>" +
|
|
|
|
self.tr("This session is not being logged.") +
|
|
|
|
"</b></p>",
|
|
|
|
QMessageBox.Ok).exec()
|
|
|
|
return
|
|
|
|
|
|
|
|
# Remind user that log files are at their best once they are complete.
|
|
|
|
msg = QMessageBox(QMessageBox.Information,
|
|
|
|
self.tr("A log file is a Work in Progress!"),
|
|
|
|
"<p><b>" +
|
|
|
|
self.tr("The log file \"{}\" will continue to be written to until Manuskript is closed.").format(os.path.basename(logfile)) +
|
|
|
|
"</b></p>" +
|
|
|
|
"<p>" +
|
|
|
|
self.tr("It will now be displayed in your file manager, but is of limited use until you close Manuskript.") +
|
|
|
|
"</p>",
|
|
|
|
QMessageBox.Ok)
|
|
|
|
|
|
|
|
ret = msg.exec()
|
|
|
|
|
|
|
|
# Open the filemanager.
|
|
|
|
if ret == QMessageBox.Ok:
|
|
|
|
if not showInFolder(logfile):
|
|
|
|
# If everything convenient fails, at least make sure the user can browse to its location manually.
|
|
|
|
QMessageBox(QMessageBox.Critical,
|
|
|
|
self.tr("Error!"),
|
|
|
|
"<p><b>" +
|
|
|
|
self.tr("An error was encountered while trying to show the log file below in your file manager.") +
|
|
|
|
"</b></p>" +
|
|
|
|
"<p>" +
|
|
|
|
logfile +
|
|
|
|
"</p>",
|
|
|
|
QMessageBox.Ok).exec()
|
|
|
|
|
|
|
|
|
2017-09-29 08:36:06 +13:00
|
|
|
def about(self):
|
|
|
|
self.dialog = aboutDialog(mw=self)
|
|
|
|
self.dialog.setFixedSize(self.dialog.size())
|
|
|
|
self.dialog.show()
|
|
|
|
# Center about dialog
|
2019-05-10 06:15:16 +12:00
|
|
|
self.centerChildWindow(self.dialog)
|
2017-09-29 08:36:06 +13:00
|
|
|
|
|
|
|
###############################################################################
|
2016-02-06 00:25:25 +13:00
|
|
|
# GENERAL AKA UNSORTED
|
|
|
|
###############################################################################
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2015-05-31 16:03:07 +12:00
|
|
|
def wordCount(self, i):
|
2015-06-23 07:34:11 +12:00
|
|
|
|
|
|
|
src = {
|
2016-03-02 11:41:55 +13:00
|
|
|
0: self.txtSummarySentence,
|
2015-06-23 07:34:11 +12:00
|
|
|
1: self.txtSummaryPara,
|
|
|
|
2: self.txtSummaryPage,
|
|
|
|
3: self.txtSummaryFull
|
2016-02-06 00:25:25 +13:00
|
|
|
}[i]
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2015-05-28 13:32:09 +12:00
|
|
|
lbl = {
|
2016-03-02 11:41:55 +13:00
|
|
|
0: self.lblSummaryWCSentence,
|
2015-06-23 07:34:11 +12:00
|
|
|
1: self.lblSummaryWCPara,
|
|
|
|
2: self.lblSummaryWCPage,
|
|
|
|
3: self.lblSummaryWCFull
|
2016-02-06 00:25:25 +13:00
|
|
|
}[i]
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2015-06-05 06:22:37 +12:00
|
|
|
wc = wordCount(src.toPlainText())
|
2015-06-23 07:34:11 +12:00
|
|
|
if i in [2, 3]:
|
|
|
|
pages = self.tr(" (~{} pages)").format(int(wc / 25) / 10.)
|
|
|
|
else:
|
|
|
|
pages = ""
|
2015-06-08 22:01:45 +12:00
|
|
|
lbl.setText(self.tr("Words: {}{}").format(wc, pages))
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2015-06-04 05:25:03 +12:00
|
|
|
def setupMoreUi(self):
|
2016-02-06 00:25:25 +13:00
|
|
|
|
2016-04-09 20:50:55 +12:00
|
|
|
style.styleMainWindow(self)
|
2016-04-09 00:49:15 +12:00
|
|
|
|
2015-07-10 01:01:07 +12:00
|
|
|
# Tool bar on the right
|
|
|
|
self.toolbar = collapsibleDockWidgets(Qt.RightDockWidgetArea, self)
|
2017-10-20 09:21:15 +13:00
|
|
|
self.toolbar.addCustomWidget(self.tr("Book summary"), self.grpPlotSummary, self.TabPlots, False)
|
|
|
|
self.toolbar.addCustomWidget(self.tr("Project tree"), self.treeRedacWidget, self.TabRedac, True)
|
|
|
|
self.toolbar.addCustomWidget(self.tr("Metadata"), self.redacMetadata, self.TabRedac, False)
|
|
|
|
self.toolbar.addCustomWidget(self.tr("Story line"), self.storylineView, self.TabRedac, False)
|
2016-02-29 01:54:11 +13:00
|
|
|
if self._toolbarState:
|
|
|
|
self.toolbar.restoreState(self._toolbarState)
|
2016-02-06 00:25:25 +13:00
|
|
|
|
2017-12-05 02:18:58 +13:00
|
|
|
# Hides navigation dock title bar
|
|
|
|
self.dckNavigation.setTitleBarWidget(QWidget(None))
|
|
|
|
|
2016-02-07 06:36:02 +13:00
|
|
|
# Custom "tab" bar on the left
|
|
|
|
self.lstTabs.setIconSize(QSize(48, 48))
|
|
|
|
for i in range(self.tabMain.count()):
|
2017-10-24 01:40:55 +13:00
|
|
|
|
2017-10-16 21:48:04 +13:00
|
|
|
icons = [QIcon.fromTheme("stock_view-details"), #info
|
|
|
|
QIcon.fromTheme("application-text-template"), #applications-publishing
|
|
|
|
F.themeIcon("characters"),
|
|
|
|
F.themeIcon("plots"),
|
|
|
|
F.themeIcon("world"),
|
|
|
|
F.themeIcon("outline"),
|
|
|
|
QIcon.fromTheme("gtk-edit"),
|
|
|
|
QIcon.fromTheme("applications-debugging")
|
2017-10-16 01:30:50 +13:00
|
|
|
]
|
2017-10-16 21:48:04 +13:00
|
|
|
self.tabMain.setTabIcon(i, icons[i])
|
2017-10-24 01:40:55 +13:00
|
|
|
|
2016-02-07 06:36:02 +13:00
|
|
|
item = QListWidgetItem(self.tabMain.tabIcon(i),
|
|
|
|
self.tabMain.tabText(i))
|
|
|
|
item.setSizeHint(QSize(item.sizeHint().width(), 64))
|
2017-10-15 01:14:17 +13:00
|
|
|
item.setToolTip(self.tabMain.tabText(i))
|
2016-02-07 06:36:02 +13:00
|
|
|
item.setTextAlignment(Qt.AlignCenter)
|
|
|
|
self.lstTabs.addItem(item)
|
|
|
|
self.tabMain.tabBar().hide()
|
|
|
|
self.lstTabs.currentRowChanged.connect(self.tabMain.setCurrentIndex)
|
2017-12-05 02:12:00 +13:00
|
|
|
self.lstTabs.item(self.TabDebug).setHidden(not self.SHOW_DEBUG_TAB)
|
2017-12-05 02:50:09 +13:00
|
|
|
self.tabMain.setTabEnabled(self.TabDebug, self.SHOW_DEBUG_TAB)
|
2016-02-07 06:36:02 +13:00
|
|
|
self.tabMain.currentChanged.connect(self.lstTabs.setCurrentRow)
|
|
|
|
|
2015-06-04 05:25:03 +12:00
|
|
|
# Splitters
|
|
|
|
self.splitterPersos.setStretchFactor(0, 25)
|
|
|
|
self.splitterPersos.setStretchFactor(1, 75)
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2015-06-04 05:25:03 +12:00
|
|
|
self.splitterPlot.setStretchFactor(0, 20)
|
2015-06-05 06:22:37 +12:00
|
|
|
self.splitterPlot.setStretchFactor(1, 60)
|
|
|
|
self.splitterPlot.setStretchFactor(2, 30)
|
2016-02-06 00:25:25 +13:00
|
|
|
|
2015-07-09 08:06:00 +12:00
|
|
|
self.splitterWorld.setStretchFactor(0, 25)
|
|
|
|
self.splitterWorld.setStretchFactor(1, 75)
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2015-06-05 06:22:37 +12:00
|
|
|
self.splitterOutlineH.setStretchFactor(0, 25)
|
|
|
|
self.splitterOutlineH.setStretchFactor(1, 75)
|
|
|
|
self.splitterOutlineV.setStretchFactor(0, 75)
|
|
|
|
self.splitterOutlineV.setStretchFactor(1, 25)
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2016-02-29 01:01:46 +13:00
|
|
|
self.splitterRedacV.setStretchFactor(0, 75)
|
|
|
|
self.splitterRedacV.setStretchFactor(1, 25)
|
2016-02-28 07:56:55 +13:00
|
|
|
|
|
|
|
self.splitterRedacH.setStretchFactor(0, 30)
|
|
|
|
self.splitterRedacH.setStretchFactor(1, 40)
|
|
|
|
self.splitterRedacH.setStretchFactor(2, 30)
|
2016-02-06 00:25:25 +13:00
|
|
|
|
2015-07-09 08:06:00 +12:00
|
|
|
# QFormLayout stretch
|
|
|
|
for w in [self.txtWorldDescription, self.txtWorldPassion, self.txtWorldConflict]:
|
|
|
|
s = w.sizePolicy()
|
|
|
|
s.setVerticalStretch(1)
|
|
|
|
w.setSizePolicy(s)
|
2016-02-06 00:25:25 +13:00
|
|
|
|
2015-06-04 05:25:03 +12:00
|
|
|
# Help box
|
|
|
|
references = [
|
|
|
|
(self.lytTabOverview,
|
2017-11-24 06:04:50 +13:00
|
|
|
self.tr("Enter information about your book, and yourself."),
|
2015-06-21 21:29:35 +12:00
|
|
|
0),
|
|
|
|
(self.lytSituation,
|
2016-02-06 00:25:25 +13:00
|
|
|
self.tr(
|
|
|
|
"""The basic situation, in the form of a 'What if...?' question. Ex: 'What if the most dangerous
|
2017-11-24 06:13:09 +13:00
|
|
|
evil wizard wasn't able to kill a baby?' (Harry Potter)"""),
|
2015-06-21 21:29:35 +12:00
|
|
|
1),
|
|
|
|
(self.lytSummary,
|
2016-02-06 00:25:25 +13:00
|
|
|
self.tr(
|
2016-03-02 11:41:55 +13:00
|
|
|
"""Take time to think about a one sentence (~50 words) summary of your book. Then expand it to
|
2016-02-06 00:25:25 +13:00
|
|
|
a paragraph, then to a page, then to a full summary."""),
|
2015-06-21 21:29:35 +12:00
|
|
|
1),
|
2015-06-04 05:25:03 +12:00
|
|
|
(self.lytTabPersos,
|
2015-06-21 21:29:35 +12:00
|
|
|
self.tr("Create your characters."),
|
|
|
|
0),
|
2015-06-04 05:25:03 +12:00
|
|
|
(self.lytTabPlot,
|
2015-06-21 21:29:35 +12:00
|
|
|
self.tr("Develop plots."),
|
|
|
|
0),
|
2017-09-24 07:53:18 +13:00
|
|
|
(self.lytTabContext,
|
|
|
|
self.tr("Build worlds. Create hierarchy of broad categories down to specific details."),
|
|
|
|
0),
|
2015-06-04 05:25:03 +12:00
|
|
|
(self.lytTabOutline,
|
2015-06-21 21:29:35 +12:00
|
|
|
self.tr("Create the outline of your masterpiece."),
|
|
|
|
0),
|
2015-06-04 05:25:03 +12:00
|
|
|
(self.lytTabRedac,
|
2015-06-21 21:29:35 +12:00
|
|
|
self.tr("Write."),
|
|
|
|
0),
|
2015-06-04 05:25:03 +12:00
|
|
|
(self.lytTabDebug,
|
2017-10-15 07:40:50 +13:00
|
|
|
self.tr("Debug info. Sometimes useful."),
|
2015-06-21 21:29:35 +12:00
|
|
|
0)
|
2016-02-06 00:25:25 +13:00
|
|
|
]
|
2015-06-04 05:25:03 +12:00
|
|
|
|
2015-06-21 21:29:35 +12:00
|
|
|
for widget, text, pos in references:
|
2015-06-25 06:32:50 +12:00
|
|
|
label = helpLabel(text, self)
|
2017-11-21 03:42:30 +13:00
|
|
|
self.actShowHelp.toggled.connect(label.setVisible, F.AUC)
|
2015-06-21 21:29:35 +12:00
|
|
|
widget.layout().insertWidget(pos, label)
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2015-06-07 05:10:44 +12:00
|
|
|
self.actShowHelp.setChecked(False)
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2015-06-07 05:10:44 +12:00
|
|
|
# Spellcheck
|
2019-02-22 09:32:34 +13:00
|
|
|
if Spellchecker.isInstalled():
|
2015-06-08 22:01:45 +12:00
|
|
|
self.menuDict = QMenu(self.tr("Dictionary"))
|
2015-06-07 05:10:44 +12:00
|
|
|
self.menuDictGroup = QActionGroup(self)
|
2015-06-16 06:09:16 +12:00
|
|
|
self.updateMenuDict()
|
2015-06-07 05:10:44 +12:00
|
|
|
self.menuTools.addMenu(self.menuDict)
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2017-11-21 03:42:30 +13:00
|
|
|
self.actSpellcheck.toggled.connect(self.toggleSpellcheck, F.AUC)
|
2019-02-22 09:32:34 +13:00
|
|
|
# self.dictChanged.connect(self.mainEditor.setDict, F.AUC)
|
|
|
|
# self.dictChanged.connect(self.redacMetadata.setDict, F.AUC)
|
|
|
|
# self.dictChanged.connect(self.outlineItemEditor.setDict, F.AUC)
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2015-06-07 05:10:44 +12:00
|
|
|
else:
|
|
|
|
# No Spell check support
|
|
|
|
self.actSpellcheck.setVisible(False)
|
2019-02-24 13:48:57 +13:00
|
|
|
for lib, requirement in Spellchecker.supportedLibraries().items():
|
|
|
|
a = QAction(self.tr("Install {}{} to use spellcheck").format(lib, requirement or ""), self)
|
2019-02-22 12:50:28 +13:00
|
|
|
a.setIcon(self.style().standardIcon(QStyle.SP_MessageBoxWarning))
|
|
|
|
# Need to bound the lib argument otherwise the lambda uses the same lib value across all calls
|
|
|
|
def gen_slot_cb(l):
|
|
|
|
return lambda: self.openSpellcheckWebPage(l)
|
|
|
|
a.triggered.connect(gen_slot_cb(lib), F.AUC)
|
|
|
|
self.menuTools.addAction(a)
|
|
|
|
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2016-02-06 00:25:25 +13:00
|
|
|
###############################################################################
|
|
|
|
# SPELLCHECK
|
|
|
|
###############################################################################
|
2015-06-24 04:22:39 +12:00
|
|
|
|
2015-06-16 06:09:16 +12:00
|
|
|
def updateMenuDict(self):
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2019-02-22 09:32:34 +13:00
|
|
|
if not Spellchecker.isInstalled():
|
2015-06-16 18:15:24 +12:00
|
|
|
return
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2015-06-16 06:09:16 +12:00
|
|
|
self.menuDict.clear()
|
2019-02-24 13:48:57 +13:00
|
|
|
dictionaries = Spellchecker.availableDictionaries()
|
2019-03-28 09:43:56 +13:00
|
|
|
|
|
|
|
# Set first run dictionary
|
2023-02-10 10:13:01 +13:00
|
|
|
if settings.dict is None:
|
2019-03-28 09:43:56 +13:00
|
|
|
settings.dict = Spellchecker.getDefaultDictionary()
|
|
|
|
|
|
|
|
# Check if project dict is unavailable on this machine
|
|
|
|
dict_available = False
|
|
|
|
for lib, dicts in dictionaries.items():
|
|
|
|
if dict_available:
|
|
|
|
break
|
|
|
|
for i in dicts:
|
|
|
|
if Spellchecker.normalizeDictName(lib, i) == settings.dict:
|
|
|
|
dict_available = True
|
|
|
|
break
|
|
|
|
# Reset dict to default one if it's unavailable
|
|
|
|
if not dict_available:
|
|
|
|
settings.dict = Spellchecker.getDefaultDictionary()
|
|
|
|
|
2019-02-24 13:48:57 +13:00
|
|
|
for lib, dicts in dictionaries.items():
|
|
|
|
if len(dicts) > 0:
|
2019-02-22 12:50:28 +13:00
|
|
|
a = QAction(lib, self)
|
|
|
|
else:
|
2019-02-24 13:48:57 +13:00
|
|
|
a = QAction(self.tr("{} has no installed dictionaries").format(lib), self)
|
2019-02-22 12:50:28 +13:00
|
|
|
a.setEnabled(False)
|
2015-06-16 06:09:16 +12:00
|
|
|
self.menuDict.addAction(a)
|
2019-02-22 12:50:28 +13:00
|
|
|
for i in dicts:
|
|
|
|
a = QAction(i, self)
|
|
|
|
a.data = lib
|
|
|
|
a.setCheckable(True)
|
|
|
|
if Spellchecker.normalizeDictName(lib, i) == settings.dict:
|
|
|
|
a.setChecked(True)
|
|
|
|
a.triggered.connect(self.setDictionary, F.AUC)
|
|
|
|
self.menuDictGroup.addAction(a)
|
|
|
|
self.menuDict.addAction(a)
|
|
|
|
self.menuDict.addSeparator()
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2019-03-28 09:43:56 +13:00
|
|
|
# If a new dictionary was chosen, apply the change and re-enable spellcheck if it was enabled.
|
|
|
|
if not dict_available:
|
|
|
|
self.setDictionary()
|
|
|
|
self.toggleSpellcheck(settings.spellcheck)
|
|
|
|
|
2019-02-24 13:48:57 +13:00
|
|
|
for lib, requirement in Spellchecker.supportedLibraries().items():
|
|
|
|
if lib not in dictionaries:
|
|
|
|
a = QAction(self.tr("{}{} is not installed").format(lib, requirement or ""), self)
|
|
|
|
a.setEnabled(False)
|
|
|
|
self.menuDict.addAction(a)
|
|
|
|
self.menuDict.addSeparator()
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2015-06-07 05:10:44 +12:00
|
|
|
def setDictionary(self):
|
2019-02-22 09:32:34 +13:00
|
|
|
if not Spellchecker.isInstalled():
|
2015-06-16 18:15:24 +12:00
|
|
|
return
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2015-06-07 05:10:44 +12:00
|
|
|
for i in self.menuDictGroup.actions():
|
|
|
|
if i.isChecked():
|
2016-02-06 00:25:25 +13:00
|
|
|
# self.dictChanged.emit(i.text().replace("&", ""))
|
2019-02-22 12:50:28 +13:00
|
|
|
settings.dict = Spellchecker.normalizeDictName(i.data, i.text().replace("&", ""))
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2015-06-22 06:01:58 +12:00
|
|
|
# Find all textEditView from self, and toggle spellcheck
|
2015-06-23 07:34:11 +12:00
|
|
|
for w in self.findChildren(textEditView, QRegExp(".*"),
|
|
|
|
Qt.FindChildrenRecursively):
|
2015-06-22 06:01:58 +12:00
|
|
|
w.setDict(settings.dict)
|
2015-06-07 05:10:44 +12:00
|
|
|
|
2019-02-22 12:50:28 +13:00
|
|
|
def openSpellcheckWebPage(self, lib):
|
|
|
|
F.openURL(Spellchecker.getLibraryURL(lib))
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2015-06-16 06:09:16 +12:00
|
|
|
def toggleSpellcheck(self, val):
|
|
|
|
settings.spellcheck = val
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2015-06-22 04:13:14 +12:00
|
|
|
# Find all textEditView from self, and toggle spellcheck
|
2015-06-23 07:34:11 +12:00
|
|
|
for w in self.findChildren(textEditView, QRegExp(".*"),
|
|
|
|
Qt.FindChildrenRecursively):
|
2015-06-22 04:13:14 +12:00
|
|
|
w.toggleSpellcheck(val)
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2016-02-06 00:25:25 +13:00
|
|
|
###############################################################################
|
|
|
|
# SETTINGS
|
|
|
|
###############################################################################
|
2015-06-10 10:20:32 +12:00
|
|
|
|
|
|
|
def settingsLabel(self):
|
2015-07-04 09:00:54 +12:00
|
|
|
self.settingsWindow(3)
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2015-06-10 10:20:32 +12:00
|
|
|
def settingsStatus(self):
|
2015-07-04 09:00:54 +12:00
|
|
|
self.settingsWindow(4)
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2015-06-10 10:20:32 +12:00
|
|
|
def settingsWindow(self, tab=None):
|
|
|
|
self.sw = settingsWindow(self)
|
|
|
|
self.sw.hide()
|
|
|
|
self.sw.setWindowModality(Qt.ApplicationModal)
|
|
|
|
self.sw.setWindowFlags(Qt.Dialog)
|
2019-05-10 06:15:16 +12:00
|
|
|
self.centerChildWindow(self.sw)
|
2015-06-10 10:20:32 +12:00
|
|
|
if tab:
|
2015-06-18 06:45:24 +12:00
|
|
|
self.sw.setTab(tab)
|
2015-06-10 10:20:32 +12:00
|
|
|
self.sw.show()
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2016-02-09 01:50:35 +13:00
|
|
|
###############################################################################
|
|
|
|
# TOOLS
|
|
|
|
###############################################################################
|
|
|
|
|
|
|
|
def frequencyAnalyzer(self):
|
|
|
|
self.fw = frequencyAnalyzer(self)
|
|
|
|
self.fw.show()
|
2019-05-11 08:59:26 +12:00
|
|
|
self.centerChildWindow(self.fw)
|
2016-02-09 01:50:35 +13:00
|
|
|
|
2019-01-22 18:37:22 +13:00
|
|
|
def sessionTargets(self):
|
2022-12-11 06:05:41 +13:00
|
|
|
self.td = TargetsDialog(self)
|
2019-01-22 18:37:22 +13:00
|
|
|
self.td.show()
|
2022-12-11 06:05:41 +13:00
|
|
|
self.centerChildWindow(self.td)
|
2019-01-22 18:37:22 +13:00
|
|
|
|
2016-02-06 00:25:25 +13:00
|
|
|
###############################################################################
|
|
|
|
# VIEW MENU
|
|
|
|
###############################################################################
|
2015-06-23 07:34:11 +12:00
|
|
|
|
|
|
|
def generateViewMenu(self):
|
|
|
|
|
2015-06-16 06:09:16 +12:00
|
|
|
values = [
|
2016-02-06 00:25:25 +13:00
|
|
|
(self.tr("Nothing"), "Nothing"),
|
|
|
|
(self.tr("POV"), "POV"),
|
|
|
|
(self.tr("Label"), "Label"),
|
|
|
|
(self.tr("Progress"), "Progress"),
|
|
|
|
(self.tr("Compile"), "Compile"),
|
|
|
|
]
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2015-06-16 06:09:16 +12:00
|
|
|
menus = [
|
2017-10-16 09:23:06 +13:00
|
|
|
(self.tr("Tree"), "Tree", "view-list-tree"),
|
|
|
|
(self.tr("Index cards"), "Cork", "view-cards"),
|
|
|
|
(self.tr("Outline"), "Outline", "view-outline")
|
2016-02-06 00:25:25 +13:00
|
|
|
]
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2015-06-16 06:09:16 +12:00
|
|
|
submenus = {
|
|
|
|
"Tree": [
|
|
|
|
(self.tr("Icon color"), "Icon"),
|
|
|
|
(self.tr("Text color"), "Text"),
|
|
|
|
(self.tr("Background color"), "Background"),
|
2016-02-06 00:25:25 +13:00
|
|
|
],
|
2015-06-16 06:09:16 +12:00
|
|
|
"Cork": [
|
|
|
|
(self.tr("Icon"), "Icon"),
|
|
|
|
(self.tr("Text"), "Text"),
|
|
|
|
(self.tr("Background"), "Background"),
|
|
|
|
(self.tr("Border"), "Border"),
|
|
|
|
(self.tr("Corner"), "Corner"),
|
2016-02-06 00:25:25 +13:00
|
|
|
],
|
2015-06-16 06:09:16 +12:00
|
|
|
"Outline": [
|
|
|
|
(self.tr("Icon color"), "Icon"),
|
|
|
|
(self.tr("Text color"), "Text"),
|
|
|
|
(self.tr("Background color"), "Background"),
|
2016-02-06 00:25:25 +13:00
|
|
|
],
|
|
|
|
}
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2015-06-16 06:09:16 +12:00
|
|
|
self.menuView.clear()
|
2016-03-25 01:42:47 +13:00
|
|
|
self.menuView.addMenu(self.menuMode)
|
|
|
|
self.menuView.addSeparator()
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2019-10-15 01:36:06 +13:00
|
|
|
# LOGGER.debug("Generating menus with %s.", settings.viewSettings)
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2017-10-16 09:23:06 +13:00
|
|
|
for mnu, mnud, icon in menus:
|
2015-06-16 06:09:16 +12:00
|
|
|
m = QMenu(mnu, self.menuView)
|
2017-10-16 09:23:06 +13:00
|
|
|
if icon:
|
|
|
|
m.setIcon(QIcon.fromTheme(icon))
|
2015-06-16 06:09:16 +12:00
|
|
|
for s, sd in submenus[mnud]:
|
|
|
|
m2 = QMenu(s, m)
|
|
|
|
agp = QActionGroup(m2)
|
|
|
|
for v, vd in values:
|
|
|
|
a = QAction(v, m)
|
|
|
|
a.setCheckable(True)
|
|
|
|
a.setData("{},{},{}".format(mnud, sd, vd))
|
|
|
|
if settings.viewSettings[mnud][sd] == vd:
|
|
|
|
a.setChecked(True)
|
2017-11-21 03:42:30 +13:00
|
|
|
a.triggered.connect(self.setViewSettingsAction, F.AUC)
|
2015-06-16 06:09:16 +12:00
|
|
|
agp.addAction(a)
|
|
|
|
m2.addAction(a)
|
|
|
|
m.addMenu(m2)
|
|
|
|
self.menuView.addMenu(m)
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2015-06-18 04:40:55 +12:00
|
|
|
def setViewSettingsAction(self):
|
2015-06-16 06:09:16 +12:00
|
|
|
action = self.sender()
|
|
|
|
item, part, element = action.data().split(",")
|
2015-06-18 04:40:55 +12:00
|
|
|
self.setViewSettings(item, part, element)
|
2015-06-23 07:34:11 +12:00
|
|
|
|
2015-06-18 04:40:55 +12:00
|
|
|
def setViewSettings(self, item, part, element):
|
2015-06-16 09:15:10 +12:00
|
|
|
settings.viewSettings[item][part] = element
|
|
|
|
if item == "Cork":
|
2015-06-28 00:06:35 +12:00
|
|
|
self.mainEditor.updateCorkView()
|
2015-06-18 03:15:13 +12:00
|
|
|
if item == "Outline":
|
2015-06-28 00:06:35 +12:00
|
|
|
self.mainEditor.updateTreeView()
|
2015-07-03 06:10:25 +12:00
|
|
|
self.treeOutlineOutline.viewport().update()
|
2015-06-18 03:15:13 +12:00
|
|
|
if item == "Tree":
|
2015-07-01 23:14:03 +12:00
|
|
|
self.treeRedacOutline.viewport().update()
|
2015-07-02 20:08:20 +12:00
|
|
|
|
2016-03-25 01:42:47 +13:00
|
|
|
###############################################################################
|
|
|
|
# VIEW MODES
|
|
|
|
###############################################################################
|
|
|
|
|
|
|
|
def setViewModeSimple(self):
|
|
|
|
settings.viewMode = "simple"
|
|
|
|
self.tabMain.setCurrentIndex(self.TabRedac)
|
|
|
|
self.viewModeFictionVisibilitySwitch(False)
|
|
|
|
self.actModeSimple.setChecked(True)
|
|
|
|
|
|
|
|
def setViewModeFiction(self):
|
|
|
|
settings.viewMode = "fiction"
|
|
|
|
self.viewModeFictionVisibilitySwitch(True)
|
|
|
|
self.actModeFiction.setChecked(True)
|
|
|
|
|
|
|
|
def viewModeFictionVisibilitySwitch(self, val):
|
|
|
|
"""
|
2018-01-07 06:48:40 +13:00
|
|
|
Switches the visibility of some UI components useful for fiction only
|
2016-03-25 01:42:47 +13:00
|
|
|
@param val: sets visibility to val
|
|
|
|
"""
|
|
|
|
|
2018-01-07 06:48:40 +13:00
|
|
|
# Menu navigation & button in toolbar
|
2016-03-25 01:42:47 +13:00
|
|
|
self.toolbar.setDockVisibility(self.dckNavigation, val)
|
|
|
|
|
2018-01-07 06:48:40 +13:00
|
|
|
# POV in metadata
|
2016-03-25 01:42:47 +13:00
|
|
|
from manuskript.ui.views.propertiesView import propertiesView
|
|
|
|
for w in findWidgetsOfClass(propertiesView):
|
|
|
|
w.lblPOV.setVisible(val)
|
|
|
|
w.cmbPOV.setVisible(val)
|
|
|
|
|
|
|
|
# POV in outline view
|
2023-02-10 10:13:01 +13:00
|
|
|
if val is None and Outline.POV in settings.outlineViewColumns:
|
2017-11-16 08:58:12 +13:00
|
|
|
settings.outlineViewColumns.remove(Outline.POV)
|
2016-03-25 01:42:47 +13:00
|
|
|
|
|
|
|
from manuskript.ui.views.outlineView import outlineView
|
|
|
|
for w in findWidgetsOfClass(outlineView):
|
|
|
|
w.hideColumns()
|
|
|
|
|
|
|
|
# TODO: clean up all other fiction things in non-fiction view mode
|
|
|
|
# Character in search widget
|
|
|
|
# POV in settings / views
|
|
|
|
|
2016-02-06 00:25:25 +13:00
|
|
|
###############################################################################
|
2017-11-06 21:16:44 +13:00
|
|
|
# IMPORT / EXPORT
|
2016-02-06 00:25:25 +13:00
|
|
|
###############################################################################
|
2015-07-01 23:14:03 +12:00
|
|
|
|
2017-11-06 21:16:44 +13:00
|
|
|
def doImport(self):
|
2019-09-13 05:54:53 +12:00
|
|
|
# Warn about buggy Qt versions and import crash
|
|
|
|
#
|
|
|
|
# (Py)Qt 5.11 and 5.12 have a bug that can cause crashes when simply
|
|
|
|
# setting up various UI elements.
|
|
|
|
# This has been reported and verified to happen with File -> Import.
|
|
|
|
# See PR #611.
|
|
|
|
if re.match("^5\\.1[12](\\.?|$)", qVersion()):
|
|
|
|
warning1 = self.tr("PyQt / Qt versions 5.11 and 5.12 are known to cause a crash which might result in a loss of data.")
|
|
|
|
warning2 = self.tr("PyQt {} and Qt {} are in use.").format(qVersion(), PYQT_VERSION_STR)
|
|
|
|
|
|
|
|
# Don't translate for debug log.
|
2019-10-15 01:36:06 +13:00
|
|
|
LOGGER.warning(warning1)
|
|
|
|
LOGGER.warning(warning2)
|
2019-09-13 05:54:53 +12:00
|
|
|
|
|
|
|
msg = QMessageBox(QMessageBox.Warning,
|
2020-01-10 06:46:48 +13:00
|
|
|
self.tr("Proceed with import at your own risk"),
|
2019-09-13 05:54:53 +12:00
|
|
|
"<p><b>" +
|
|
|
|
warning1 +
|
|
|
|
"</b></p>" +
|
|
|
|
"<p>" +
|
|
|
|
warning2 +
|
|
|
|
"</p>",
|
|
|
|
QMessageBox.Abort | QMessageBox.Ignore)
|
|
|
|
msg.setDefaultButton(QMessageBox.Abort)
|
|
|
|
|
|
|
|
# Return because user heeds warning
|
|
|
|
if msg.exec() == QMessageBox.Abort:
|
|
|
|
return
|
|
|
|
|
|
|
|
# Proceed with Import
|
2017-11-06 21:16:44 +13:00
|
|
|
self.dialog = importerDialog(mw=self)
|
|
|
|
self.dialog.show()
|
2019-05-10 06:15:16 +12:00
|
|
|
self.centerChildWindow(self.dialog)
|
2017-11-06 21:16:44 +13:00
|
|
|
|
|
|
|
|
2015-07-01 23:14:03 +12:00
|
|
|
def doCompile(self):
|
2016-04-05 06:00:19 +12:00
|
|
|
self.dialog = exporterDialog(mw=self)
|
2016-04-02 06:01:27 +13:00
|
|
|
self.dialog.show()
|
2019-05-10 06:15:16 +12:00
|
|
|
self.centerChildWindow(self.dialog)
|