Implement basic outline overview

Signed-off-by: TheJackiMonster <thejackimonster@gmail.com>
This commit is contained in:
TheJackiMonster 2022-10-30 17:48:02 +01:00
parent 792e68038d
commit 3481e40b4f
No known key found for this signature in database
GPG key ID: D850A5F772E880F9
10 changed files with 428 additions and 61 deletions

View file

@ -3,12 +3,12 @@
from manuskript.data.characters import Characters, Character
from manuskript.data.color import Color
from manuskript.data.goal import GoalKind, Goal
from manuskript.data.goal import Goal
from manuskript.data.importance import Importance
from manuskript.data.info import Info
from manuskript.data.labels import LabelHost, Label
from manuskript.data.links import LinkAction, Links
from manuskript.data.outline import Outline, OutlineFolder, OutlineText
from manuskript.data.outline import Outline, OutlineFolder, OutlineText, OutlineItem, OutlineState
from manuskript.data.plots import Plots, PlotLine, PlotStep
from manuskript.data.project import Project
from manuskript.data.revisions import Revisions

View file

@ -1,18 +1,12 @@
#!/usr/bin/env python
# --!-- coding: utf8 --!--
from enum import Enum, unique
@unique
class GoalKind(Enum):
WORDS = 0
CHARACTERS = 1
from manuskript.util import CounterKind, countText
class Goal:
def __init__(self, value: int = 0, kind: GoalKind = GoalKind.WORDS):
def __init__(self, value: int = 0, kind: CounterKind = CounterKind.WORDS):
self.value = max(value, 0)
self.kind = kind
@ -20,11 +14,14 @@ class Goal:
return str(self.value) + " " + self.kind.name.lower()
def __str__(self):
if self.kind != GoalKind.WORDS:
if self.kind != CounterKind.WORDS:
return self.prettyString()
else:
return str(self.value)
def count(self, text: str):
return countText(text, self.kind)
@classmethod
def parse(cls, string: str):
if string is None:
@ -34,7 +31,7 @@ class Goal:
try:
value = int(parts[0])
kind = GoalKind[parts[1].upper()] if len(parts) > 1 else GoalKind.WORDS
kind = CounterKind[parts[1].upper()] if len(parts) > 1 else CounterKind.WORDS
except ValueError:
return None

View file

@ -6,8 +6,10 @@ import os
from collections import OrderedDict
from enum import Enum, unique
from manuskript.data.goal import Goal
from manuskript.data.plots import Plots
from manuskript.data.unique_id import UniqueIDHost
from manuskript.io.mmdFile import MmdFile
from manuskript.util import CounterKind, countText, validString
@unique
@ -43,7 +45,9 @@ class OutlineItem:
if ID is None:
return
item.UID = item.outline.host.loadID(int(ID))
if (item.UID is None) or (item.UID.value != int(ID)):
item.UID = item.outline.host.loadID(int(ID))
item.title = metadata.get("title", None)
item.type = metadata.get("type", "md")
item.summarySentence = metadata.get("summarySentence", None)
@ -76,6 +80,12 @@ class OutlineItem:
return metadata
def textCount(self, counterKind: CounterKind = None) -> int:
return 0
def goalCount(self) -> int:
return 0 if self.goal is None else self.goal.value
def load(self, optimized: bool = True):
raise IOError('Loading undefined!')
@ -90,6 +100,12 @@ class OutlineText(OutlineItem):
self.text = ""
def textCount(self, counterKind: CounterKind = None) -> int:
if counterKind is None:
counterKind = CounterKind.WORDS if self.goal is None else self.goal.kind
return super().textCount(counterKind) + countText(self.text, counterKind)
def load(self, optimized: bool = True):
metadata, body = self.file.loadMMD(optimized)
OutlineItem.loadMetadata(self, metadata)
@ -126,6 +142,7 @@ class OutlineFolder(OutlineItem):
names = os.listdir(folder.dir_path)
names.remove("folder.txt")
names.sort()
for name in names:
path = os.path.join(folder.dir_path, name)
@ -147,6 +164,17 @@ class OutlineFolder(OutlineItem):
if type(item) is OutlineFolder:
cls.loadItems(outline, item, recursive)
def textCount(self, counterKind: CounterKind = None) -> int:
if counterKind is None:
counterKind = CounterKind.WORDS if self.goal is None else self.goal.kind
count = super().textCount(counterKind)
for item in self.items:
count += item.textCount(counterKind)
return count
def load(self, _: bool = True):
metadata, _ = self.file.loadMMD(True)
OutlineItem.loadMetadata(self, metadata)
@ -170,14 +198,22 @@ class OutlineFolder(OutlineItem):
class Outline:
def __init__(self, path):
def __init__(self, path, plots: Plots):
self.dir_path = os.path.join(path, "outline")
self.host = UniqueIDHost()
self.plots = plots
self.items = list()
def __iter__(self):
return self.items.__iter__()
def getItemByID(self, ID: int) -> OutlineItem | None:
for item in self.all():
if item.UID.value == ID:
return item
return None
def all(self):
result = list()
queue = list(self.items)
@ -196,7 +232,10 @@ class Outline:
def load(self):
self.items.clear()
for name in os.listdir(self.dir_path):
names = os.listdir(self.dir_path)
names.sort()
for name in names:
path = os.path.join(self.dir_path, name)
if os.path.isdir(path):

View file

@ -32,7 +32,7 @@ class Project:
self.characters = Characters(self.file.dir_path)
self.plots = Plots(self.file.dir_path, self.characters)
self.world = World(self.file.dir_path)
self.outline = Outline(self.file.dir_path)
self.outline = Outline(self.file.dir_path, self.plots)
self.revisions = Revisions(self.file.dir_path)
def __del__(self):

View file

@ -68,7 +68,7 @@ class MainWindow:
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.outlineView = MainWindow.packViewIntoSlot(builder, "outline_slot", OutlineView, self.project.outline)
self.editorView = MainWindow.packViewIntoSlot(builder, "editor_slot", EditorView)
self.startupWindow = StartupWindow(self)

View file

@ -6,14 +6,269 @@ import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk
from manuskript.data import Outline, OutlineFolder, OutlineText, OutlineItem, OutlineState, Plots, PlotLine, Characters, Character, Importance, Goal
from manuskript.ui.util import rgbaFromColor, pixbufFromColor
from manuskript.util import validString, invalidString, validInt, invalidInt, CounterKind, countText
class OutlineView:
def __init__(self):
def __init__(self, outline: Outline):
self.outline = outline
self.outlineItem = None
builder = Gtk.Builder()
builder.add_from_file("ui/outline.glade")
self.widget = builder.get_object("outline_view")
self.plotsStore = builder.get_object("plots_store")
self.refreshPlotsStore()
self.charactersStore = builder.get_object("characters_store")
self.refreshCharactersStore()
self.outlineStore = builder.get_object("outline_store")
self.refreshOutlineStore()
self.mainPlotsStore = builder.get_object("main_plots_store")
self.secondaryPlotsStore = builder.get_object("secondary_plots_store")
self.minorPlotsStore = builder.get_object("minor_plots_store")
self.mainPlotsStore.set_visible_func(
lambda model, iter, userdata: model[iter][2] == Importance.MAIN.value)
self.secondaryPlotsStore.set_visible_func(
lambda model, iter, userdata: model[iter][2] == Importance.SECONDARY.value)
self.minorPlotsStore.set_visible_func(
lambda model, iter, userdata: model[iter][2] == Importance.MINOR.value)
self.mainPlotsStore.refilter()
self.secondaryPlotsStore.refilter()
self.minorPlotsStore.refilter()
self.plotSelections = [
builder.get_object("minor_plot_selection"),
builder.get_object("secondary_plot_selection"),
builder.get_object("main_plot_selection")
]
for selection in self.plotSelections:
selection.connect("changed", self.plotSelectionChanged)
self.filterOutlineBuffer = builder.get_object("filter_outline")
self.filterOutlineBuffer.connect("deleted-text", self.filterOutlineDeletedText)
self.filterOutlineBuffer.connect("inserted-text", self.filterOutlineInsertedText)
self.filteredOutlineStore = builder.get_object("filtered_outline_store")
self.filteredOutlineStore.set_visible_func(self.filterOutline)
self.filteredOutlineStore.refilter()
self.outlineSelection = builder.get_object("outline_selection")
self.outlineSelection.connect("changed", self.outlineSelectionChanged)
self.goalBuffer = builder.get_object("goal")
self.oneLineSummaryBuffer = builder.get_object("one_line_summary")
self.fewSentencesSummaryBuffer = builder.get_object("few_sentences_summary")
self.goalBuffer.connect("deleted-text", self.goalDeletedText)
self.goalBuffer.connect("inserted-text", self.goalInsertedText)
self.oneLineSummaryBuffer.connect("deleted-text", self.oneLineSummaryDeletedText)
self.oneLineSummaryBuffer.connect("inserted-text", self.oneLineSummaryInsertedText)
self.fewSentencesSummaryBuffer.connect("changed", self.fewSentencesSummaryChanged)
self.unloadOutlineData()
def refreshPlotsStore(self):
self.plotsStore.clear()
for plotLine in self.outline.plots:
tree_iter = self.plotsStore.append()
if tree_iter is None:
continue
self.plotsStore.set_value(tree_iter, 0, plotLine.UID.value)
self.plotsStore.set_value(tree_iter, 1, validString(plotLine.name))
self.plotsStore.set_value(tree_iter, 2, Importance.asValue(plotLine.importance))
def refreshCharactersStore(self):
self.charactersStore.clear()
for character in self.outline.plots.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))
def __appendOutlineItem(self, outlineItem: OutlineItem, parent_iter=None):
tree_iter = self.outlineStore.append(parent_iter)
if tree_iter is None:
return
if outlineItem.state != OutlineState.COMPLETE:
outlineItem.load(False)
if type(outlineItem) is OutlineFolder:
for item in outlineItem:
self.__appendOutlineItem(item, tree_iter)
wordCount = validInt(outlineItem.textCount())
goal = validInt(outlineItem.goalCount())
progress = 0
if goal > wordCount:
progress = 100 * wordCount / goal
elif goal > 0:
progress = 100
self.outlineStore.set_value(tree_iter, 0, outlineItem.UID.value)
self.outlineStore.set_value(tree_iter, 1, validString(outlineItem.title))
self.outlineStore.set_value(tree_iter, 2, validString(outlineItem.label))
self.outlineStore.set_value(tree_iter, 3, validString(outlineItem.status))
self.outlineStore.set_value(tree_iter, 4, outlineItem.compile)
self.outlineStore.set_value(tree_iter, 5, wordCount)
self.outlineStore.set_value(tree_iter, 6, goal)
self.outlineStore.set_value(tree_iter, 7, progress)
def refreshOutlineStore(self):
self.outlineStore.clear()
for item in self.outline.items:
self.__appendOutlineItem(item)
def plotSelectionChanged(self, selection: Gtk.TreeSelection):
model, tree_iter = selection.get_selected()
if tree_iter is None:
return
for other in self.plotSelections:
if other != selection:
other.unselect_all()
def loadOutlineData(self, outlineItem: OutlineItem):
self.outlineItem = None
self.goalBuffer.set_text(validString(outlineItem.goal), -1)
self.oneLineSummaryBuffer.set_text(validString(outlineItem.summarySentence), -1)
self.fewSentencesSummaryBuffer.set_text(validString(outlineItem.summaryFull), -1)
self.outlineItem = outlineItem
def unloadOutlineData(self):
self.outlineItem = None
self.goalBuffer.set_text("", -1)
self.oneLineSummaryBuffer.set_text("", -1)
self.fewSentencesSummaryBuffer.set_text("", -1)
def outlineSelectionChanged(self, selection: Gtk.TreeSelection):
model, tree_iter = selection.get_selected()
if tree_iter is None:
self.unloadOutlineData()
return
outlineItem = self.outline.getItemByID(model[tree_iter][0])
if outlineItem is None:
self.unloadOutlineData()
else:
self.loadOutlineData(outlineItem)
def __matchOutlineItemByText(self, outlineItem: OutlineItem, text: str):
if type(outlineItem) is OutlineFolder:
for item in outlineItem:
if self.__matchOutlineItemByText(item, text):
return True
title = validString(outlineItem.title)
return text in title.lower()
def filterOutline(self, model, iter, userdata):
outlineItem = self.outline.getItemByID(model[iter][0])
if outlineItem is None:
return False
text = validString(self.filterOutlineBuffer.get_text())
return self.__matchOutlineItemByText(outlineItem, text.lower())
def filterOutlineChanged(self, buffer: Gtk.EntryBuffer):
self.filteredOutlineStore.refilter()
def filterOutlineDeletedText(self, buffer: Gtk.EntryBuffer, position: int, n_chars: int):
self.filterOutlineChanged(buffer)
def filterOutlineInsertedText(self, buffer: Gtk.EntryBuffer, position: int, chars: str, n_chars: int):
self.filterOutlineChanged(buffer)
def goalChanged(self, buffer: Gtk.EntryBuffer):
if self.outlineItem is None:
return
text = buffer.get_text()
self.outlineItem.goal = Goal.parse(text)
outline_id = self.outlineItem.UID.value
wordCount = validInt(self.outlineItem.textCount())
goal = validInt(self.outlineItem.goalCount())
progress = 0
if goal > wordCount:
progress = 100 * wordCount / goal
elif goal > 0:
progress = 100
for row in self.outlineStore:
if row[0] == outline_id:
row[6] = goal
row[7] = progress
break
def goalDeletedText(self, buffer: Gtk.EntryBuffer, position: int, n_chars: int):
self.goalChanged(buffer)
def goalInsertedText(self, buffer: Gtk.EntryBuffer, position: int, chars: str, n_chars: int):
self.goalChanged(buffer)
def oneLineSummaryChanged(self, buffer: Gtk.EntryBuffer):
if self.outlineItem is None:
return
text = buffer.get_text()
summary = invalidString(text)
self.outlineItem.summarySentence = summary
def oneLineSummaryDeletedText(self, buffer: Gtk.EntryBuffer, position: int, n_chars: int):
self.oneLineSummaryChanged(buffer)
def oneLineSummaryInsertedText(self, buffer: Gtk.EntryBuffer, position: int, chars: str, n_chars: int):
self.oneLineSummaryChanged(buffer)
def fewSentencesSummaryChanged(self, buffer: Gtk.TextBuffer):
if self.outlineItem is None:
return
start_iter = buffer.get_start_iter()
end_iter = buffer.get_end_iter()
text = buffer.get_text(start_iter, end_iter, False)
self.outlineItem.summaryFull = invalidString(text)
def show(self):
self.widget.show_all()

View file

@ -59,7 +59,7 @@ class WorldView:
self.unloadWorldData()
def __appendWorldItem(self, worldItem: WorldItem, parent_iter = None):
def __appendWorldItem(self, worldItem: WorldItem, parent_iter=None):
tree_iter = self.worldStore.append(parent_iter)
if tree_iter is None:

View file

@ -2,7 +2,8 @@
# -*- coding: utf-8 -*-
import re
from manuskript.util.counter import CharCounter, WordCounter, PageCounter
from manuskript.util.counter import CounterKind, CharCounter, WordCounter, PageCounter
def safeInt(s: str, d: int) -> int:
@ -15,16 +16,16 @@ def safeInt(s: str, d: int) -> int:
return d
def validString(invalid: str) -> str:
return "" if invalid is None else invalid
def validString(invalid) -> str:
return "" if invalid is None else str(invalid)
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 validInt(invalid) -> int:
return 0 if invalid is None else int(invalid)
def invalidInt(valid: int) -> int:
@ -46,3 +47,17 @@ def safeFilename(filename: str, extension: str = None) -> str:
name = "_" + name
return re.sub(r"[^a-zA-Z0-9._\-+()]", "_", name)
def countText(text: str, kind: CounterKind = CounterKind.WORDS):
if text is None:
return 0
if kind == CounterKind.CHARACTERS:
return CharCounter.count(text)
elif kind == CounterKind.WORDS:
return WordCounter.count(text)
elif kind == CounterKind.PAGES:
return PageCounter.count(text)
else:
return 0

View file

@ -3,6 +3,15 @@
import re
from enum import Enum, unique
@unique
class CounterKind(Enum):
WORDS = 0
CHARACTERS = 1
PAGES = 2
class CharCounter:

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.38.2
<!-- Generated with glade 3.40.0
Copyright (C) 2015-2021 Olivier Keshavjee et al.
@ -25,16 +25,24 @@ along with Manuskript. If not, see <http://www.gnu.org/licenses/>.
<!-- interface-name Manuskript -->
<!-- interface-description Manuskript is an open-source tool for writers. -->
<!-- interface-copyright 2015-2021 Olivier Keshavjee et al. -->
<object class="GtkListStore" id="character_store">
<object class="GtkListStore" id="characters_store">
<columns>
<!-- column-name ID -->
<column type="gint"/>
<!-- column-name name -->
<column type="gchararray"/>
<!-- column-name color -->
<column type="GdkPixbuf"/>
</columns>
</object>
<object class="GtkTextBuffer" id="few_sentences_summary"/>
<object class="GtkEntryBuffer" id="filter_outline"/>
<object class="GtkEntryBuffer" id="goal"/>
<object class="GtkEntryBuffer" id="one_line_summary"/>
<object class="GtkTreeStore" id="outline_store">
<columns>
<!-- column-name ID -->
<column type="gint"/>
<!-- column-name title -->
<column type="gchararray"/>
<!-- column-name label -->
@ -47,25 +55,31 @@ along with Manuskript. If not, see <http://www.gnu.org/licenses/>.
<column type="gulong"/>
<!-- column-name goal -->
<column type="gulong"/>
<!-- column-name progress -->
<column type="gint"/>
</columns>
</object>
<object class="GtkTreeStore" id="plot_main_store">
<object class="GtkTreeModelFilter" id="filtered_outline_store">
<property name="child-model">outline_store</property>
</object>
<object class="GtkListStore" id="plots_store">
<columns>
<!-- column-name ID -->
<column type="gint"/>
<!-- column-name name -->
<column type="gchararray"/>
<!-- column-name importance -->
<column type="gint"/>
</columns>
</object>
<object class="GtkTreeStore" id="plot_minor_store">
<columns>
<!-- column-name name -->
<column type="gchararray"/>
</columns>
<object class="GtkTreeModelFilter" id="main_plots_store">
<property name="child-model">plots_store</property>
</object>
<object class="GtkTreeStore" id="plot_secondary_store">
<columns>
<!-- column-name name -->
<column type="gchararray"/>
</columns>
<object class="GtkTreeModelFilter" id="minor_plots_store">
<property name="child-model">plots_store</property>
</object>
<object class="GtkTreeModelFilter" id="secondary_plots_store">
<property name="child-model">plots_store</property>
</object>
<object class="GtkScrolledWindow" id="outline_view">
<property name="visible">True</property>
@ -102,10 +116,21 @@ along with Manuskript. If not, see <http://www.gnu.org/licenses/>.
<object class="GtkTreeView">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="model">plot_main_store</property>
<property name="model">main_plots_store</property>
<property name="headers-visible">False</property>
<property name="search-column">0</property>
<child internal-child="selection">
<object class="GtkTreeSelection"/>
<object class="GtkTreeSelection" id="main_plot_selection"/>
</child>
<child>
<object class="GtkTreeViewColumn">
<child>
<object class="GtkCellRendererText"/>
<attributes>
<attribute name="text">1</attribute>
</attributes>
</child>
</object>
</child>
</object>
<packing>
@ -130,10 +155,21 @@ along with Manuskript. If not, see <http://www.gnu.org/licenses/>.
<object class="GtkTreeView">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="model">plot_secondary_store</property>
<property name="model">secondary_plots_store</property>
<property name="headers-visible">False</property>
<property name="search-column">0</property>
<child internal-child="selection">
<object class="GtkTreeSelection"/>
<object class="GtkTreeSelection" id="secondary_plot_selection"/>
</child>
<child>
<object class="GtkTreeViewColumn">
<child>
<object class="GtkCellRendererText"/>
<attributes>
<attribute name="text">1</attribute>
</attributes>
</child>
</object>
</child>
</object>
<packing>
@ -158,10 +194,21 @@ along with Manuskript. If not, see <http://www.gnu.org/licenses/>.
<object class="GtkTreeView">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="model">plot_minor_store</property>
<property name="model">minor_plots_store</property>
<property name="headers-visible">False</property>
<property name="search-column">0</property>
<child internal-child="selection">
<object class="GtkTreeSelection"/>
<object class="GtkTreeSelection" id="minor_plot_selection"/>
</child>
<child>
<object class="GtkTreeViewColumn">
<child>
<object class="GtkCellRendererText"/>
<attributes>
<attribute name="text">1</attribute>
</attributes>
</child>
</object>
</child>
</object>
<packing>
@ -206,11 +253,11 @@ along with Manuskript. If not, see <http://www.gnu.org/licenses/>.
<object class="GtkTreeView">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="model">outline_store</property>
<property name="model">filtered_outline_store</property>
<property name="search-column">0</property>
<property name="enable-grid-lines">both</property>
<property name="enable-tree-lines">True</property>
<child internal-child="selection">
<object class="GtkTreeSelection"/>
<object class="GtkTreeSelection" id="outline_selection"/>
</child>
<child>
<object class="GtkTreeViewColumn">
@ -218,7 +265,7 @@ along with Manuskript. If not, see <http://www.gnu.org/licenses/>.
<child>
<object class="GtkCellRendererText"/>
<attributes>
<attribute name="text">0</attribute>
<attribute name="text">1</attribute>
</attributes>
</child>
</object>
@ -229,7 +276,7 @@ along with Manuskript. If not, see <http://www.gnu.org/licenses/>.
<child>
<object class="GtkCellRendererCombo"/>
<attributes>
<attribute name="text">1</attribute>
<attribute name="text">2</attribute>
</attributes>
</child>
</object>
@ -240,7 +287,7 @@ along with Manuskript. If not, see <http://www.gnu.org/licenses/>.
<child>
<object class="GtkCellRendererCombo"/>
<attributes>
<attribute name="text">2</attribute>
<attribute name="text">3</attribute>
</attributes>
</child>
</object>
@ -251,7 +298,7 @@ along with Manuskript. If not, see <http://www.gnu.org/licenses/>.
<child>
<object class="GtkCellRendererToggle"/>
<attributes>
<attribute name="active">3</attribute>
<attribute name="active">4</attribute>
</attributes>
</child>
</object>
@ -260,9 +307,9 @@ along with Manuskript. If not, see <http://www.gnu.org/licenses/>.
<object class="GtkTreeViewColumn">
<property name="title" translatable="yes">Word count</property>
<child>
<object class="GtkCellRendererText"/>
<object class="GtkCellRendererSpin"/>
<attributes>
<attribute name="text">4</attribute>
<attribute name="text">5</attribute>
</attributes>
</child>
</object>
@ -271,9 +318,9 @@ along with Manuskript. If not, see <http://www.gnu.org/licenses/>.
<object class="GtkTreeViewColumn">
<property name="title" translatable="yes">Goal</property>
<child>
<object class="GtkCellRendererText"/>
<object class="GtkCellRendererSpin"/>
<attributes>
<attribute name="text">5</attribute>
<attribute name="text">6</attribute>
</attributes>
</child>
</object>
@ -284,7 +331,7 @@ along with Manuskript. If not, see <http://www.gnu.org/licenses/>.
<child>
<object class="GtkCellRendererProgress"/>
<attributes>
<attribute name="value">4</attribute>
<attribute name="value">7</attribute>
</attributes>
</child>
</object>
@ -311,7 +358,7 @@ along with Manuskript. If not, see <http://www.gnu.org/licenses/>.
<property name="can-focus">False</property>
<property name="spacing">4</property>
<child>
<object class="GtkButton">
<object class="GtkButton" id="add_folder">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
@ -330,7 +377,7 @@ along with Manuskript. If not, see <http://www.gnu.org/licenses/>.
</packing>
</child>
<child>
<object class="GtkButton">
<object class="GtkButton" id="add_text">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
@ -349,7 +396,7 @@ along with Manuskript. If not, see <http://www.gnu.org/licenses/>.
</packing>
</child>
<child>
<object class="GtkButton">
<object class="GtkButton" id="remove">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
@ -371,6 +418,7 @@ along with Manuskript. If not, see <http://www.gnu.org/licenses/>.
<object class="GtkEntry">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="buffer">filter_outline</property>
<property name="placeholder-text" translatable="yes">Filter</property>
</object>
<packing>
@ -407,6 +455,8 @@ along with Manuskript. If not, see <http://www.gnu.org/licenses/>.
<object class="GtkEntry">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="buffer">goal</property>
<property name="placeholder-text" translatable="yes">0</property>
<property name="input-purpose">digits</property>
</object>
<packing>
@ -433,17 +483,17 @@ along with Manuskript. If not, see <http://www.gnu.org/licenses/>.
<object class="GtkComboBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="model">character_store</property>
<property name="model">characters_store</property>
<child>
<object class="GtkCellRendererText"/>
<attributes>
<attribute name="text">0</attribute>
<attribute name="text">1</attribute>
</attributes>
</child>
<child>
<object class="GtkCellRendererPixbuf"/>
<attributes>
<attribute name="pixbuf">1</attribute>
<attribute name="pixbuf">2</attribute>
</attributes>
</child>
</object>
@ -478,6 +528,7 @@ along with Manuskript. If not, see <http://www.gnu.org/licenses/>.
<object class="GtkEntry">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="buffer">one_line_summary</property>
<property name="placeholder-text" translatable="yes">One line summary</property>
</object>
<packing>
@ -512,6 +563,7 @@ along with Manuskript. If not, see <http://www.gnu.org/licenses/>.
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="wrap-mode">word-char</property>
<property name="buffer">few_sentences_summary</property>
</object>
</child>
<child type="label_item">