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 + Next @@ -1210,6 +1211,7 @@ summary 4 + 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 - + True False recent_filter @@ -91,9 +91,10 @@ _Recent True - + True False + recent_filter 10 mru @@ -457,7 +458,7 @@ end 9 - + True True True @@ -500,12 +501,12 @@ - + True True False True - recent_chooser_menu + recent_chooser_menu_btn False @@ -546,7 +547,7 @@ - + True True True