diff --git a/bin/manuskript b/bin/manuskript
index 1fef8fe6..cedac928 100755
--- a/bin/manuskript
+++ b/bin/manuskript
@@ -14,5 +14,6 @@ from manuskript.ui import MainWindow
path = os.path.join(os.getcwd(), "sample-projects/book-of-acts")
-window = MainWindow(path + ".msk")
+window = MainWindow()
+window.openProject(path + ".msk")
window.run()
diff --git a/manuskript/data/abstractData.py b/manuskript/data/abstractData.py
index 00faad2f..bb34f7e2 100644
--- a/manuskript/data/abstractData.py
+++ b/manuskript/data/abstractData.py
@@ -20,6 +20,11 @@ class AbstractData:
self.dataPath = path
self.dataStatus = DataStatus.UNDEFINED
+ def changePath(self, path: str):
+ print("{} -> {}".format(self.dataPath, path))
+
+ self.dataPath = path
+
def complete(self, statusCompletion: bool = True):
if self.dataStatus == DataStatus.LOADING:
self.dataStatus = DataStatus.LOADED if statusCompletion else DataStatus.UNDEFINED
diff --git a/manuskript/data/characters.py b/manuskript/data/characters.py
index b416a8c9..e6f55eaa 100644
--- a/manuskript/data/characters.py
+++ b/manuskript/data/characters.py
@@ -37,6 +37,10 @@ class Character(AbstractData):
self.color = None
self.details = dict()
+ def changePath(self, path: str):
+ AbstractData.changePath(self, path)
+ self.file = MmdFile(self.dataPath, 21)
+
def allowPOV(self) -> bool:
return True if self.POV is None else self.POV
@@ -119,6 +123,15 @@ class Characters(AbstractData):
self.host = UniqueIDHost()
self.data = dict()
+ def changePath(self, path: str):
+ AbstractData.changePath(self, os.path.join(path, "characters"))
+
+ for character in self.data.values():
+ filename = safeFilename("%s-%s" % (str(character.UID), character.name), "txt")
+ path_ = os.path.join(self.dataPath, filename)
+
+ character.changePath(path_)
+
def __iter__(self):
return self.data.values().__iter__()
diff --git a/manuskript/data/info.py b/manuskript/data/info.py
index 32c29a80..612c85d1 100644
--- a/manuskript/data/info.py
+++ b/manuskript/data/info.py
@@ -22,6 +22,10 @@ class Info(AbstractData):
self.author = None
self.email = None
+ def changePath(self, path: str):
+ AbstractData.changePath(self, os.path.join(path, "infos.txt"))
+ self.file = MmdFile(self.dataPath, 16)
+
def load(self):
AbstractData.load(self)
diff --git a/manuskript/data/labels.py b/manuskript/data/labels.py
index bdeae335..15226153 100644
--- a/manuskript/data/labels.py
+++ b/manuskript/data/labels.py
@@ -35,6 +35,10 @@ class LabelHost(AbstractData):
self.file = MmdFile(self.dataPath, 21)
self.labels = collections.OrderedDict()
+ def changePath(self, path: str):
+ AbstractData.changePath(self, os.path.join(path, "labels.txt"))
+ self.file = MmdFile(self.dataPath, 21)
+
def addLabel(self, name: str = None, color: Color = None) -> Label:
if name is None:
name = "New Label"
diff --git a/manuskript/data/outline.py b/manuskript/data/outline.py
index fc6b23bb..fafa097e 100644
--- a/manuskript/data/outline.py
+++ b/manuskript/data/outline.py
@@ -13,7 +13,7 @@ from manuskript.data.plots import Plots
from manuskript.data.status import StatusHost
from manuskript.data.unique_id import UniqueIDHost
from manuskript.io.mmdFile import MmdFile
-from manuskript.util import CounterKind, countText, safeInt
+from manuskript.util import CounterKind, countText, safeInt, safeFilename
@unique
@@ -43,6 +43,10 @@ class OutlineItem(AbstractData):
self.compile = True
self.goal = None
+ def changePath(self, path: str):
+ AbstractData.changePath(self, path)
+ self.file = MmdFile(self.dataPath)
+
def parentItem(self):
for item in self.outline.all():
if item.contains(self):
@@ -94,8 +98,8 @@ class OutlineItem(AbstractData):
metadata["summaryFull"] = item.summaryFull
metadata["POV"] = item.POV
metadata["notes"] = item.notes
- metadata["label"] = None if item is None else item.label.ID
- metadata["status"] = None if item is None else item.status.ID
+ metadata["label"] = None if item.label is None else item.label.ID
+ metadata["status"] = None if item.status is None else item.status.ID
metadata["compile"] = item.compile
metadata["setGoal"] = item.goal
@@ -177,6 +181,19 @@ class OutlineFolder(OutlineItem):
self.folderPath = path
self.items = list()
+ def changePath(self, path: str):
+ OutlineItem.changePath(self, os.path.join(path, "folder.txt"))
+
+ self.folderPath = path
+
+ index = 0
+ for item in self.items:
+ filename = safeFilename("%s-%s" % (str(index), item.title), None if type(item) is OutlineFolder else "md")
+ path_ = os.path.join(self.folderPath, filename)
+
+ item.changePath(path_)
+ index += 1
+
def __iter__(self):
return self.items.__iter__()
@@ -250,6 +267,7 @@ class OutlineFolder(OutlineItem):
self.type = "folder"
OutlineItem.save(self)
+ os.makedirs(self.folderPath, exist_ok=True)
metadata = OutlineItem.saveMetadata(self)
self.file.save((metadata, "\n"))
@@ -267,6 +285,17 @@ class Outline(AbstractData):
self.items = list()
self.cache = dict()
+ def changePath(self, path: str):
+ AbstractData.changePath(self, os.path.join(path, "outline"))
+
+ index = 0
+ for item in self.items:
+ filename = safeFilename("%s-%s" % (str(index), item.title), None if type(item) is OutlineFolder else "md")
+ path_ = os.path.join(self.dataPath, filename)
+
+ item.changePath(path_)
+ index += 1
+
def __iter__(self):
return self.items.__iter__()
@@ -317,6 +346,10 @@ class Outline(AbstractData):
AbstractData.load(self)
+ if not os.path.isdir(self.dataPath):
+ self.complete(False)
+ return
+
names = os.listdir(self.dataPath)
names.sort()
diff --git a/manuskript/data/plots.py b/manuskript/data/plots.py
index fb8e8336..9399f156 100644
--- a/manuskript/data/plots.py
+++ b/manuskript/data/plots.py
@@ -113,6 +113,10 @@ class Plots(AbstractData):
self.characters = characters
self.lines = dict()
+ def changePath(self, path: str):
+ AbstractData.changePath(self, os.path.join(path, "plots.xml"))
+ self.file = XmlFile(self.dataPath)
+
def addLine(self, name: str = None, importance: Importance = Importance.MINOR):
line = PlotLine(self, self.host.newID(), name, importance)
self.lines[line.UID.value] = line
diff --git a/manuskript/data/project.py b/manuskript/data/project.py
index 57c30f73..93ca9fc8 100644
--- a/manuskript/data/project.py
+++ b/manuskript/data/project.py
@@ -29,7 +29,8 @@ from manuskript.util import profileTime
class Project(AbstractData):
- def __init__(self, path):
+
+ def __init__(self, path: str):
AbstractData.__init__(self, path)
self.file = MskFile(self.dataPath)
@@ -68,6 +69,25 @@ class Project(AbstractData):
def upgradeVersion(self):
self.version.value = CURRENT_MSK_VERSION
+ def changePath(self, path: str):
+ AbstractData.changePath(self, path)
+ saveToZip = self.settings.isEnabled("saveToZip")
+
+ self.file = MskFile(self.dataPath, ignorePath=True, forceZip=saveToZip)
+ os.makedirs(self.file.directoryPath, exist_ok=True)
+
+ self.version.changePath(self.file.directoryPath)
+ self.info.changePath(self.file.directoryPath)
+ self.summary.changePath(self.file.directoryPath)
+ self.labels.changePath(self.file.directoryPath)
+ self.statuses.changePath(self.file.directoryPath)
+ self.settings.changePath(self.file.directoryPath)
+ self.characters.changePath(self.file.directoryPath)
+ self.plots.changePath(self.file.directoryPath)
+ self.world.changePath(self.file.directoryPath)
+ self.outline.changePath(self.file.directoryPath)
+ self.revisions.changePath(self.file.directoryPath)
+
def load(self):
AbstractData.load(self)
diff --git a/manuskript/data/revisions.py b/manuskript/data/revisions.py
index b39264ad..9ce19444 100644
--- a/manuskript/data/revisions.py
+++ b/manuskript/data/revisions.py
@@ -43,6 +43,10 @@ class Revisions(AbstractData):
self.file = XmlFile(self.dataPath)
self.outline = dict()
+ def changePath(self, path: str):
+ AbstractData.changePath(self, os.path.join(path, "revisions.xml"))
+ self.file = XmlFile(self.dataPath)
+
def __iter__(self):
return self.outline.values().__iter__()
diff --git a/manuskript/data/settings.py b/manuskript/data/settings.py
index f1e0c807..625c4bbd 100644
--- a/manuskript/data/settings.py
+++ b/manuskript/data/settings.py
@@ -17,6 +17,10 @@ class Settings(AbstractData):
if initDefault:
Settings.loadDefaultSettings(self)
+ def changePath(self, path: str):
+ AbstractData.changePath(self, os.path.join(path, "settings.txt"))
+ self.file = JsonFile(self.dataPath)
+
def get(self, key: str):
props = self.properties
path = key.split(".")
diff --git a/manuskript/data/status.py b/manuskript/data/status.py
index c972acdf..061de807 100644
--- a/manuskript/data/status.py
+++ b/manuskript/data/status.py
@@ -33,6 +33,10 @@ class StatusHost(AbstractData):
self.file = TextFile(self.dataPath)
self.statuses = collections.OrderedDict()
+ def changePath(self, path: str):
+ AbstractData.changePath(self, os.path.join(path, "status.txt"))
+ self.file = TextFile(self.dataPath)
+
def addStatus(self, name: str = None) -> Status:
if name is None:
name = "New Status"
diff --git a/manuskript/data/summary.py b/manuskript/data/summary.py
index 5bf0da66..f79a8c4d 100644
--- a/manuskript/data/summary.py
+++ b/manuskript/data/summary.py
@@ -19,6 +19,10 @@ class Summary(AbstractData):
self.page = None
self.full = None
+ def changePath(self, path: str):
+ AbstractData.changePath(self, os.path.join(path, "summary.txt"))
+ self.file = MmdFile(self.dataPath, 13)
+
def load(self):
AbstractData.load(self)
diff --git a/manuskript/data/version.py b/manuskript/data/version.py
index 7f8c2dc5..ffc5fae0 100644
--- a/manuskript/data/version.py
+++ b/manuskript/data/version.py
@@ -19,6 +19,11 @@ class Version(AbstractData):
self.value = LEGACY_MSK_VERSION
+ def changePath(self, path: str):
+ AbstractData.changePath(self, os.path.join(path, "MANUSKRIPT"))
+ self.file = TextFile(self.dataPath)
+ self.legacy_file = TextFile(os.path.join(path, "VERSION"))
+
def loadLegacy(self):
try:
return int(self.legacy_file.load())
diff --git a/manuskript/data/world.py b/manuskript/data/world.py
index 17c9bc4b..e04e44ab 100644
--- a/manuskript/data/world.py
+++ b/manuskript/data/world.py
@@ -45,6 +45,10 @@ class World(AbstractData):
self.items = dict()
self.top = list()
+ def changePath(self, path: str):
+ AbstractData.changePath(self, os.path.join(path, "world.opml"))
+ self.file = OpmlFile(self.dataPath)
+
def addItem(self, name: str = None, parent: WorldItem = None) -> WorldItem:
item = WorldItem(self, self.host.newID(), name)
diff --git a/manuskript/io/mskFile.py b/manuskript/io/mskFile.py
index a6bbe9b7..46ffdad5 100644
--- a/manuskript/io/mskFile.py
+++ b/manuskript/io/mskFile.py
@@ -13,14 +13,16 @@ from manuskript.data.version import LEGACY_MSK_VERSION
class MskFile(TextFile, ZipFile):
- def __init__(self, path):
+ def __init__(self, path, ignorePath: bool = False, forceZip: bool = False):
try:
- _ZipFile(path)
+ if not forceZip:
+ _ZipFile(path)
+
directoryPath = None
- except BadZipFile:
+ except (BadZipFile, FileNotFoundError):
directoryPath = os.path.splitext(path)[0]
- if not os.path.isdir(directoryPath):
+ if (not ignorePath) and (not os.path.isdir(directoryPath)):
directoryPath = None
self.zipFile = directoryPath is None
@@ -47,7 +49,8 @@ class MskFile(TextFile, ZipFile):
if not os.path.isdir(self.directoryPath):
os.mkdir(self.directoryPath)
- ZipFile.load(self)
+ if os.path.exists(self.path):
+ ZipFile.load(self)
self.zipFile = zipFile
diff --git a/manuskript/ui/chooser/__init__.py b/manuskript/ui/chooser/__init__.py
new file mode 100644
index 00000000..8bdda90c
--- /dev/null
+++ b/manuskript/ui/chooser/__init__.py
@@ -0,0 +1,68 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+import gi
+
+gi.require_version('Gdk', '3.0')
+
+from gi.repository import GObject, Gtk
+
+from manuskript.ui.chooser.fileFilter import FileFilter
+
+
+def openFileDialog(window, fileFilter_: FileFilter = None, appendAllFilter: bool = True) -> str | None:
+ dialog = Gtk.FileChooserDialog(
+ "Please choose a file",
+ window,
+ Gtk.FileChooserAction.OPEN,
+ (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
+ Gtk.STOCK_OPEN, Gtk.ResponseType.OK)
+ )
+
+ dialog.set_default_response(Gtk.ResponseType.OK)
+
+ if fileFilter_ is not None:
+ fileFilter_.addToChooser(dialog)
+
+ if appendAllFilter:
+ FileFilter("All files").addToChooser(dialog)
+
+ response = dialog.run()
+ result = None
+
+ if response == Gtk.ResponseType.OK:
+ result = dialog.get_filename()
+
+ dialog.destroy()
+ return result
+
+
+def saveFileDialog(window, fileFilter_: FileFilter = None, appendAllFilter: bool = True) -> str | None:
+ dialog = Gtk.FileChooserDialog(
+ "Please choose a file",
+ window,
+ Gtk.FileChooserAction.SAVE,
+ (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
+ Gtk.STOCK_SAVE, Gtk.ResponseType.OK)
+ )
+
+ dialog.set_default_response(Gtk.ResponseType.OK)
+
+ if fileFilter_ is not None:
+ fileFilter_.addToChooser(dialog)
+
+ if appendAllFilter:
+ FileFilter("All files").addToChooser(dialog)
+
+ response = dialog.run()
+ result = None
+
+ if response == Gtk.ResponseType.OK:
+ result = dialog.get_filename()
+
+ if ((fileFilter_ is not None) and (fileFilter_.name == dialog.get_filter().get_name()) and
+ (not result.endswith("." + fileFilter_.extension))):
+ result += "." + fileFilter_.extension
+
+ dialog.destroy()
+ return result
diff --git a/manuskript/ui/chooser/fileFilter.py b/manuskript/ui/chooser/fileFilter.py
new file mode 100644
index 00000000..192ee2ef
--- /dev/null
+++ b/manuskript/ui/chooser/fileFilter.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+import gi
+
+gi.require_version('Gdk', '3.0')
+
+from gi.repository import GObject, Gtk
+
+
+class FileFilter:
+
+ def __init__(self, name: str, extension: str = ""):
+ self.name = name
+ self.extension = extension
+
+ def addToChooser(self, chooser: Gtk.FileChooser):
+ fileFilter = Gtk.FileFilter()
+ fileFilter.set_name(self.name)
+ fileFilter.add_pattern("*.{}".format(self.extension) if len(self.extension) > 0 else "*")
+
+ chooser.add_filter(fileFilter)
diff --git a/manuskript/ui/mainWindow.py b/manuskript/ui/mainWindow.py
index 78e863c7..acaed049 100644
--- a/manuskript/ui/mainWindow.py
+++ b/manuskript/ui/mainWindow.py
@@ -13,40 +13,19 @@ Handy.init()
from manuskript.data import Project
from manuskript.ui.views import *
+from manuskript.ui.chooser import openFileDialog, saveFileDialog, FileFilter
from manuskript.ui.tools import *
from manuskript.ui.aboutDialog import AboutDialog
from manuskript.ui.settingsWindow import SettingsWindow
from manuskript.ui.startupWindow import StartupWindow
-from manuskript.ui.util import bindMenuItem
-from manuskript.util import profileTime
+from manuskript.ui.util import bindMenuItem, packViewIntoSlot, unpackFromSlot
+from manuskript.util import parseFilenameFromURL
class MainWindow:
- @classmethod
- def packViewIntoSlot(cls, builder, id, view_cls, data=None):
- slot = builder.get_object(id)
-
- if slot is None:
- return None
-
- try:
- if data is None:
- view = profileTime(view_cls)
- else:
- view = profileTime(view_cls, data)
- except Exception:
- return None
-
- if view.widget is None:
- return None
-
- slot.pack_start(view.widget, True, True, 0)
- return view
-
- def __init__(self, path):
- self.project = Project(path)
- self.project.load()
+ def __init__(self):
+ self.project = None
builder = Gtk.Builder()
builder.add_from_file("ui/main.glade")
@@ -58,19 +37,25 @@ class MainWindow:
self.leaflet = builder.get_object("leaflet")
self.viewSwitcherBar = builder.get_object("view_switcher_bar")
- self.headerBar.set_subtitle(self.project.info.title)
-
self.leaflet.bind_property("folded", self.viewSwitcherBar, "reveal", GObject.BindingFlags.SYNC_CREATE)
self.leaflet.bind_property("folded", self.headerBar, "show-close-button", GObject.BindingFlags.SYNC_CREATE |
GObject.BindingFlags.INVERT_BOOLEAN)
- self.generalView = MainWindow.packViewIntoSlot(builder, "general_slot", GeneralView, self.project.info)
- self.summaryView = MainWindow.packViewIntoSlot(builder, "summary_slot", SummaryView, self.project.summary)
- self.charactersView = MainWindow.packViewIntoSlot(builder, "characters_slot", CharactersView, self.project)
- self.plotView = MainWindow.packViewIntoSlot(builder, "plot_slot", PlotView, self.project.plots)
- self.worldView = MainWindow.packViewIntoSlot(builder, "world_slot", WorldView, self.project.world)
- self.outlineView = MainWindow.packViewIntoSlot(builder, "outline_slot", OutlineView, self.project.outline)
- self.editorView = MainWindow.packViewIntoSlot(builder, "editor_slot", EditorView, self.project)
+ self.generalSlot = builder.get_object("general_slot")
+ self.summarySlot = builder.get_object("summary_slot")
+ self.charactersSlot = builder.get_object("characters_slot")
+ self.plotSlot = builder.get_object("plot_slot")
+ self.worldSlot = builder.get_object("world_slot")
+ self.outlineSlot = builder.get_object("outline_slot")
+ self.editorSlot = builder.get_object("editor_slot")
+
+ self.generalView = None
+ self.summaryView = None
+ self.charactersView = None
+ self.plotView = None
+ self.worldView = None
+ self.outlineView = None
+ self.editorView = None
self.startupWindow = StartupWindow(self)
self.aboutDialog = AboutDialog(self)
@@ -84,8 +69,12 @@ class MainWindow:
self.settingsWindow
]
+ self.recentChooserMenu = builder.get_object("recent_chooser_menu")
+ self.recentChooserMenu.connect("item-activated", self._recentAction)
+
bindMenuItem(builder, "open_menu_item", self._openAction)
bindMenuItem(builder, "save_menu_item", self._saveAction)
+ bindMenuItem(builder, "saveas_menu_item", self._saveAsAction)
bindMenuItem(builder, "close_menu_item", self._closeAction)
bindMenuItem(builder, "quit_menu_item", self._quitAction)
@@ -94,21 +83,78 @@ class MainWindow:
bindMenuItem(builder, "character_details_template_editor", self._characterDetailsTemplateEditorAction)
bindMenuItem(builder, "about_menu_item", self._aboutAction)
+ self.hide()
+
def getProject(self):
return self.project
- def openProject(self):
- pass
+ def openProject(self, path=None):
+ if self.project is not None:
+ self.closeProject()
+
+ if path is None:
+ return
+
+ self.project = Project(path)
+ self.project.load()
+
+ self.headerBar.set_subtitle(self.project.info.title)
+
+ self.generalView = packViewIntoSlot(self.generalSlot, GeneralView, self.project.info)
+ self.summaryView = packViewIntoSlot(self.summarySlot, SummaryView, self.project.summary)
+ self.charactersView = packViewIntoSlot(self.charactersSlot, CharactersView, self.project.characters)
+ self.plotView = packViewIntoSlot(self.plotSlot, PlotView, self.project.plots)
+ self.worldView = packViewIntoSlot(self.worldSlot, WorldView, self.project.world)
+ self.outlineView = packViewIntoSlot(self.outlineSlot, OutlineView, self.project.outline)
+ self.editorView = packViewIntoSlot(self.editorSlot, EditorView, self.project)
+
+ self.startupWindow.hide()
+ self.show()
def closeProject(self):
+ if self.project is not None:
+ self.generalView = unpackFromSlot(self.generalSlot, self.generalView)
+ self.summaryView = unpackFromSlot(self.summarySlot, self.summaryView)
+ self.charactersView = unpackFromSlot(self.charactersSlot, self.charactersView)
+ self.plotView = unpackFromSlot(self.plotSlot, self.plotView)
+ self.worldView = unpackFromSlot(self.worldSlot, self.worldView)
+ self.outlineView = unpackFromSlot(self.outlineSlot, self.outlineView)
+ self.editorView = unpackFromSlot(self.editorSlot, self.editorView)
+
+ del self.project
+ self.project = None
+
self.hide()
self.startupWindow.show()
def _openAction(self, menuItem: Gtk.MenuItem):
- self.openProject()
+ path = openFileDialog(self.window, FileFilter("Manuskript project", "msk"))
+ if path is None:
+ return
+
+ self.openProject(path)
+
+ def _recentAction(self, recentChooser: Gtk.RecentChooser):
+ uri = recentChooser.get_current_uri()
+ if uri is None:
+ return
+
+ path = parseFilenameFromURL(uri)
+ if path is None:
+ return
+
+ self.openProject(path)
def _saveAction(self, menuItem: Gtk.MenuItem):
- self.getProject().save()
+ self.project.save()
+
+ def _saveAsAction(self, menuItem: Gtk.MenuItem):
+ path = saveFileDialog(self.window, FileFilter("Manuskript project", "msk"))
+ if path is None:
+ return
+
+ self.project.changePath(path)
+ self.project.save()
def _closeAction(self, menuItem: Gtk.MenuItem):
self.closeProject()
@@ -117,7 +163,7 @@ class MainWindow:
self.exit(True)
def getSettings(self):
- return self.getProject().settings
+ return self.project.settings
def _settingsAction(self, menuItem: Gtk.MenuItem):
self.settingsWindow.show()
diff --git a/manuskript/ui/startupWindow.py b/manuskript/ui/startupWindow.py
index cb030e7e..53bc1e66 100644
--- a/manuskript/ui/startupWindow.py
+++ b/manuskript/ui/startupWindow.py
@@ -6,10 +6,11 @@ import gi
gi.require_version("Gtk", "3.0")
from gi.repository import GObject, Gtk, Handy
-from manuskript.data import Template, TemplateLevel, TemplateKind
-from manuskript.util import validInt, validString
+from manuskript.data import Project, Template, TemplateKind
+from manuskript.util import validInt, validString, parseFilenameFromURL
from manuskript.ui.abstractDialog import AbstractDialog
+from manuskript.ui.chooser import openFileDialog, saveFileDialog, FileFilter
from manuskript.ui.startup import TemplateEntry
from manuskript.ui.util import bindMenuItem
@@ -25,6 +26,9 @@ class StartupWindow(AbstractDialog):
self.headerBar = None
self.templatesLeaflet = None
+ self.recentChooserMenu = None
+ self.recentChooserMenuBtn = None
+
self.templatesStore = None
self.fictionTemplatesStore = None
self.nonfictionTemplatesStore = None
@@ -37,6 +41,10 @@ class StartupWindow(AbstractDialog):
self.addLevelButton = None
self.addGoalButton = None
+ self.openButton = None
+ self.recentButton = None
+ self.createButton = None
+
def initWindow(self, builder, window):
self.headerBar = builder.get_object("header_bar")
self.templatesLeaflet = builder.get_object("templates_leaflet")
@@ -45,6 +53,11 @@ class StartupWindow(AbstractDialog):
GObject.BindingFlags.SYNC_CREATE |
GObject.BindingFlags.INVERT_BOOLEAN)
+ self.recentChooserMenu = builder.get_object("recent_chooser_menu")
+ self.recentChooserMenuBtn = builder.get_object("recent_chooser_menu_btn")
+
+ self.recentChooserMenu.connect("item-activated", self._recentAction)
+ self.recentChooserMenuBtn.connect("item-activated", self._recentAction)
bindMenuItem(builder, "open_menu_item", self._openAction)
bindMenuItem(builder, "quit_menu_item", self._quitAction)
@@ -98,6 +111,13 @@ class StartupWindow(AbstractDialog):
self.addLevelButton.connect("clicked", self._addLevelClicked)
self.addGoalButton.connect("clicked", self._addGoalClicked)
+ self.openButton = builder.get_object("open_button")
+ self.recentButton = builder.get_object("recent_button")
+ self.createButton = builder.get_object("create_button")
+
+ self.openButton.connect("clicked", self._openClicked)
+ self.createButton.connect("clicked", self._createClicked)
+
def loadTemplate(self, template: Template):
self.template = template
self.templateLevelsListbox.foreach(lambda child: self.templateLevelsListbox.remove(child))
@@ -152,8 +172,40 @@ class StartupWindow(AbstractDialog):
self.template.addGoal()
self.loadTemplate(self.template)
+ def openProject(self):
+ path = openFileDialog(self.window, FileFilter("Manuskript project", "msk"))
+ if path is None:
+ return
+
+ self.mainWindow.openProject(path)
+
+ def _openClicked(self, button: Gtk.Button):
+ self.openProject()
+
+ def _createClicked(self, button: Gtk.Button):
+ path = saveFileDialog(self.window, FileFilter("Manuskript project", "msk"), appendAllFilter=False)
+ if path is None:
+ return
+
+ project = Project(path)
+ # TODO: apply project template!
+ project.save()
+
+ self.mainWindow.openProject(path)
+
def _openAction(self, menuItem: Gtk.MenuItem):
- self.mainWindow.openProject()
+ self.openProject()
+
+ def _recentAction(self, recentChooser: Gtk.RecentChooser):
+ uri = recentChooser.get_current_uri()
+ if uri is None:
+ return
+
+ path = parseFilenameFromURL(uri)
+ if path is None:
+ return
+
+ self.mainWindow.openProject(path)
def _quitAction(self, menuItem: Gtk.MenuItem):
self.mainWindow.exit(True)
diff --git a/manuskript/ui/util.py b/manuskript/ui/util.py
index 8b3ed0b3..c31661e1 100644
--- a/manuskript/ui/util.py
+++ b/manuskript/ui/util.py
@@ -8,6 +8,7 @@ gi.require_version('GdkPixbuf', '2.0')
from gi.repository import GdkPixbuf, Gdk
from manuskript.data import Color, OutlineItem, OutlineText, OutlineFolder
+from manuskript.util import profileTime
def rgbaFromColor(color: Color) -> Gdk.RGBA:
@@ -34,6 +35,36 @@ def bindMenuItem(builder, id, action):
menuItem.connect("activate", action)
+def packViewIntoSlot(slot, view_cls, data=None):
+ if slot is None:
+ return None
+
+ for child in slot.get_children():
+ slot.remove(child)
+
+ try:
+ if data is None:
+ view = profileTime(view_cls)
+ else:
+ view = profileTime(view_cls, data)
+ except Exception:
+ return None
+
+ if view.widget is None:
+ return None
+
+ slot.pack_start(view.widget, True, True, 0)
+ return view
+
+
+def unpackFromSlot(slot, view):
+ if (slot is not None) and (view.widget is not None):
+ slot.remove(view.widget)
+
+ del view
+ return None
+
+
def iconByOutlineItemType(outlineItem: OutlineItem) -> str:
if type(outlineItem) is OutlineFolder:
return "folder-symbolic"
diff --git a/manuskript/util/__init__.py b/manuskript/util/__init__.py
index c5f2bd06..5023f689 100644
--- a/manuskript/util/__init__.py
+++ b/manuskript/util/__init__.py
@@ -4,6 +4,7 @@
import re
import time
import traceback
+import urllib.parse
from manuskript.util.counter import CounterKind, CharCounter, WordCounter, PageCounter
@@ -51,6 +52,18 @@ def safeFilename(filename: str, extension: str = None) -> str:
return re.sub(r"[^a-zA-Z0-9._\-+()]", "_", name)
+def parseFilenameFromURL(url: str) -> str | None:
+ result = urllib.parse.urlparse(url)
+
+ if result is None:
+ return None
+
+ if result.scheme != "file":
+ return None
+
+ return result.path
+
+
def countText(text: str, kind: CounterKind = CounterKind.WORDS):
if text is None:
return 0
diff --git a/ui/characters.glade b/ui/characters.glade
index 327434c9..20ca14a9 100644
--- a/ui/characters.glade
+++ b/ui/characters.glade
@@ -1175,6 +1175,7 @@ summary
2
+
+
False
diff --git a/ui/startup.glade b/ui/startup.glade
index 33d4ce5c..48c34705 100644
--- a/ui/startup.glade
+++ b/ui/startup.glade
@@ -8,7 +8,7 @@
*.msk
-
-