diff --git a/manuskript/data/__init__.py b/manuskript/data/__init__.py index 6587fcc2..839d0f9a 100644 --- a/manuskript/data/__init__.py +++ b/manuskript/data/__init__.py @@ -4,6 +4,7 @@ from manuskript.data.characters import Characters, Character from manuskript.data.color import Color from manuskript.data.goal import GoalKind, Goal +from manuskript.data.importance import Importance from manuskript.data.info import Info from manuskript.data.labels import LabelHost, Label from manuskript.data.outline import Outline, OutlineFolder, OutlineText diff --git a/manuskript/data/characters.py b/manuskript/data/characters.py index a78ac18b..d8a87632 100644 --- a/manuskript/data/characters.py +++ b/manuskript/data/characters.py @@ -5,6 +5,7 @@ import os from collections import OrderedDict 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 @@ -48,9 +49,11 @@ class Character: if ID is None: raise IOError("Character is missing ID!") + importance = Character.loadAttribute(metadata, "Importance", None) + self.UID = self.characters.host.loadID(int(ID)) self.name = Character.loadAttribute(metadata, "Name", None) - self.importance = Character.loadAttribute(metadata, "Importance", None) + self.importance = Importance.fromRawString(importance) self.POV = Character.loadAttribute(metadata, "POV", None) self.motivation = Character.loadAttribute(metadata, "Motivation", None) self.goal = Character.loadAttribute(metadata, "Goal", None) @@ -72,7 +75,7 @@ class Character: metadata["Name"] = self.name metadata["ID"] = str(self.UID.value) - metadata["Importance"] = self.importance + metadata["Importance"] = Importance.toRawString(self.importance) metadata["POV"] = self.POV metadata["Motivation"] = self.motivation metadata["Goal"] = self.goal @@ -96,10 +99,16 @@ class Characters: def __init__(self, path): self.dir_path = os.path.join(path, "characters") self.host = UniqueIDHost() - self.characters = list() + self.data = dict() + + def __iter__(self): + return self.data.values().__iter__() + + def getByID(self, ID: int) -> Character: + return self.data.get(ID, None) def load(self): - self.characters.clear() + self.data.clear() for name in os.listdir(self.dir_path): path = os.path.join(self.dir_path, name) @@ -114,8 +123,8 @@ class Characters: except FileNotFoundError: continue - self.characters.append(character) + self.data[character.UID.value] = character def save(self): - for character in self.characters: + for character in self.data.values(): character.save() diff --git a/manuskript/data/color.py b/manuskript/data/color.py index 12c5f2af..fc89699a 100644 --- a/manuskript/data/color.py +++ b/manuskript/data/color.py @@ -11,6 +11,9 @@ class Color: self.green = green self.blue = blue + def getRGB(self): + return (self.red << 24) | (self.green << 16) | (self.blue << 8) + def __str__(self): return "#%02x%02x%02x" % (self.red, self.green, self.blue) diff --git a/manuskript/data/importance.py b/manuskript/data/importance.py new file mode 100644 index 00000000..c0d86b02 --- /dev/null +++ b/manuskript/data/importance.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- + +from enum import Enum, unique + + +@unique +class Importance(Enum): + MINOR = 0 + SECONDARY = 1 + MAIN = 2 + + @classmethod + def asValue(cls, importance): + return 0 if importance is None else importance.value + + @classmethod + def fromRawString(cls, raw: str): + if raw is None: + return None + + try: + return Importance(int(raw)) + except ValueError: + return None + + @classmethod + def toRawString(cls, importance): + return None if importance is None else str(importance.value) diff --git a/manuskript/data/plots.py b/manuskript/data/plots.py index ae439b37..5fbe71d5 100644 --- a/manuskript/data/plots.py +++ b/manuskript/data/plots.py @@ -5,17 +5,11 @@ import os from lxml import etree from enum import Enum, unique +from manuskript.data.importance import Importance from manuskript.data.unique_id import UniqueIDHost, UniqueID from manuskript.io.xmlFile import XmlFile -@unique -class PlotImportance(Enum): - MINOR = 0 - SECONDARY = 1 - MAIN = 2 - - class PlotStep: def __init__(self, plot, UID: UniqueID, name: str, meta: str = "", summary: str = ""): @@ -32,7 +26,7 @@ class PlotStep: class PlotLine: - def __init__(self, plots, UID: UniqueID, name: str, importance: PlotImportance = PlotImportance.MINOR): + def __init__(self, plots, UID: UniqueID, name: str, importance: Importance = Importance.MINOR): self.plots = plots self.host = UniqueIDHost() @@ -72,12 +66,12 @@ class Plots: self.host = UniqueIDHost() self.lines = dict() - def addLine(self, name: str, importance: PlotImportance = PlotImportance.MINOR): + def addLine(self, name: str, importance: Importance = Importance.MINOR): line = PlotLine(self, self.host.newID(), name, importance) self.lines[line.UID.value] = line return line - def loadLine(self, ID: int, name: str, importance: PlotImportance = PlotImportance.MINOR): + def loadLine(self, ID: int, name: str, importance: Importance = Importance.MINOR): line = PlotLine(self, self.host.loadID(ID), name, importance) self.lines[line.UID.value] = line return line @@ -110,7 +104,7 @@ class Plots: if ID is None: return - importance = PlotImportance(int(element.get("importance", 0))) + importance = Importance.fromRawString(element.get("importance", None)) line = plots.loadLine(int(ID), element.get("name"), importance) line.description = element.get("description") line.result = element.get("result") @@ -169,7 +163,7 @@ class Plots: cls.saveElementAttribute(element, "name", line.name) cls.saveElementAttribute(element, "ID", line.UID.value) - cls.saveElementAttribute(element, "importance", line.importance.value) + cls.saveElementAttribute(element, "importance", Importance.toRawString(line.importance)) cls.saveElementAttribute(element, "characters", characters) cls.saveElementAttribute(element, "description", line.description) cls.saveElementAttribute(element, "result", line.result) diff --git a/manuskript/ui/__init__.py b/manuskript/ui/__init__.py index 51372bdf..55439777 100644 --- a/manuskript/ui/__init__.py +++ b/manuskript/ui/__init__.py @@ -1,6 +1,8 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +from manuskript.ui.util import pixbufFromColor + from manuskript.ui.generalView import GeneralView from manuskript.ui.summaryView import SummaryView from manuskript.ui.charactersView import CharactersView @@ -10,3 +12,6 @@ from manuskript.ui.outlineView import OutlineView from manuskript.ui.editorView import EditorView from manuskript.ui.mainWindow import MainWindow + + + diff --git a/manuskript/ui/charactersView.py b/manuskript/ui/charactersView.py index 5f582a8e..1c972520 100644 --- a/manuskript/ui/charactersView.py +++ b/manuskript/ui/charactersView.py @@ -6,20 +6,61 @@ import gi gi.require_version("Gtk", "3.0") from gi.repository import Gtk +from manuskript.data import Characters, Character, Importance, Color +from manuskript.ui.util import rgbaFromColor, pixbufFromColor +from manuskript.util import validString, invalidString, validInt, invalidInt + class CharactersView: - def __init__(self): + def __init__(self, characters: Characters): + self.characters = characters + self.character = None + builder = Gtk.Builder() builder.add_from_file("ui/characters.glade") 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.mainCharactersStore = builder.get_object("main_characters_store") + self.secondaryCharactersStore = builder.get_object("secondary_characters_store") + self.minorCharactersStore = builder.get_object("minor_characters_store") + + self.mainCharactersStore.set_visible_func(lambda model, iter, userdata: model[iter][3] == 2) + self.secondaryCharactersStore.set_visible_func(lambda model, iter, userdata: model[iter][3] == 1) + self.minorCharactersStore.set_visible_func(lambda model, iter, userdata: model[iter][3] == 0) + + self.mainCharactersStore.refilter() + self.secondaryCharactersStore.refilter() + self.minorCharactersStore.refilter() + + self.characterSelections = [ + builder.get_object("minor_character_selection"), + builder.get_object("secondary_character_selection"), + builder.get_object("main_character_selection") + ] + + for selection in self.characterSelections: + selection.connect("changed", self.characterSelectionChanged) + self.colorButton = builder.get_object("color") self.importanceCombo = builder.get_object("importance") self.allowPOVCheck = builder.get_object("allow_POV") - self.colorButton.connect("color-set", self.colorSet) + self.colorSetSignal = self.colorButton.connect("color-set", self.colorSet) self.importanceCombo.connect("changed", self.importanceChanged) self.allowPOVCheck.connect("toggled", self.allowPOVToggled) @@ -45,10 +86,71 @@ class CharactersView: self.summaryBuffer = builder.get_object("summary") self.notesBuffer = builder.get_object("notes") + self.unloadCharacterData() + + def loadCharacterData(self, character: Character): + self.character = None + + self.colorButton.set_rgba(rgbaFromColor(character.color)) + self.allowPOVCheck.set_active(character.allowPOV()) + + self.nameBuffer.set_text(validString(character.name), -1) + self.motivationBuffer.set_text(validString(character.motivation), -1) + self.goalBuffer.set_text(validString(character.goal), -1) + self.conflictBuffer.set_text(validString(character.conflict), -1) + self.epiphanyBuffer.set_text(validString(character.epiphany), -1) + self.oneSentenceBuffer.set_text(validString(character.summarySentence), -1) + self.oneParagraphBuffer.set_text(validString(character.summaryParagraph), -1) + self.summaryBuffer.set_text(validString(character.summaryFull), -1) + self.notesBuffer.set_text(validString(character.notes), -1) + + self.character = character + + def unloadCharacterData(self): + self.character = None + + self.colorButton.set_rgba(rgbaFromColor(Color(0, 0, 0))) + self.allowPOVCheck.set_active(False) + + self.nameBuffer.set_text("", -1) + self.motivationBuffer.set_text("", -1) + self.goalBuffer.set_text("", -1) + self.conflictBuffer.set_text("", -1) + self.epiphanyBuffer.set_text("", -1) + self.oneSentenceBuffer.set_text("", -1) + self.oneParagraphBuffer.set_text("", -1) + self.summaryBuffer.set_text("", -1) + self.notesBuffer.set_text("", -1) + + def characterSelectionChanged(self, selection: Gtk.TreeSelection): + model, tree_iter = selection.get_selected() + + if tree_iter is None: + self.unloadCharacterData() + return + + for other in self.characterSelections: + if other != selection: + other.unselect_all() + + character = self.characters.getByID(model[tree_iter][0]) + + if character is None: + self.unloadCharacterData() + else: + self.loadCharacterData(character) + def colorSet(self, button: Gtk.ColorButton): + if self.character is None: + return + color = button.get_rgba() - print("{} {} {} {}".format(color.red, color.green, color.blue, color.alpha)) + red = int(color.red * 255) + green = int(color.green * 255) + blue = int(color.blue * 255) + + self.character.color = Color(red, green, blue) def importanceChanged(self, combo: Gtk.ComboBox): tree_iter = combo.get_active_iter() @@ -62,9 +164,10 @@ class CharactersView: print("blub " + name) def allowPOVToggled(self, button: Gtk.ToggleButton): - state = button.get_active() + if self.character is None: + return - print("OK: {}".format(state)) + self.character.POV = button.get_active() def addDetailsClicked(self, button: Gtk.Button): tree_iter = self.detailsStore.append() diff --git a/manuskript/ui/mainWindow.py b/manuskript/ui/mainWindow.py index 8a63e407..01787430 100644 --- a/manuskript/ui/mainWindow.py +++ b/manuskript/ui/mainWindow.py @@ -49,7 +49,7 @@ class MainWindow: 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.charactersView = MainWindow.packViewIntoSlot(builder, "characters_slot", CharactersView, self.project.characters) self.plotView = MainWindow.packViewIntoSlot(builder, "plot_slot", PlotView) self.worldView = MainWindow.packViewIntoSlot(builder, "world_slot", WorldView) self.outlineView = MainWindow.packViewIntoSlot(builder, "outline_slot", OutlineView) diff --git a/manuskript/ui/util.py b/manuskript/ui/util.py new file mode 100644 index 00000000..bff12473 --- /dev/null +++ b/manuskript/ui/util.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import gi + +gi.require_version('Gdk', '3.0') +gi.require_version('GdkPixbuf', '2.0') +from gi.repository import GdkPixbuf, Gdk + +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.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()) + return pixbuf diff --git a/manuskript/util/__init__.py b/manuskript/util/__init__.py index 1467046d..72123e91 100644 --- a/manuskript/util/__init__.py +++ b/manuskript/util/__init__.py @@ -10,3 +10,11 @@ def validString(invalid: str) -> str: def invalidString(valid: str) -> str: return None if len(valid) == 0 else valid + + +def validInt(invalid: int) -> int: + return 0 if invalid is None else invalid + + +def invalidInt(valid: int) -> int: + return None if valid == 0 else valid diff --git a/ui/characters.glade b/ui/characters.glade index 795b08b2..e95afe44 100644 --- a/ui/characters.glade +++ b/ui/characters.glade @@ -25,6 +25,27 @@ along with Manuskript. If not, see . + + + + + + + + + + + + + + characters_store + + + characters_store + + + characters_store + @@ -109,9 +130,30 @@ along with Manuskript. If not, see . - + True - False + True + main_characters_store + False + + + + + + + + + 2 + + + + + + 1 + + + + False @@ -132,9 +174,30 @@ along with Manuskript. If not, see . - + True - False + True + secondary_characters_store + False + + + + + + + + + 2 + + + + + + 1 + + + + False @@ -155,12 +218,33 @@ along with Manuskript. If not, see . - + True - False + True + minor_characters_store + False + + + + + + + + + 2 + + + + + + 1 + + + + - True + False True 5 @@ -349,6 +433,7 @@ summary 100 True True + word-char one_paragraph_summary @@ -374,6 +459,7 @@ summary 100 True True + word-char one_sentence_summary @@ -399,6 +485,7 @@ summary 100 True True + word-char epiphany @@ -424,6 +511,7 @@ summary 100 True True + word-char conflict @@ -449,6 +537,7 @@ summary 100 True True + word-char goal @@ -474,6 +563,7 @@ summary 100 True True + word-char motivation @@ -631,6 +721,7 @@ summary True True + word-char summary @@ -667,6 +758,7 @@ summary True True + word-char notes