diff --git a/manuskript/data/characters.py b/manuskript/data/characters.py index d8a87632..cea33843 100644 --- a/manuskript/data/characters.py +++ b/manuskript/data/characters.py @@ -8,6 +8,7 @@ from manuskript.data.color import Color from manuskript.data.importance import Importance from manuskript.data.unique_id import UniqueIDHost from manuskript.io.mmdFile import MmdFile +from manuskript.util import safeFilename class Character: @@ -34,6 +35,12 @@ class Character: def allowPOV(self) -> bool: return True if self.POV is None else self.POV + def remove(self): + if self.UID is None: + return + + self.characters.removeByID(self.UID.value) + @classmethod def loadAttribute(cls, metadata: dict, name: str, defaultValue=None): if name in metadata: @@ -104,14 +111,37 @@ class Characters: def __iter__(self): return self.data.values().__iter__() + def add(self, name: str = None) -> Character | None: + if name is None: + name = "New character" + + UID = self.host.newID() + filename = safeFilename("%s-%s" % (str(UID), name), "txt") + + path = os.path.join(self.dir_path, filename) + + if os.path.exists(filename): + return None + + character = Character(path, self) + character.UID = UID + character.name = name + + self.data[character.UID.value] = character + + return character + def getByID(self, ID: int) -> Character: return self.data.get(ID, None) + def removeByID(self, ID: int): + self.data.pop(ID) + def load(self): self.data.clear() - for name in os.listdir(self.dir_path): - path = os.path.join(self.dir_path, name) + for filename in os.listdir(self.dir_path): + path = os.path.join(self.dir_path, filename) if not os.path.isfile(path): continue diff --git a/manuskript/data/color.py b/manuskript/data/color.py index fc89699a..59fa8159 100644 --- a/manuskript/data/color.py +++ b/manuskript/data/color.py @@ -19,6 +19,9 @@ class Color: @classmethod def parse(cls, string: str): + if string is None: + return None + colorPattern = re.compile(r"\#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})") m = colorPattern.match(string) diff --git a/manuskript/ui/util.py b/manuskript/ui/util.py index 8fbe1c99..898c62b9 100644 --- a/manuskript/ui/util.py +++ b/manuskript/ui/util.py @@ -12,19 +12,16 @@ from manuskript.data import Color def rgbaFromColor(color: Color) -> Gdk.RGBA: rgba = Gdk.RGBA() - rgba.red = color.red / 255. - rgba.green = color.green / 255. - rgba.blue = color.blue / 255. + rgba.red = 0 if color is None else color.red / 255. + rgba.green = 0 if color is None else color.green / 255. + rgba.blue = 0 if color is None else color.blue / 255. rgba.alpha = 1. return rgba def pixbufFromColor(color: Color) -> GdkPixbuf: - if color is None: - return None - pixbuf = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB, False, 8, 16, 16) - pixbuf.fill(color.getRGB()) + pixbuf.fill(0 if color is None else color.getRGB()) return pixbuf diff --git a/manuskript/ui/views/charactersView.py b/manuskript/ui/views/charactersView.py index 3f001d16..83d7cfe8 100644 --- a/manuskript/ui/views/charactersView.py +++ b/manuskript/ui/views/charactersView.py @@ -23,17 +23,7 @@ class CharactersView: self.widget = builder.get_object("characters_view") self.charactersStore = builder.get_object("characters_store") - - for character in self.characters: - tree_iter = self.charactersStore.append() - - if tree_iter is None: - continue - - self.charactersStore.set_value(tree_iter, 0, character.UID.value) - self.charactersStore.set_value(tree_iter, 1, validString(character.name)) - self.charactersStore.set_value(tree_iter, 2, pixbufFromColor(character.color)) - self.charactersStore.set_value(tree_iter, 3, Importance.asValue(character.importance)) + self.refreshCharacterStore() self.mainCharactersStore = builder.get_object("main_characters_store") self.secondaryCharactersStore = builder.get_object("secondary_characters_store") @@ -56,6 +46,16 @@ class CharactersView: for selection in self.characterSelections: selection.connect("changed", self.characterSelectionChanged) + self.addCharacterButton = builder.get_object("add_character") + self.removeCharacterButton = builder.get_object("remove_character") + self.filterCharactersBuffer = builder.get_object("filter_characters") + + self.addCharacterButton.connect("clicked", self.addCharacterClicked) + self.removeCharacterButton.connect("clicked", self.removeCharacterClicked) + + self.filterCharactersBuffer.connect("deleted-text", self.filterCharactersDeletedText) + self.filterCharactersBuffer.connect("inserted-text", self.filterCharactersInsertedText) + self.colorButton = builder.get_object("color") self.importanceCombo = builder.get_object("importance") self.allowPOVCheck = builder.get_object("allow_POV") @@ -100,6 +100,20 @@ class CharactersView: self.unloadCharacterData() + def refreshCharacterStore(self): + self.charactersStore.clear() + + for character in self.characters: + tree_iter = self.charactersStore.append() + + if tree_iter is None: + continue + + self.charactersStore.set_value(tree_iter, 0, character.UID.value) + self.charactersStore.set_value(tree_iter, 1, validString(character.name)) + self.charactersStore.set_value(tree_iter, 2, pixbufFromColor(character.color)) + self.charactersStore.set_value(tree_iter, 3, Importance.asValue(character.importance)) + def loadCharacterData(self, character: Character): self.character = None @@ -166,6 +180,30 @@ class CharactersView: else: self.loadCharacterData(character) + def addCharacterClicked(self, button: Gtk.Button): + character = self.characters.add() + + if character is None: + return + + self.refreshCharacterStore() + + def removeCharacterClicked(self, button: Gtk.Button): + if self.character is None: + return + + self.character.remove() + self.refreshCharacterStore() + + def filterCharactersChanged(self, buffer: Gtk.EntryBuffer): + pass + + def filterCharactersDeletedText(self, buffer: Gtk.EntryBuffer, position: int, n_chars: int): + self.filterCharactersChanged(buffer) + + def filterCharactersInsertedText(self, buffer: Gtk.EntryBuffer, position: int, chars: str, n_chars: int): + self.filterCharactersChanged(buffer) + def colorSet(self, button: Gtk.ColorButton): if self.character is None: return diff --git a/manuskript/util/__init__.py b/manuskript/util/__init__.py index 352ed120..db2d5c13 100644 --- a/manuskript/util/__init__.py +++ b/manuskript/util/__init__.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +import re from manuskript.util.counter import CharCounter, WordCounter, PageCounter @@ -28,3 +29,20 @@ def validInt(invalid: int) -> int: def invalidInt(valid: int) -> int: return None if valid == 0 else valid + + +def safeFilename(filename: str, extension: str = None) -> str: + if extension is not None: + filename = "%s.%s" % (filename, extension) + + name = filename.encode('ascii', 'replace').decode('ascii') + filenamesToAvoid = list(["CON", "PRN", "AUX", "NUL"]) + + for i in range(1, 9): + filenamesToAvoid.append("COM" + str(i)) + filenamesToAvoid.append("LPT" + str(i)) + + if name.upper() in filenamesToAvoid: + name = "_" + name + + return re.sub(r"[^a-zA-Z0-9._\-+()]", "_", name) diff --git a/ui/characters.glade b/ui/characters.glade index 5fceffcc..e5ff1ab2 100644 --- a/ui/characters.glade +++ b/ui/characters.glade @@ -1,5 +1,5 @@ -