mirror of
https://github.com/olivierkes/manuskript.git
synced 2024-05-15 10:22:26 +12:00
3d469e671e
Signed-off-by: TheJackiMonster <thejackimonster@gmail.com>
395 lines
14 KiB
Python
395 lines
14 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
|
|
import gi
|
|
|
|
gi.require_version("Gtk", "3.0")
|
|
from gi.repository import GObject, Gtk, Pango
|
|
|
|
from manuskript.data import Project, OutlineFolder, OutlineText, OutlineItem, OutlineState, Goal
|
|
from manuskript.ui.editor import GridItem
|
|
from manuskript.ui.util import pixbufFromColor, iconByOutlineItemType
|
|
from manuskript.util import validString, validInt, safeFraction
|
|
|
|
|
|
class EditorView:
|
|
|
|
def __init__(self, project: Project):
|
|
self.project = project
|
|
self.outlineItem = None
|
|
self.outlineCompletion = []
|
|
self.editorItems = list()
|
|
|
|
builder = Gtk.Builder()
|
|
builder.add_from_file("ui/editor.glade")
|
|
|
|
self.widget = builder.get_object("editor_view")
|
|
|
|
self.labelStore = builder.get_object("label_store")
|
|
self.refreshLabelStore()
|
|
|
|
self.statusStore = builder.get_object("status_store")
|
|
self.refreshStatusStore()
|
|
|
|
self.outlineStore = builder.get_object("outline_store")
|
|
self.refreshOutlineStore()
|
|
|
|
self.editorOutlineStore = builder.get_object("editor_outline_store")
|
|
|
|
self.outlineView = builder.get_object("outline_view")
|
|
self.editorOutlineView = builder.get_object("editor_outline_view")
|
|
|
|
self.outlineSelection = builder.get_object("outline_selection")
|
|
self.editorOutlineSelection = builder.get_object("editor_outline_selection")
|
|
|
|
self.outlineSelection.connect("changed", self._outlineSelectionChanged)
|
|
self.editorOutlineSelection.connect("changed", self._editorOutlineSelectionChanged)
|
|
|
|
self.viewStack = builder.get_object("view_stack")
|
|
|
|
self.editorTextBuffer = builder.get_object("editor_text")
|
|
self.editorFlowbox = builder.get_object("editor_flowbox")
|
|
|
|
self.editorFlowbox.connect("selected-children-changed", self._editorFlowboxSelectionChanged)
|
|
self.editorFlowbox.connect("child-activated", self._editorFlowboxChildActivated)
|
|
|
|
self.upButtons = [
|
|
builder.get_object("up"),
|
|
builder.get_object("up_")
|
|
]
|
|
|
|
for button in self.upButtons:
|
|
button.connect("clicked", self._upButtonClicked)
|
|
|
|
self.counterLabel = builder.get_object("counter")
|
|
self.counterProgressBar = builder.get_object("counter_progress")
|
|
|
|
self.unloadOutlineData()
|
|
|
|
def refreshLabelStore(self):
|
|
self.labelStore.clear()
|
|
|
|
for label in self.project.labels:
|
|
tree_iter = self.labelStore.append()
|
|
|
|
if tree_iter is None:
|
|
continue
|
|
|
|
self.labelStore.set_value(tree_iter, 0, validString(label.name))
|
|
self.labelStore.set_value(tree_iter, 1, pixbufFromColor(label.color))
|
|
|
|
def refreshStatusStore(self):
|
|
self.statusStore.clear()
|
|
|
|
for status in self.project.statuses:
|
|
tree_iter = self.statusStore.append()
|
|
|
|
if tree_iter is None:
|
|
continue
|
|
|
|
self.statusStore.set_value(tree_iter, 0, validString(status.name))
|
|
|
|
def __updateOutlineItem(self, tree_iter, outlineItem: OutlineItem):
|
|
icon = iconByOutlineItemType(outlineItem)
|
|
|
|
wordCount = validInt(outlineItem.textCount())
|
|
goal = validInt(outlineItem.goalCount())
|
|
progress = 100 * safeFraction(wordCount, 0, goal)
|
|
|
|
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)
|
|
self.outlineStore.set_value(tree_iter, 8, icon)
|
|
|
|
def __completeOutlineItem(self):
|
|
(tree_iter, outlineItem) = self.outlineCompletion.pop(0)
|
|
|
|
if outlineItem.state != OutlineState.COMPLETE:
|
|
outlineItem.load(False)
|
|
|
|
self.__updateOutlineItem(tree_iter, outlineItem)
|
|
|
|
completedItem = outlineItem
|
|
while completedItem is not None:
|
|
if completedItem in self.editorItems:
|
|
self.loadOutlineData(self.outlineItem)
|
|
break
|
|
|
|
completedItem = completedItem.parentItem()
|
|
|
|
return len(self.outlineCompletion) > 0
|
|
|
|
def __appendOutlineItem(self, outlineItem: OutlineItem, parent_iter=None):
|
|
tree_iter = self.outlineStore.append(parent_iter)
|
|
|
|
if tree_iter is None:
|
|
return
|
|
|
|
if type(outlineItem) is OutlineFolder:
|
|
for item in outlineItem:
|
|
self.__appendOutlineItem(item, tree_iter)
|
|
|
|
if outlineItem.state != OutlineState.COMPLETE:
|
|
if len(self.outlineCompletion) == 0:
|
|
GObject.idle_add(self.__completeOutlineItem)
|
|
|
|
self.outlineCompletion.append((tree_iter, outlineItem))
|
|
|
|
self.__updateOutlineItem(tree_iter, outlineItem)
|
|
|
|
def refreshOutlineStore(self):
|
|
self.outlineStore.clear()
|
|
|
|
for item in self.project.outline.items:
|
|
self.__appendOutlineItem(item)
|
|
|
|
def __updateEditorOutlineItem(self, list_iter, outlineItem: OutlineItem):
|
|
icon = iconByOutlineItemType(outlineItem)
|
|
|
|
wordCount = validInt(outlineItem.textCount())
|
|
goal = validInt(outlineItem.goalCount())
|
|
progress = 100 * safeFraction(wordCount, 0, goal)
|
|
|
|
self.editorOutlineStore.set_value(list_iter, 0, outlineItem.UID.value)
|
|
self.editorOutlineStore.set_value(list_iter, 1, validString(outlineItem.title))
|
|
self.editorOutlineStore.set_value(list_iter, 2, validString(outlineItem.label))
|
|
self.editorOutlineStore.set_value(list_iter, 3, validString(outlineItem.status))
|
|
self.editorOutlineStore.set_value(list_iter, 4, outlineItem.compile)
|
|
self.editorOutlineStore.set_value(list_iter, 5, wordCount)
|
|
self.editorOutlineStore.set_value(list_iter, 6, goal)
|
|
self.editorOutlineStore.set_value(list_iter, 7, progress)
|
|
self.editorOutlineStore.set_value(list_iter, 8, icon)
|
|
|
|
def refreshEditorOutlineStore(self):
|
|
self.editorOutlineSelection.unselect_all()
|
|
self.editorOutlineStore.clear()
|
|
|
|
for outlineItem in self.editorItems:
|
|
list_iter = self.editorOutlineStore.append()
|
|
|
|
if list_iter is None:
|
|
continue
|
|
|
|
self.__updateEditorOutlineItem(list_iter, outlineItem)
|
|
|
|
def loadOutlineData(self, outlineItem: OutlineItem):
|
|
if outlineItem is None:
|
|
self.unloadOutlineData()
|
|
return
|
|
|
|
self.outlineItem = None
|
|
self.loadEditorData(outlineItem)
|
|
|
|
if type(outlineItem) is OutlineText:
|
|
self.viewStack.set_visible_child_name("page_text")
|
|
else:
|
|
self.viewStack.set_visible_child_name("page_stack")
|
|
|
|
goalKind = outlineItem.goalKind()
|
|
textCount = outlineItem.textCount(goalKind)
|
|
goalCount = outlineItem.goalCount()
|
|
|
|
self.counterLabel.set_text("{0} {1}".format(textCount, goalKind.name.lower()))
|
|
self.counterProgressBar.set_text("{0} / {1} {2}".format(textCount, goalCount, goalKind.name.lower()))
|
|
self.counterProgressBar.set_fraction(safeFraction(textCount, 0, goalCount))
|
|
|
|
self.outlineItem = outlineItem
|
|
|
|
def unloadOutlineData(self):
|
|
self.outlineItem = None
|
|
self.loadEditorData(None)
|
|
|
|
goalKind = self.project.outline.goalKind()
|
|
textCount = self.project.outline.textCount(goalKind)
|
|
goalCount = self.project.outline.goalCount()
|
|
|
|
self.counterLabel.set_text("{0} {1}".format(textCount, goalKind.name.lower()))
|
|
self.counterProgressBar.set_text("{0} / {1} {2}".format(textCount, goalCount, goalKind.name.lower()))
|
|
self.counterProgressBar.set_fraction(safeFraction(textCount, 0, goalCount))
|
|
|
|
def __appendOutlineItemText(self, outlineItem: OutlineItem, level: int = 1):
|
|
end_iter = self.editorTextBuffer.get_end_iter()
|
|
|
|
if type(outlineItem) is OutlineFolder:
|
|
if self.editorTextBuffer.get_line_count() > 1:
|
|
self.editorTextBuffer.insert_with_tags_by_name(end_iter, "\n", "none")
|
|
|
|
headerTag = "h{0}".format(min(level, 6))
|
|
end_iter = self.editorTextBuffer.get_end_iter()
|
|
|
|
self.editorTextBuffer.insert_with_tags_by_name(end_iter, outlineItem.title + "\n", headerTag)
|
|
|
|
firstItem = True
|
|
|
|
for item in outlineItem:
|
|
if firstItem:
|
|
firstItem = False
|
|
else:
|
|
end_iter = self.editorTextBuffer.get_end_iter()
|
|
self.editorTextBuffer.insert_with_tags_by_name(end_iter, "\n", "none")
|
|
|
|
self.__appendOutlineItemText(item, level + 1)
|
|
|
|
return True
|
|
elif type(outlineItem) is OutlineText:
|
|
outlineText: OutlineText = outlineItem
|
|
|
|
if (outlineText.text is None) or (len(outlineText.text) <= 0):
|
|
return False
|
|
|
|
paragraphs = outlineText.text.split("\n")
|
|
firstParagraph = True
|
|
|
|
for paragraph in paragraphs:
|
|
if firstParagraph:
|
|
firstParagraph = False
|
|
else:
|
|
self.editorTextBuffer.insert_with_tags_by_name(end_iter, "\n", "none")
|
|
end_iter = self.editorTextBuffer.get_end_iter()
|
|
|
|
self.editorTextBuffer.insert_with_tags_by_name(end_iter, paragraph, "p")
|
|
end_iter = self.editorTextBuffer.get_end_iter()
|
|
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def loadEditorData(self, outlineItem: OutlineItem | None = None):
|
|
self.editorItems = list()
|
|
self.outlineItem = None
|
|
|
|
start_iter, end_iter = self.editorTextBuffer.get_bounds()
|
|
self.editorTextBuffer.delete(start_iter, end_iter)
|
|
|
|
if outlineItem is None:
|
|
self.editorItems = self.project.outline.items
|
|
elif type(outlineItem) is OutlineFolder:
|
|
self.editorItems = outlineItem.items
|
|
|
|
if outlineItem is None:
|
|
for item in self.editorItems:
|
|
self.__appendOutlineItemText(item)
|
|
else:
|
|
self.__appendOutlineItemText(outlineItem)
|
|
|
|
self.editorFlowbox.foreach(self.editorFlowbox.remove)
|
|
if len(self.editorItems) <= 0:
|
|
self.outlineItem = outlineItem
|
|
return
|
|
|
|
for item in self.editorItems:
|
|
self.editorFlowbox.insert(GridItem(item).widget, -1)
|
|
|
|
self.refreshEditorOutlineStore()
|
|
self.outlineItem = outlineItem
|
|
|
|
def _outlineSelectionChanged(self, selection: Gtk.TreeSelection):
|
|
model, tree_iter = selection.get_selected()
|
|
|
|
if tree_iter is None:
|
|
self.unloadOutlineData()
|
|
return
|
|
|
|
outlineItem = self.project.outline.getItemByID(model[tree_iter][0])
|
|
|
|
self.loadOutlineData(outlineItem)
|
|
|
|
def _editorOutlineSelectionChanged(self, selection: Gtk.TreeSelection):
|
|
if len(self.editorItems) == 0:
|
|
return
|
|
|
|
model, tree_iter = selection.get_selected()
|
|
|
|
if tree_iter is None:
|
|
return
|
|
|
|
outlineItem = self.project.outline.getItemByID(model[tree_iter][0])
|
|
|
|
try:
|
|
index = self.editorItems.index(outlineItem)
|
|
except ValueError:
|
|
self.editorFlowbox.unselect_all()
|
|
return
|
|
|
|
for child in self.editorFlowbox.get_children():
|
|
if index == child.get_index():
|
|
self.editorFlowbox.select_child(child)
|
|
break
|
|
|
|
def _editorFlowboxSelectionChanged(self, box: Gtk.FlowBox):
|
|
if len(self.editorItems) == 0:
|
|
return
|
|
|
|
children = box.get_selected_children()
|
|
child = children[0] if len(children) > 0 else None
|
|
|
|
if child is None:
|
|
self.editorOutlineSelection.unselect_all()
|
|
return
|
|
|
|
index = child.get_index()
|
|
if (index < 0) or (index >= len(self.editorItems)):
|
|
return
|
|
|
|
outlineItem = self.editorItems[index]
|
|
|
|
def selectEditorOutlineItem(model: Gtk.TreeModel, path: Gtk.TreePath, _iter: Gtk.TreeIter, outline_id: int):
|
|
if model[_iter][0] != outline_id:
|
|
return False
|
|
|
|
if not self.editorOutlineSelection.path_is_selected(path):
|
|
self.editorOutlineSelection.select_path(path)
|
|
|
|
return True
|
|
|
|
self.editorOutlineStore.foreach(selectEditorOutlineItem, outlineItem.UID.value)
|
|
|
|
def __openOutlineItem(self, outlineItem: OutlineItem | None):
|
|
if outlineItem is None:
|
|
self.outlineSelection.unselect_all()
|
|
return
|
|
|
|
def selectOutlineItem(model: Gtk.TreeModel, path: Gtk.TreePath, _iter: Gtk.TreeIter, outline_id: int):
|
|
if model[_iter][0] != outline_id:
|
|
return False
|
|
|
|
if not self.outlineView.row_expanded(path):
|
|
self.outlineView.expand_to_path(path)
|
|
|
|
if not self.outlineSelection.path_is_selected(path):
|
|
self.outlineSelection.select_path(path)
|
|
|
|
return True
|
|
|
|
self.outlineStore.foreach(selectOutlineItem, outlineItem.UID.value)
|
|
|
|
def _editorFlowboxChildActivated(self, box: Gtk.FlowBox, child: Gtk.FlowBoxChild):
|
|
if len(self.editorItems) == 0:
|
|
return
|
|
|
|
if child is None:
|
|
self.__openOutlineItem(None)
|
|
return
|
|
|
|
index = child.get_index()
|
|
if (index < 0) or (index >= len(self.editorItems)):
|
|
return
|
|
|
|
outlineItem = self.editorItems[index]
|
|
|
|
self.__openOutlineItem(outlineItem)
|
|
|
|
def _upButtonClicked(self, button: Gtk.Button):
|
|
if self.outlineItem is None:
|
|
return
|
|
|
|
self.__openOutlineItem(self.outlineItem.parentItem())
|
|
|
|
def show(self):
|
|
self.widget.show_all()
|