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 .
+
+
+
+
@@ -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