mirror of
https://github.com/olivierkes/manuskript.git
synced 2024-09-30 01:07:24 +13:00
b0d9ac29e7
Signed-off-by: TheJackiMonster <thejackimonster@gmail.com>
538 lines
20 KiB
Python
538 lines
20 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
|
|
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, unique_name_checker
|
|
|
|
|
|
class CharactersView:
|
|
|
|
def __init__(self, project):
|
|
self.characterTemplates = project.character_templates # The template for detailed info
|
|
self.characters = project.characters
|
|
self.character = None
|
|
|
|
builder = Gtk.Builder()
|
|
builder.add_from_file("ui/characters.glade")
|
|
|
|
self.widget = builder.get_object("characters_view")
|
|
self.notebook = builder.get_object("character_notebook")
|
|
|
|
self.charactersStore = builder.get_object("characters_store")
|
|
self.refreshCharacterStore()
|
|
|
|
self.filteredCharactersStore = builder.get_object("filtered_characters_store")
|
|
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.filterCharactersBuffer = builder.get_object("filter_characters")
|
|
|
|
self.filterCharactersBuffer.connect("deleted-text", self._filterCharactersDeletedText)
|
|
self.filterCharactersBuffer.connect("inserted-text", self._filterCharactersInsertedText)
|
|
|
|
self.filteredCharactersStore.set_visible_func(self._filterCharacters)
|
|
self.filteredCharactersStore.refilter()
|
|
|
|
self.mainCharactersStore.set_visible_func(lambda model, iterator, userdata:
|
|
model[iterator][3] == Importance.MAIN.value)
|
|
self.secondaryCharactersStore.set_visible_func(lambda model, iterator, userdata:
|
|
model[iterator][3] == Importance.SECONDARY.value)
|
|
self.minorCharactersStore.set_visible_func(lambda model, iterator, userdata:
|
|
model[iterator][3] == Importance.MINOR.value)
|
|
|
|
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.addCharacterButton = builder.get_object("add_character")
|
|
self.removeCharacterButton = builder.get_object("remove_character")
|
|
|
|
self.addCharacterButton.connect("clicked", self._addCharacterClicked)
|
|
self.removeCharacterButton.connect("clicked", self._removeCharacterClicked)
|
|
|
|
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.importanceCombo.connect("changed", self._importanceChanged)
|
|
self.allowPOVCheck.connect("toggled", self._allowPOVToggled)
|
|
|
|
self.detailsStore = builder.get_object("details_store")
|
|
self.detailsSelection = builder.get_object("details_selection")
|
|
self.addDetailsButton = builder.get_object("add_details")
|
|
self.removeDetailsButton = builder.get_object("remove_details")
|
|
self.charecterDetailsMenuButton = builder.get_object("characters_details_menu_button")
|
|
self.newTemplateButton = builder.get_object("new_template_button")
|
|
self.newTemplateEntry = builder.get_object("new_template_entry")
|
|
self.newTemplateEntryBuffer = builder.get_object("new_template_entry_buffer")
|
|
self.charecterDetailsMenuAppendBox = builder.get_object("template_select_box")
|
|
self.charecterDetailsMenuTemplateBox = builder.get_object("template_select_box2")
|
|
self.detailsNameRenderer = builder.get_object("details_name")
|
|
self.detailsValueRenderer = builder.get_object("details_value")
|
|
|
|
self.addDetailsButton.connect("clicked", self._addDetailsClicked)
|
|
self.removeDetailsButton.connect("clicked", self._removeDetailsClicked)
|
|
self.charecterDetailsMenuButton.connect("clicked", self._onCharecterDetailsMenuClicked)
|
|
self.newTemplateButton.connect("clicked", self._onNewTemplateButtonClicked)
|
|
self.detailsNameRenderer.connect("edited", self._detailsNameEdited)
|
|
self.detailsValueRenderer.connect("edited", self._detailsValueEdited)
|
|
|
|
self.nameBuffer = builder.get_object("name")
|
|
self.motivationBuffer = builder.get_object("motivation")
|
|
self.goalBuffer = builder.get_object("goal")
|
|
self.conflictBuffer = builder.get_object("conflict")
|
|
self.epiphanyBuffer = builder.get_object("epiphany")
|
|
self.oneSentenceBuffer = builder.get_object("one_sentence_summary")
|
|
self.oneParagraphBuffer = builder.get_object("one_paragraph_summary")
|
|
self.summaryBuffer = builder.get_object("summary")
|
|
self.notesBuffer = builder.get_object("notes")
|
|
|
|
self.nameBuffer.connect("deleted-text", self._nameDeletedText)
|
|
self.nameBuffer.connect("inserted-text", self._nameInsertedText)
|
|
|
|
self.motivationBuffer.connect("changed", self._motivationChanged)
|
|
self.goalBuffer.connect("changed", self._goalChanged)
|
|
self.conflictBuffer.connect("changed", self._conflictChanged)
|
|
self.epiphanyBuffer.connect("changed", self._epiphanyChanged)
|
|
self.oneSentenceBuffer.connect("changed", self._oneSentenceChanged)
|
|
self.oneParagraphBuffer.connect("changed", self._oneParagraphChanged)
|
|
self.summaryBuffer.connect("changed", self._summaryChanged)
|
|
self.notesBuffer.connect("changed", self._notesChanged)
|
|
|
|
self.unloadCharacterData()
|
|
|
|
|
|
def refreshCharacterStore(self):
|
|
self.charactersStore.clear()
|
|
|
|
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))
|
|
|
|
def loadCharacterData(self, character: Character):
|
|
self.character = None
|
|
|
|
self.colorButton.set_rgba(rgbaFromColor(character.color))
|
|
self.importanceCombo.set_active(Importance.asValue(character.importance))
|
|
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.detailsStore.clear()
|
|
|
|
for (key, value) in character.details.items():
|
|
tree_iter = self.detailsStore.append()
|
|
|
|
if tree_iter is None:
|
|
continue
|
|
|
|
self.detailsStore.set_value(tree_iter, 0, validString(key))
|
|
self.detailsStore.set_value(tree_iter, 1, validString(value))
|
|
|
|
self.character = character
|
|
self.notebook.set_sensitive(True)
|
|
|
|
def unloadCharacterData(self):
|
|
self.character = None
|
|
self.notebook.set_sensitive(False)
|
|
|
|
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)
|
|
|
|
self.detailsStore.clear()
|
|
|
|
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 _addCharacterClicked(self, button: Gtk.Button):
|
|
name = invalidString(self.filterCharactersBuffer.get_text())
|
|
character = self.characters.add(name)
|
|
|
|
if character is None:
|
|
return
|
|
|
|
if self.character is not None:
|
|
character.importance = self.character.importance
|
|
|
|
self.refreshCharacterStore()
|
|
|
|
def _removeCharacterClicked(self, button: Gtk.Button):
|
|
if self.character is None:
|
|
return
|
|
|
|
self.character.remove()
|
|
self.refreshCharacterStore()
|
|
|
|
def _filterCharacters(self, model, iterator, userdata):
|
|
name = validString(model[iterator][1])
|
|
text = validString(self.filterCharactersBuffer.get_text())
|
|
|
|
return text.lower() in name.lower()
|
|
|
|
def __filterCharactersChanged(self, buffer: Gtk.EntryBuffer):
|
|
self.filteredCharactersStore.refilter()
|
|
|
|
def _filterCharactersDeletedText(self, buffer: Gtk.EntryBuffer, position: int, n_chars: int):
|
|
self.__filterCharactersChanged(buffer)
|
|
|
|
def _filterCharactersInsertedText(self, buffer: Gtk.EntryBuffer, position: int, chars: str, n_chars: int):
|
|
self.__filterCharactersChanged(buffer)
|
|
|
|
def _colorSet(self, button: Gtk.ColorButton):
|
|
if self.character is None:
|
|
return
|
|
|
|
color = button.get_rgba()
|
|
|
|
red = int(color.red * 255)
|
|
green = int(color.green * 255)
|
|
blue = int(color.blue * 255)
|
|
|
|
color = Color(red, green, blue)
|
|
|
|
self.character.color = color
|
|
|
|
character_id = self.character.UID.value
|
|
|
|
for row in self.charactersStore:
|
|
if row[0] == character_id:
|
|
row[2] = pixbufFromColor(color)
|
|
break
|
|
|
|
def _importanceChanged(self, combo: Gtk.ComboBox):
|
|
if self.character is None:
|
|
return
|
|
|
|
tree_iter = combo.get_active_iter()
|
|
|
|
if tree_iter is None:
|
|
return
|
|
|
|
model = combo.get_model()
|
|
value = model[tree_iter][1]
|
|
|
|
importance = Importance.fromValue(value)
|
|
|
|
if (importance is None) or (self.character.importance == importance):
|
|
return
|
|
|
|
self.character.importance = importance
|
|
|
|
character_id = self.character.UID.value
|
|
|
|
for row in self.charactersStore:
|
|
if row[0] == character_id:
|
|
row[3] = Importance.asValue(importance)
|
|
break
|
|
|
|
self.mainCharactersStore.refilter()
|
|
self.secondaryCharactersStore.refilter()
|
|
self.minorCharactersStore.refilter()
|
|
|
|
selection = self.characterSelections[importance.value]
|
|
tree_view = selection.get_tree_view()
|
|
model = tree_view.get_model()
|
|
|
|
for row in model:
|
|
if row[0] == character_id:
|
|
selection.select_iter(row.iter)
|
|
break
|
|
|
|
def _allowPOVToggled(self, button: Gtk.ToggleButton):
|
|
if self.character is None:
|
|
return
|
|
|
|
self.character.POV = button.get_active()
|
|
|
|
def _addDetailsClicked(self, button: Gtk.Button):
|
|
if self.character is None:
|
|
return
|
|
|
|
tree_iter = self.detailsStore.append()
|
|
|
|
if tree_iter is None:
|
|
return
|
|
|
|
name = unique_name_checker.get_unique_name_for_dictionary(self.character.details, "Description")
|
|
value = "Value"
|
|
|
|
self.detailsStore.set_value(tree_iter, 0, name)
|
|
self.detailsStore.set_value(tree_iter, 1, value)
|
|
|
|
self.character.details[name] = value
|
|
|
|
def _removeDetailsClicked(self, button: Gtk.Button):
|
|
if self.character is None:
|
|
return
|
|
|
|
model, tree_iter = self.detailsSelection.get_selected()
|
|
|
|
if (model is None) or (tree_iter is None):
|
|
return
|
|
|
|
name = model.get_value(tree_iter, 0)
|
|
model.remove(tree_iter)
|
|
|
|
self.character.details.pop(name)
|
|
|
|
def _updateCharecterDetailsMenu(self):
|
|
def clear_container(container):
|
|
data = container.get_children()
|
|
for d in data:
|
|
container.remove(d)
|
|
clear_container( self.charecterDetailsMenuAppendBox)
|
|
clear_container(self.charecterDetailsMenuTemplateBox)
|
|
for template_name in self.characterTemplates.templates:
|
|
button = Gtk.Button(label=template_name) # TODO: turn into ModelButton
|
|
button.connect("clicked", self._appendTemplateClicked, template_name)
|
|
self.charecterDetailsMenuAppendBox.add(button)
|
|
# Now we do the buttons for charecterDetailsMenuTemplateBox
|
|
box = Gtk.Box()
|
|
label = Gtk.Label(label=template_name)
|
|
overwrite_button = Gtk.Button()
|
|
overwrite_button.add(Gtk.Image(icon_name='emblem-synchronizing-symbolic'))
|
|
overwrite_button.connect("clicked", self._updateTemplateClicked, template_name)
|
|
overwrite_button.set_tooltip_markup ('Overwrite template with text of current file') # TODO: This might be an issue when it comes to translating
|
|
delete_button = Gtk.Button()
|
|
delete_button.add(Gtk.Image(icon_name='list-remove-symbolic'))
|
|
delete_button.set_tooltip_markup ('Delete') # TODO: This might be an issue when it comes to translating
|
|
delete_button.connect("clicked", self._deleteTemplateClicked, template_name)
|
|
box.pack_end(delete_button, False, False, 0)
|
|
box.pack_end(overwrite_button, False, False, 0)
|
|
box.pack_start(label, True, False, 0)
|
|
self.charecterDetailsMenuTemplateBox.add(box)
|
|
|
|
self.charecterDetailsMenuAppendBox.show_all()
|
|
self.charecterDetailsMenuTemplateBox.show_all()
|
|
|
|
def _onCharecterDetailsMenuClicked(self, button: Gtk.MenuButton):
|
|
self._updateCharecterDetailsMenu()
|
|
|
|
def _updateTemplateClicked(self, button: Gtk.ModelButton, template_name):
|
|
if self.character is None:
|
|
return
|
|
self.characterTemplates.templates[template_name] = self.character.details # TODO: Add A warning? Or should there be undo/ redo when revisions are written.
|
|
|
|
def _deleteTemplateClicked(self, button: Gtk.ModelButton, template_name):
|
|
del self.characterTemplates.templates[template_name]
|
|
self._updateCharecterDetailsMenu()
|
|
|
|
def _appendTemplateClicked(self, button: Gtk.ModelButton, template_name):
|
|
if self.character is None:
|
|
return
|
|
self.character.details.update(self.characterTemplates.templates[template_name])
|
|
|
|
# We have to reload the character
|
|
self.loadCharacterData(self.character)
|
|
|
|
def _onNewTemplateButtonClicked(self, button: Gtk.Button):
|
|
text = self.newTemplateEntryBuffer.get_text()
|
|
if text == "":
|
|
return
|
|
if text in self.characterTemplates.templates:
|
|
new_text = unique_name_checker.get_unique_name_for_dictionary(self.characterTemplates.templates, text)
|
|
self.newTemplateEntryBuffer.set_text(new_text, -1) # TODO: Add a warning
|
|
return
|
|
else:
|
|
self.characterTemplates.templates[text] = self.character.details
|
|
self._updateCharecterDetailsMenu()
|
|
|
|
def _detailsNameEdited(self, renderer: Gtk.CellRendererText, path: str, text: str):
|
|
if self.character is None:
|
|
return
|
|
|
|
model, tree_iter = self.detailsSelection.get_selected()
|
|
|
|
if (model is None) or (tree_iter is None):
|
|
return
|
|
|
|
text_to_set = unique_name_checker.get_unique_name_for_dictionary(self.character.details, text)
|
|
name = model.get_value(tree_iter, 0)
|
|
model.set_value(tree_iter, 0, text_to_set)
|
|
|
|
self.character.details[text_to_set] = self.character.details.pop(name)
|
|
|
|
def _detailsValueEdited(self, renderer: Gtk.CellRendererText, path: str, text: str):
|
|
if self.character is None:
|
|
return
|
|
|
|
model, tree_iter = self.detailsSelection.get_selected()
|
|
|
|
if (model is None) or (tree_iter is None):
|
|
return
|
|
|
|
name = model.get_value(tree_iter, 0)
|
|
model.set_value(tree_iter, 1, text)
|
|
|
|
self.character.details[name] = text
|
|
|
|
def __nameChanged(self, buffer: Gtk.EntryBuffer):
|
|
if self.character is None:
|
|
return
|
|
|
|
text = buffer.get_text()
|
|
name = invalidString(text)
|
|
|
|
self.character.name = name
|
|
|
|
character_id = self.character.UID.value
|
|
|
|
for row in self.charactersStore:
|
|
if row[0] == character_id:
|
|
row[1] = validString(name)
|
|
break
|
|
|
|
def _nameDeletedText(self, buffer: Gtk.EntryBuffer, position: int, n_chars: int):
|
|
self.__nameChanged(buffer)
|
|
|
|
def _nameInsertedText(self, buffer: Gtk.EntryBuffer, position: int, chars: str, n_chars: int):
|
|
self.__nameChanged(buffer)
|
|
|
|
def _motivationChanged(self, buffer: Gtk.TextBuffer):
|
|
if self.character 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.character.motivation = invalidString(text)
|
|
|
|
def _goalChanged(self, buffer: Gtk.TextBuffer):
|
|
if self.character 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.character.goal = invalidString(text)
|
|
|
|
def _conflictChanged(self, buffer: Gtk.TextBuffer):
|
|
if self.character 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.character.conflict = invalidString(text)
|
|
|
|
def _epiphanyChanged(self, buffer: Gtk.TextBuffer):
|
|
if self.character 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.character.epiphany = invalidString(text)
|
|
|
|
def _oneSentenceChanged(self, buffer: Gtk.TextBuffer):
|
|
if self.character 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.character.summarySentence = invalidString(text)
|
|
|
|
def _oneParagraphChanged(self, buffer: Gtk.TextBuffer):
|
|
if self.character 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.character.summaryParagraph = invalidString(text)
|
|
|
|
def _summaryChanged(self, buffer: Gtk.TextBuffer):
|
|
if self.character 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.character.summaryFull = invalidString(text)
|
|
|
|
def _notesChanged(self, buffer: Gtk.TextBuffer):
|
|
if self.character 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.character.notes = invalidString(text)
|
|
|
|
def show(self):
|
|
self.widget.show_all()
|
|
|