manuskript/manuskript/ui/views/editorView.py
TheJackiMonster 8110b204ca
Improve outline selection in editor view
Signed-off-by: TheJackiMonster <thejackimonster@gmail.com>
2023-02-20 15:55:23 +01:00

373 lines
13 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):
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")
end_iter = self.editorTextBuffer.get_end_iter()
self.editorTextBuffer.insert_with_tags_by_name(end_iter, outlineItem.title + "\n", "h1")
for item in outlineItem:
self.__appendOutlineItemText(item)
return True
elif type(outlineItem) is OutlineText:
outlineText: OutlineText = outlineItem
if (outlineText.text is None) or (len(outlineText.text) <= 0):
return False
self.editorTextBuffer.insert(end_iter, outlineText.text)
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
elif type(outlineItem) is OutlineText:
self.__appendOutlineItemText(outlineItem)
self.editorFlowbox.foreach(self.editorFlowbox.remove)
if len(self.editorItems) <= 0:
self.outlineItem = outlineItem
return
for item in self.editorItems:
self.__appendOutlineItemText(item)
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()