|
22 | 22 | from manuskript.models import outlineModel |
23 | 23 | from manuskript.models.plotModel import plotModel |
24 | 24 | from manuskript.models.worldModel import worldModel |
| 25 | +from manuskript.projectManager import ProjectManager |
25 | 26 | from manuskript.settingsWindow import settingsWindow |
26 | 27 | from manuskript.ui import style |
27 | 28 | from manuskript.ui import characterInfoDialog |
@@ -76,6 +77,7 @@ def __init__(self): |
76 | 77 | self.sessionStartWordCount = 0 # Used to track session targets |
77 | 78 | self.history = History() |
78 | 79 | self._previousSelectionEmpty = True |
| 80 | + self.projectManager = ProjectManager(self) |
79 | 81 |
|
80 | 82 | self.readSettings() |
81 | 83 |
|
@@ -113,11 +115,11 @@ def __init__(self): |
113 | 115 |
|
114 | 116 | # Main Menu:: File |
115 | 117 | self.actOpen.triggered.connect(self.welcome.openFile) |
116 | | - self.actSave.triggered.connect(self.saveDatas) |
| 118 | + self.actSave.triggered.connect(self.projectManager.saveDatas) |
117 | 119 | self.actSaveAs.triggered.connect(self.welcome.saveAsFile) |
118 | 120 | self.actImport.triggered.connect(self.doImport) |
119 | 121 | self.actCompile.triggered.connect(self.doCompile) |
120 | | - self.actCloseProject.triggered.connect(self.closeProject) |
| 122 | + self.actCloseProject.triggered.connect(self.projectManager.closeProject) |
121 | 123 | self.actQuit.triggered.connect(self.close) |
122 | 124 |
|
123 | 125 | # Main menu:: Edit |
@@ -905,191 +907,6 @@ def navigated(self, event): |
905 | 907 | self.actBack.setEnabled(event.position > 0) |
906 | 908 | self.actForward.setEnabled(event.position < event.count - 1) |
907 | 909 |
|
908 | | - ############################################################################### |
909 | | - # LOAD AND SAVE |
910 | | - ############################################################################### |
911 | | - |
912 | | - def loadProject(self, project, loadFromFile=True): |
913 | | - """Loads the project ``project``. |
914 | | -
|
915 | | - If ``loadFromFile`` is False, then it does not load datas from file. |
916 | | - It assumes that the datas have been populated in a different way.""" |
917 | | - |
918 | | - # Convert project path to OS norm |
919 | | - project = os.path.normpath(project) |
920 | | - |
921 | | - if loadFromFile and not os.path.exists(project): |
922 | | - LOGGER.warning("The file {} does not exist. Has it been moved or deleted?".format(project)) |
923 | | - F.statusMessage( |
924 | | - self.tr("The file {} does not exist. Has it been moved or deleted?").format(project), importance=3) |
925 | | - return |
926 | | - |
927 | | - if loadFromFile: |
928 | | - # Load empty settings |
929 | | - importlib.reload(settings) |
930 | | - settings.initDefaultValues() |
931 | | - |
932 | | - # Load data |
933 | | - self.loadEmptyDatas() |
934 | | - |
935 | | - if not self.loadDatas(project): |
936 | | - self.closeProject() |
937 | | - return |
938 | | - |
939 | | - self.makeConnections() |
940 | | - |
941 | | - # Load settings |
942 | | - if settings.openIndexes and settings.openIndexes != [""]: |
943 | | - self.mainEditor.tabSplitter.restoreOpenIndexes(settings.openIndexes) |
944 | | - self.generateViewMenu() |
945 | | - self.mainEditor.sldCorkSizeFactor.setValue(settings.corkSizeFactor) |
946 | | - self.actSpellcheck.setChecked(settings.spellcheck) |
947 | | - self.toggleSpellcheck(settings.spellcheck) |
948 | | - self.updateMenuDict() |
949 | | - self.setDictionary() |
950 | | - |
951 | | - iconSize = settings.viewSettings["Tree"]["iconSize"] |
952 | | - self.treeRedacOutline.setIconSize(QSize(iconSize, iconSize)) |
953 | | - self.mainEditor.setFolderView(settings.folderView) |
954 | | - self.mainEditor.updateFolderViewButtons(settings.folderView) |
955 | | - self.mainEditor.tabSplitter.updateStyleSheet() |
956 | | - self.tabMain.setCurrentIndex(settings.lastTab) |
957 | | - self.mainEditor.updateCorkBackground() |
958 | | - if settings.viewMode == "simple": |
959 | | - self.setViewModeSimple() |
960 | | - else: |
961 | | - self.setViewModeFiction() |
962 | | - |
963 | | - # Set autosave |
964 | | - self.saveTimer = QTimer() |
965 | | - self.saveTimer.setInterval(settings.autoSaveDelay * 60 * 1000) |
966 | | - self.saveTimer.setSingleShot(False) |
967 | | - self.saveTimer.timeout.connect(self.saveDatas) |
968 | | - if settings.autoSave: |
969 | | - self.saveTimer.start() |
970 | | - |
971 | | - # Set autosave if no changes |
972 | | - self.saveTimerNoChanges = QTimer() |
973 | | - self.saveTimerNoChanges.setInterval(settings.autoSaveNoChangesDelay * 1000) |
974 | | - self.saveTimerNoChanges.setSingleShot(True) |
975 | | - self.mdlFlatData.dataChanged.connect(self.startTimerNoChanges) |
976 | | - self.mdlOutline.dataChanged.connect(self.startTimerNoChanges) |
977 | | - self.mdlCharacter.dataChanged.connect(self.startTimerNoChanges) |
978 | | - self.mdlPlots.dataChanged.connect(self.startTimerNoChanges) |
979 | | - self.mdlWorld.dataChanged.connect(self.startTimerNoChanges) |
980 | | - self.mdlStatus.dataChanged.connect(self.startTimerNoChanges) |
981 | | - self.mdlLabels.dataChanged.connect(self.startTimerNoChanges) |
982 | | - |
983 | | - self.saveTimerNoChanges.timeout.connect(self.saveDatas) |
984 | | - self.saveTimerNoChanges.stop() |
985 | | - |
986 | | - # UI |
987 | | - for i in [self.actOpen, self.menuRecents]: |
988 | | - i.setEnabled(False) |
989 | | - for i in [self.actSave, self.actSaveAs, self.actCloseProject, |
990 | | - self.menuEdit, self.menuView, self.menuOrganize, |
991 | | - self.menuNavigate, |
992 | | - self.menuTools, self.menuHelp, self.actImport, |
993 | | - self.actCompile, self.actSettings]: |
994 | | - i.setEnabled(True) |
995 | | - # We force to emit even if it opens on the current tab |
996 | | - self.tabMain.currentChanged.emit(settings.lastTab) |
997 | | - |
998 | | - # Make sure we can update the window title later. |
999 | | - self.currentProject = project |
1000 | | - self.projectDirty = False |
1001 | | - QSettings().setValue("lastProject", project) |
1002 | | - |
1003 | | - item = self.mdlOutline.rootItem |
1004 | | - wc = item.data(Outline.wordCount) |
1005 | | - self.sessionStartWordCount = int(wc) if wc != "" else 0 |
1006 | | - # Add project name to Window's name |
1007 | | - self.setWindowTitle(self.projectName() + " - " + self.tr("Manuskript")) |
1008 | | - |
1009 | | - # Reset history |
1010 | | - self.history.reset() |
1011 | | - |
1012 | | - # Show main Window |
1013 | | - self.switchToProject() |
1014 | | - |
1015 | | - def handleUnsavedChanges(self): |
1016 | | - """ |
1017 | | - There may be some currently unsaved changes, but the action the user triggered |
1018 | | - will result in the project or application being closed. To save, or not to save? |
1019 | | -
|
1020 | | - Or just bail out entirely? |
1021 | | -
|
1022 | | - Sometimes it is best to just ask. |
1023 | | - """ |
1024 | | - |
1025 | | - if not self.projectDirty: |
1026 | | - return True # no unsaved changes, all is good |
1027 | | - |
1028 | | - msg = QMessageBox(QMessageBox.Question, |
1029 | | - self.tr("Save project?"), |
1030 | | - "<p><b>" + |
1031 | | - self.tr("Save changes to project \"{}\" before closing?").format(self.projectName()) + |
1032 | | - "</b></p>" + |
1033 | | - "<p>" + |
1034 | | - self.tr("Your changes will be lost if you don't save them.") + |
1035 | | - "</p>", |
1036 | | - QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel) |
1037 | | - |
1038 | | - ret = msg.exec() |
1039 | | - |
1040 | | - if ret == QMessageBox.Cancel: |
1041 | | - return False # the situation has not been handled, cancel action |
1042 | | - |
1043 | | - if ret == QMessageBox.Save: |
1044 | | - self.saveDatas() |
1045 | | - |
1046 | | - return True # the situation has been handled |
1047 | | - |
1048 | | - |
1049 | | - def closeProject(self): |
1050 | | - |
1051 | | - if not self.currentProject: |
1052 | | - return |
1053 | | - |
1054 | | - # Make sure data is saved. |
1055 | | - if (self.projectDirty and settings.saveOnQuit == True): |
1056 | | - self.saveDatas() |
1057 | | - elif not self.handleUnsavedChanges(): |
1058 | | - return # user cancelled action |
1059 | | - |
1060 | | - # Close open tabs in editor |
1061 | | - self.mainEditor.closeAllTabs() |
1062 | | - |
1063 | | - self.currentProject = None |
1064 | | - self.projectDirty = None |
1065 | | - QSettings().setValue("lastProject", "") |
1066 | | - |
1067 | | - # Clear datas |
1068 | | - self.loadEmptyDatas() |
1069 | | - self.saveTimer.stop() |
1070 | | - self.saveTimerNoChanges.stop() |
1071 | | - loadSave.clearSaveCache() |
1072 | | - |
1073 | | - self.breakConnections() |
1074 | | - |
1075 | | - # UI |
1076 | | - for i in [self.actOpen, self.menuRecents]: |
1077 | | - i.setEnabled(True) |
1078 | | - for i in [self.actSave, self.actSaveAs, self.actCloseProject, |
1079 | | - self.menuEdit, self.menuView, self.menuOrganize, |
1080 | | - self.menuTools, self.menuHelp, self.actImport, |
1081 | | - self.actCompile, self.actSettings]: |
1082 | | - i.setEnabled(False) |
1083 | | - |
1084 | | - # Set Window's name - no project loaded |
1085 | | - self.setWindowTitle(self.tr("Manuskript")) |
1086 | | - |
1087 | | - # Reload recent files |
1088 | | - self.welcome.updateValues() |
1089 | | - |
1090 | | - # Show welcome dialog |
1091 | | - self.switchToWelcome() |
1092 | | - |
1093 | 910 | def readSettings(self): |
1094 | 911 | # Load State and geometry |
1095 | 912 | sttgns = QSettings(qApp.organizationName(), qApp.applicationName()) |
@@ -1128,133 +945,6 @@ def readSettings(self): |
1128 | 945 | else: |
1129 | 946 | self._toolbarState = "" |
1130 | 947 |
|
1131 | | - def closeEvent(self, event): |
1132 | | - # Specific settings to save before quitting |
1133 | | - settings.lastTab = self.tabMain.currentIndex() |
1134 | | - |
1135 | | - if self.currentProject: |
1136 | | - # Remembering the current items (stores outlineItem's ID) |
1137 | | - settings.openIndexes = self.mainEditor.tabSplitter.openIndexes() |
1138 | | - |
1139 | | - # Call close on the main window to clean children widgets |
1140 | | - if self.mainEditor: |
1141 | | - self.mainEditor.close() |
1142 | | - |
1143 | | - # Save data from models |
1144 | | - if settings.saveOnQuit: |
1145 | | - self.saveDatas() |
1146 | | - elif not self.handleUnsavedChanges(): |
1147 | | - event.ignore() # user opted to cancel the close action |
1148 | | - |
1149 | | - # closeEvent |
1150 | | - # QMainWindow.closeEvent(self, event) # Causing segfaults? |
1151 | | - |
1152 | | - # Close non-modal windows if they are open. |
1153 | | - if self.td: |
1154 | | - self.td.close() |
1155 | | - if self.fw: |
1156 | | - self.fw.close() |
1157 | | - |
1158 | | - # User may have canceled close event, so make sure we indeed want to close. |
1159 | | - # This is necessary because self.updateDockVisibility() hides UI elements. |
1160 | | - if event.isAccepted(): |
1161 | | - # Save State and geometry and other things |
1162 | | - appSettings = QSettings(qApp.organizationName(), qApp.applicationName()) |
1163 | | - appSettings.setValue("geometry", self.saveGeometry()) |
1164 | | - appSettings.setValue("windowState", self.saveState()) |
1165 | | - appSettings.setValue("metadataState", self.redacMetadata.saveState()) |
1166 | | - appSettings.setValue("revisionsState", self.redacMetadata.revisions.saveState()) |
1167 | | - appSettings.setValue("splitterRedacH", self.splitterRedacH.saveState()) |
1168 | | - appSettings.setValue("splitterRedacV", self.splitterRedacV.saveState()) |
1169 | | - appSettings.setValue("toolbar", self.toolbar.saveState()) |
1170 | | - |
1171 | | - # If we are not in the welcome window, we update the visibility |
1172 | | - # of the docks widgets |
1173 | | - if self.stack.currentIndex() == 1: |
1174 | | - self.updateDockVisibility() |
1175 | | - |
1176 | | - # Storing the visibility of docks to restore it on restart |
1177 | | - appSettings.setValue("docks", self._dckVisibility) |
1178 | | - |
1179 | | - def startTimerNoChanges(self): |
1180 | | - """ |
1181 | | - Something changed in the project that requires auto-saving. |
1182 | | - """ |
1183 | | - self.projectDirty = True |
1184 | | - |
1185 | | - if settings.autoSaveNoChanges: |
1186 | | - self.saveTimerNoChanges.start() |
1187 | | - |
1188 | | - def saveDatas(self, projectName=None): |
1189 | | - """Saves the current project (in self.currentProject). |
1190 | | -
|
1191 | | - If ``projectName`` is given, currentProject becomes projectName. |
1192 | | - In other words, it "saves as...". |
1193 | | - """ |
1194 | | - |
1195 | | - if projectName: |
1196 | | - self.currentProject = projectName |
1197 | | - QSettings().setValue("lastProject", projectName) |
1198 | | - |
1199 | | - # Stop the timer before saving: if auto-saving fails (bugs out?) we don't want it |
1200 | | - # to keep trying and continuously hitting the failure condition. Nor do we want to |
1201 | | - # risk a scenario where the timer somehow triggers a new save while saving. |
1202 | | - self.saveTimerNoChanges.stop() |
1203 | | - |
1204 | | - if self.currentProject is None: |
1205 | | - # No UI feedback here as this code path indicates a race condition that happens |
1206 | | - # after the user has already closed the project through some way. But in that |
1207 | | - # scenario, this code should not be reachable to begin with. |
1208 | | - LOGGER.error("There is no current project to save.") |
1209 | | - return |
1210 | | - |
1211 | | - r = loadSave.saveProject() # version=0 |
1212 | | - |
1213 | | - projectName = os.path.basename(self.currentProject) |
1214 | | - if r: |
1215 | | - self.projectDirty = False # successful save, clear dirty flag |
1216 | | - |
1217 | | - feedback = self.tr("Project {} saved.").format(projectName) |
1218 | | - F.statusMessage(feedback, importance=0) |
1219 | | - LOGGER.info("Project {} saved.".format(projectName)) |
1220 | | - else: |
1221 | | - feedback = self.tr("WARNING: Project {} not saved.").format(projectName) |
1222 | | - F.statusMessage(feedback, importance=3) |
1223 | | - LOGGER.warning("Project {} not saved.".format(projectName)) |
1224 | | - |
1225 | | - def loadEmptyDatas(self): |
1226 | | - self.mdlFlatData = QStandardItemModel(self) |
1227 | | - self.mdlCharacter = characterModel(self) |
1228 | | - self.mdlLabels = QStandardItemModel(self) |
1229 | | - self.mdlStatus = QStandardItemModel(self) |
1230 | | - self.mdlPlots = plotModel(self) |
1231 | | - self.mdlOutline = outlineModel(self) |
1232 | | - self.mdlWorld = worldModel(self) |
1233 | | - |
1234 | | - def loadDatas(self, project): |
1235 | | - errors = loadSave.loadProject(project) |
1236 | | - |
1237 | | - # Giving some feedback |
1238 | | - if not errors: |
1239 | | - LOGGER.info("Project {} loaded.".format(project)) |
1240 | | - F.statusMessage( |
1241 | | - self.tr("Project {} loaded.").format(project), 2000) |
1242 | | - else: |
1243 | | - LOGGER.error("Project {} loaded with some errors:".format(project)) |
1244 | | - for e in errors: |
1245 | | - LOGGER.error(" * {} wasn't found in project file.".format(e)) |
1246 | | - F.statusMessage( |
1247 | | - self.tr("Project {} loaded with some errors.").format(project), 5000, importance = 3) |
1248 | | - |
1249 | | - if project in errors: |
1250 | | - LOGGER.error("Loading project {} failed.".format(project)) |
1251 | | - F.statusMessage( |
1252 | | - self.tr("Loading project {} failed.").format(project), 5000, importance = 3) |
1253 | | - |
1254 | | - return False |
1255 | | - |
1256 | | - return True |
1257 | | - |
1258 | 948 | ############################################################################### |
1259 | 949 | # MAIN CONNECTIONS |
1260 | 950 | ############################################################################### |
|
0 commit comments