From 9287e85b7ecb36440a038210d21e031d6ea98aca Mon Sep 17 00:00:00 2001 From: TheJackiMonster Date: Tue, 21 Mar 2023 20:51:05 +0100 Subject: [PATCH] Implement recent projects menu Signed-off-by: TheJackiMonster --- bin/manuskript | 3 +- manuskript/ui/mainWindow.py | 112 +++++++++++++++++++++------------ manuskript/ui/startupWindow.py | 20 +++++- manuskript/ui/util.py | 31 +++++++++ manuskript/util/__init__.py | 13 ++++ ui/characters.glade | 14 ----- 6 files changed, 137 insertions(+), 56 deletions(-) diff --git a/bin/manuskript b/bin/manuskript index 1fef8fe..cedac92 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/ui/mainWindow.py b/manuskript/ui/mainWindow.py index ec51b2a..aec7e14 100644 --- a/manuskript/ui/mainWindow.py +++ b/manuskript/ui/mainWindow.py @@ -17,36 +17,14 @@ 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,20 +36,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.characters) - 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) @@ -85,6 +68,9 @@ 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, "close_menu_item", self._closeAction) @@ -94,18 +80,66 @@ class MainWindow: bindMenuItem(builder, "frequency_menu_item", self._frequencyAction) bindMenuItem(builder, "about_menu_item", self._aboutAction) + self.hide() + def getProject(self): return self.project - def openProject(self): - pass + def openProject(self, path=None, dialog=False): + if self.project is not None: + self.closeProject() + + if dialog: + return + + 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() + self.openProject(dialog=True) + + 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() diff --git a/manuskript/ui/startupWindow.py b/manuskript/ui/startupWindow.py index 1300949..cd98323 100644 --- a/manuskript/ui/startupWindow.py +++ b/manuskript/ui/startupWindow.py @@ -6,8 +6,8 @@ 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 Template, TemplateKind +from manuskript.util import validInt, validString, parseFilenameFromURL from manuskript.ui.abstractDialog import AbstractDialog from manuskript.ui.startup import TemplateEntry @@ -25,6 +25,8 @@ class StartupWindow(AbstractDialog): self.headerBar = None self.templatesLeaflet = None + self.recentChooserMenu = None + self.templatesStore = None self.fictionTemplatesStore = None self.nonfictionTemplatesStore = None @@ -45,6 +47,9 @@ class StartupWindow(AbstractDialog): GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.INVERT_BOOLEAN) + self.recentChooserMenu = builder.get_object("recent_chooser_menu") + self.recentChooserMenu.connect("item-activated", self._recentAction) + bindMenuItem(builder, "open_menu_item", self._openAction) bindMenuItem(builder, "quit_menu_item", self._quitAction) @@ -154,6 +159,17 @@ class StartupWindow(AbstractDialog): def _openAction(self, menuItem: Gtk.MenuItem): self.mainWindow.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 8b3ed0b..c31661e 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 c5f2bd0..5023f68 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 e17aae5..6a2d588 100644 --- a/ui/characters.glade +++ b/ui/characters.glade @@ -923,20 +923,6 @@ summary 2 - - - Next - True - True - True - - - False - True - end - 3 - - False