Adds: revisions

This commit is contained in:
Olivier Keshavjee 2015-07-03 18:41:18 +02:00
parent 3a8062f710
commit dcd387590c
13 changed files with 658 additions and 98 deletions

View file

@ -4,6 +4,7 @@ FORMS += ../src/ui/welcome_ui.ui
FORMS += ../src/ui/sldImportance_ui.ui
FORMS += ../src/ui/cheatSheet_ui.ui
FORMS += ../src/ui/compileDialog_ui.ui
FORMS += ../src/ui/revisions_ui.ui
FORMS += ../src/ui/editors/editorWidget_ui.ui
@ -29,6 +30,7 @@ SOURCES += ../src/ui/sldImportance.py
SOURCES += ../src/ui/welcome.py
SOURCES += ../src/ui/cheatSheet.py
SOURCES += ../src/ui/compileDialog.py
SOURCES += ../src/ui/revisions.py
SOURCES += ../src/ui/editors/editorWidget.py
SOURCES += ../src/ui/editors/fullScreenEditor.py

Binary file not shown.

View file

@ -214,7 +214,7 @@
<translation>Contexte</translation>
</message>
<message>
<location filename="../src/mainWindow.py" line="1019"/>
<location filename="../src/mainWindow.py" line="1022"/>
<source>Outline</source>
<translation>Plan</translation>
</message>
@ -329,62 +329,62 @@
<translation>Mode</translation>
</message>
<message>
<location filename="../src/mainWindow.py" line="852"/>
<location filename="../src/mainWindow.py" line="854"/>
<source> (~{} pages)</source>
<translation> (~{} pages)</translation>
</message>
<message>
<location filename="../src/mainWindow.py" line="878"/>
<location filename="../src/mainWindow.py" line="881"/>
<source>Enter infos about your book, and yourself.</source>
<translation>Entrez toutes les informations relatives au livre, ainsi qu&apos;à vous.</translation>
</message>
<message>
<location filename="../src/mainWindow.py" line="884"/>
<location filename="../src/mainWindow.py" line="887"/>
<source>Take time to think about a one sentance (~50 words) summary of your book. Then expand it to a paragraph, then to a page, then to a full summary.</source>
<translation>Prenez le temps de réfléchir à un résumé de votre livre, en une phrase (~50 mots). Puis augmentez cette phrase en un paragraphe, puis en une page, puis en un résumé complet.</translation>
</message>
<message>
<location filename="../src/mainWindow.py" line="887"/>
<location filename="../src/mainWindow.py" line="890"/>
<source>Create your characters.</source>
<translation>Créez ici vos personnage.</translation>
</message>
<message>
<location filename="../src/mainWindow.py" line="890"/>
<location filename="../src/mainWindow.py" line="893"/>
<source>Develop plots.</source>
<translation>Développez vos intrigues.</translation>
</message>
<message>
<location filename="../src/mainWindow.py" line="893"/>
<location filename="../src/mainWindow.py" line="896"/>
<source>Create the outline of your masterpiece.</source>
<translation>Créez le plan de votre chef-d&apos;œuvre.</translation>
</message>
<message>
<location filename="../src/mainWindow.py" line="896"/>
<location filename="../src/mainWindow.py" line="899"/>
<source>Write.</source>
<translation>Écrivez.</translation>
</message>
<message>
<location filename="../src/mainWindow.py" line="899"/>
<location filename="../src/mainWindow.py" line="902"/>
<source>Debug infos. Sometimes useful.</source>
<translation>Des infos pour débugger des fois pendant qu&apos;on code c&apos;est utile.</translation>
</message>
<message>
<location filename="../src/mainWindow.py" line="913"/>
<location filename="../src/mainWindow.py" line="916"/>
<source>Dictionary</source>
<translation>Dictionnaire</translation>
</message>
<message>
<location filename="../src/mainWindow.py" line="926"/>
<location filename="../src/mainWindow.py" line="929"/>
<source>Install PyEnchant to use spellcheck</source>
<translation>Installez PyEnchant pour profiter du correcteur orthographique</translation>
</message>
<message>
<location filename="../src/mainWindow.py" line="856"/>
<location filename="../src/mainWindow.py" line="858"/>
<source>Words: {}{}</source>
<translation>Mots: {}{}</translation>
</message>
<message>
<location filename="../src/mainWindow.py" line="1030"/>
<location filename="../src/mainWindow.py" line="1033"/>
<source>Text</source>
<translation>Texte</translation>
</message>
@ -424,7 +424,7 @@
<translation>Et si...?</translation>
</message>
<message>
<location filename="../src/mainWindow.py" line="1018"/>
<location filename="../src/mainWindow.py" line="1021"/>
<source>Index cards</source>
<translation>Cartes</translation>
</message>
@ -444,7 +444,7 @@
<translation>F9</translation>
</message>
<message>
<location filename="../src/mainWindow.py" line="1017"/>
<location filename="../src/mainWindow.py" line="1020"/>
<source>Tree</source>
<translation>Arbre</translation>
</message>
@ -469,77 +469,77 @@
<translation>Réglages</translation>
</message>
<message>
<location filename="../src/mainWindow.py" line="510"/>
<location filename="../src/mainWindow.py" line="512"/>
<source>Project {} saved.</source>
<translation>Le projet {} a é enregistré.</translation>
</message>
<message>
<location filename="../src/mainWindow.py" line="581"/>
<location filename="../src/mainWindow.py" line="583"/>
<source>Project {} loaded.</source>
<translation>Le projet {} a é chargé.</translation>
</message>
<message>
<location filename="../src/mainWindow.py" line="881"/>
<location filename="../src/mainWindow.py" line="884"/>
<source>The basic situation, in the form of a &apos;What if...?&apos; question. Ex: &apos;What if the most dangerous evil wizard could wasn&apos;t abled to kill a baby?&apos; (Harry Potter)</source>
<translation>La situation de base, sous la forme d&apos;une question: &quot;Et si...?&quot; Par exemple: &quot;Et si le plus dangereux magiciens mauvais n&apos;était pas capable de tuer un bébé?&quot; (Harry Potter)</translation>
</message>
<message>
<location filename="../src/mainWindow.py" line="1009"/>
<location filename="../src/mainWindow.py" line="1012"/>
<source>Nothing</source>
<translation>Rien</translation>
</message>
<message>
<location filename="../src/mainWindow.py" line="1010"/>
<location filename="../src/mainWindow.py" line="1013"/>
<source>POV</source>
<translation>POV</translation>
</message>
<message>
<location filename="../src/mainWindow.py" line="1011"/>
<location filename="../src/mainWindow.py" line="1014"/>
<source>Label</source>
<translation>Label</translation>
</message>
<message>
<location filename="../src/mainWindow.py" line="1012"/>
<location filename="../src/mainWindow.py" line="1015"/>
<source>Progress</source>
<translation>Progrès</translation>
</message>
<message>
<location filename="../src/mainWindow.py" line="1013"/>
<location filename="../src/mainWindow.py" line="1016"/>
<source>Compile</source>
<translation>Compilation</translation>
</message>
<message>
<location filename="../src/mainWindow.py" line="1036"/>
<location filename="../src/mainWindow.py" line="1039"/>
<source>Icon color</source>
<translation>Couleur de l&apos;icone</translation>
</message>
<message>
<location filename="../src/mainWindow.py" line="1037"/>
<location filename="../src/mainWindow.py" line="1040"/>
<source>Text color</source>
<translation>Couleur du texte</translation>
</message>
<message>
<location filename="../src/mainWindow.py" line="1038"/>
<location filename="../src/mainWindow.py" line="1041"/>
<source>Background color</source>
<translation>Couleur de l&apos;arrière-plan</translation>
</message>
<message>
<location filename="../src/mainWindow.py" line="1029"/>
<location filename="../src/mainWindow.py" line="1032"/>
<source>Icon</source>
<translation>Icone</translation>
</message>
<message>
<location filename="../src/mainWindow.py" line="1031"/>
<location filename="../src/mainWindow.py" line="1034"/>
<source>Background</source>
<translation>Arrière-plan</translation>
</message>
<message>
<location filename="../src/mainWindow.py" line="1032"/>
<location filename="../src/mainWindow.py" line="1035"/>
<source>Border</source>
<translation>Bordure</translation>
</message>
<message>
<location filename="../src/mainWindow.py" line="1033"/>
<location filename="../src/mainWindow.py" line="1036"/>
<source>Corner</source>
<translation>Coin</translation>
</message>
@ -549,22 +549,22 @@
<translation>Fermer le projet</translation>
</message>
<message>
<location filename="../src/mainWindow.py" line="346"/>
<location filename="../src/mainWindow.py" line="347"/>
<source>The file {} does not exist. Try again.</source>
<translation>Le fichier {} n&apos;existe pas. Essayez encore.</translation>
</message>
<message>
<location filename="../src/mainWindow.py" line="584"/>
<location filename="../src/mainWindow.py" line="586"/>
<source>Project {} loaded with some errors:</source>
<translation>Le projet {} a é chargé, avec des erreurs:</translation>
</message>
<message>
<location filename="../src/mainWindow.py" line="586"/>
<location filename="../src/mainWindow.py" line="588"/>
<source> * {} wasn&apos;t found in project file.</source>
<translation>* {} n&apos;a pas é trouvé dans le fichier du projet.</translation>
</message>
<message>
<location filename="../src/mainWindow.py" line="587"/>
<location filename="../src/mainWindow.py" line="589"/>
<source>Project {} loaded with some errors.</source>
<translation>Le projet {} a é chargé avec des erreurs.</translation>
</message>
@ -1051,7 +1051,7 @@ des lignes:</translation>
<context>
<name>SpellAction</name>
<message>
<location filename="../src/ui/views/textEditView.py" line="432"/>
<location filename="../src/ui/views/textEditView.py" line="445"/>
<source>Spelling Suggestions</source>
<translation>Suggestions</translation>
</message>
@ -1115,27 +1115,27 @@ des lignes:</translation>
<translation>Filtre</translation>
</message>
<message>
<location filename="../src/ui/cheatSheet.py" line="56"/>
<location filename="../src/ui/cheatSheet.py" line="58"/>
<source>Minor</source>
<translation>Mineur</translation>
</message>
<message>
<location filename="../src/ui/cheatSheet.py" line="56"/>
<location filename="../src/ui/cheatSheet.py" line="58"/>
<source>Secondary</source>
<translation>Secondaire</translation>
</message>
<message>
<location filename="../src/ui/cheatSheet.py" line="56"/>
<location filename="../src/ui/cheatSheet.py" line="58"/>
<source>Main</source>
<translation>Principal</translation>
</message>
<message>
<location filename="../src/ui/cheatSheet.py" line="59"/>
<location filename="../src/ui/cheatSheet.py" line="61"/>
<source>Characters</source>
<translation>Personnages</translation>
</message>
<message>
<location filename="../src/ui/cheatSheet.py" line="71"/>
<location filename="../src/ui/cheatSheet.py" line="73"/>
<source>Texts</source>
<translation>Textes</translation>
</message>
@ -1323,17 +1323,17 @@ des lignes:</translation>
<context>
<name>mainEditor</name>
<message>
<location filename="../src/ui/editors/mainEditor.py" line="105"/>
<location filename="../src/ui/editors/mainEditor.py" line="101"/>
<source>Root</source>
<translation>Racine</translation>
</message>
<message>
<location filename="../src/ui/editors/mainEditor.py" line="171"/>
<location filename="../src/ui/editors/mainEditor.py" line="183"/>
<source>{} words / {}</source>
<translation>{} mots / {}</translation>
</message>
<message>
<location filename="../src/ui/editors/mainEditor.py" line="176"/>
<location filename="../src/ui/editors/mainEditor.py" line="188"/>
<source>{} words</source>
<translation>{} mots</translation>
</message>
@ -1362,8 +1362,13 @@ des lignes:</translation>
</message>
<message>
<location filename="../src/ui/views/metadataView_ui.ui" line="88"/>
<source>Notes</source>
<translation>Notes</translation>
<source>Notes / References</source>
<translation>Notes / Références</translation>
</message>
<message>
<location filename="../src/ui/views/metadataView_ui.ui" line="109"/>
<source>Revisions</source>
<translation>Révisions</translation>
</message>
</context>
<context>
@ -1442,47 +1447,47 @@ des lignes:</translation>
<context>
<name>outlineModel</name>
<message>
<location filename="../src/models/outlineModel.py" line="141"/>
<location filename="../src/models/outlineModel.py" line="148"/>
<source>Title</source>
<translation>Titre</translation>
</message>
<message>
<location filename="../src/models/outlineModel.py" line="143"/>
<location filename="../src/models/outlineModel.py" line="150"/>
<source>POV</source>
<translation>POV</translation>
</message>
<message>
<location filename="../src/models/outlineModel.py" line="145"/>
<location filename="../src/models/outlineModel.py" line="152"/>
<source>Label</source>
<translation>Label</translation>
</message>
<message>
<location filename="../src/models/outlineModel.py" line="147"/>
<location filename="../src/models/outlineModel.py" line="154"/>
<source>Status</source>
<translation>Status</translation>
</message>
<message>
<location filename="../src/models/outlineModel.py" line="149"/>
<location filename="../src/models/outlineModel.py" line="156"/>
<source>Compile</source>
<translation>Compilation</translation>
</message>
<message>
<location filename="../src/models/outlineModel.py" line="151"/>
<location filename="../src/models/outlineModel.py" line="158"/>
<source>Word count</source>
<translation>Nombre de mots</translation>
</message>
<message>
<location filename="../src/models/outlineModel.py" line="153"/>
<location filename="../src/models/outlineModel.py" line="160"/>
<source>Goal</source>
<translation>Goal</translation>
</message>
<message>
<location filename="../src/models/outlineModel.py" line="683"/>
<location filename="../src/models/outlineModel.py" line="713"/>
<source>{} words / {} ({})</source>
<translation>{} mots / {} ({})</translation>
</message>
<message>
<location filename="../src/models/outlineModel.py" line="688"/>
<location filename="../src/models/outlineModel.py" line="718"/>
<source>{} words</source>
<translation>{} mots</translation>
</message>
@ -1549,27 +1554,27 @@ des lignes:</translation>
<context>
<name>plotModel</name>
<message>
<location filename="../src/models/plotModel.py" line="69"/>
<location filename="../src/models/plotModel.py" line="79"/>
<source>New plot</source>
<translation>Nouvelle intrigue</translation>
</message>
<message>
<location filename="../src/models/plotModel.py" line="107"/>
<location filename="../src/models/plotModel.py" line="117"/>
<source>New subplot</source>
<translation>Nouvelle sous-intrigue</translation>
</message>
<message>
<location filename="../src/models/plotModel.py" line="163"/>
<location filename="../src/models/plotModel.py" line="173"/>
<source>Main</source>
<translation>Principale</translation>
</message>
<message>
<location filename="../src/models/plotModel.py" line="163"/>
<location filename="../src/models/plotModel.py" line="173"/>
<source>Secondary</source>
<translation>Secondaire</translation>
</message>
<message>
<location filename="../src/models/plotModel.py" line="163"/>
<location filename="../src/models/plotModel.py" line="173"/>
<source>Minor</source>
<translation>Mineure</translation>
</message>
@ -1577,20 +1582,25 @@ des lignes:</translation>
<context>
<name>plotTreeView</name>
<message>
<location filename="../src/ui/views/plotTreeView.py" line="85"/>
<location filename="../src/ui/views/plotTreeView.py" line="112"/>
<source>Main</source>
<translation>Principale</translation>
</message>
<message>
<location filename="../src/ui/views/plotTreeView.py" line="85"/>
<location filename="../src/ui/views/plotTreeView.py" line="112"/>
<source>Secondary</source>
<translation>Secondaire</translation>
</message>
<message>
<location filename="../src/ui/views/plotTreeView.py" line="85"/>
<location filename="../src/ui/views/plotTreeView.py" line="112"/>
<source>Minor</source>
<translation>Mineure</translation>
</message>
<message>
<location filename="../src/ui/views/plotTreeView.py" line="173"/>
<source>**Plot:** {}</source>
<translation>**Intrigue:** {}</translation>
</message>
</context>
<context>
<name>propertiesView</name>
@ -1638,111 +1648,154 @@ des lignes:</translation>
<context>
<name>references</name>
<message>
<location filename="../src/models/references.py" line="243"/>
<location filename="../src/models/references.py" line="240"/>
<source>Unknown reference: {}.</source>
<translation>Référence inconnue: {}.</translation>
</message>
<message>
<location filename="../src/models/references.py" line="232"/>
<location filename="../src/models/references.py" line="229"/>
<source>Text: &lt;b&gt;{}&lt;/b&gt;</source>
<translation>Texte: &lt;b&gt;{}&lt;/b&gt;</translation>
</message>
<message>
<location filename="../src/models/references.py" line="240"/>
<location filename="../src/models/references.py" line="237"/>
<source>Character: &lt;b&gt;{}&lt;/b&gt;</source>
<translation>Personnage: &lt;b&gt;{}&lt;/b&gt;</translation>
</message>
<message>
<location filename="../src/models/references.py" line="111"/>
<location filename="../src/models/references.py" line="136"/>
<source>Basic infos</source>
<translation>Informations générales</translation>
</message>
<message>
<location filename="../src/models/references.py" line="112"/>
<location filename="../src/models/references.py" line="137"/>
<source>Detailed infos</source>
<translation>Informations détaillées</translation>
</message>
<message>
<location filename="../src/models/references.py" line="113"/>
<location filename="../src/models/references.py" line="138"/>
<source>POV of:</source>
<translation>POV de:</translation>
</message>
<message>
<location filename="../src/models/references.py" line="114"/>
<location filename="../src/models/references.py" line="139"/>
<source>Referenced in:</source>
<translation>Référencé dans:</translation>
</message>
<message>
<location filename="../src/models/references.py" line="119"/>
<location filename="../src/models/references.py" line="144"/>
<source>Motivation</source>
<translation>Motivation</translation>
</message>
<message>
<location filename="../src/models/references.py" line="120"/>
<location filename="../src/models/references.py" line="145"/>
<source>Goal</source>
<translation>Goal</translation>
</message>
<message>
<location filename="../src/models/references.py" line="121"/>
<location filename="../src/models/references.py" line="146"/>
<source>Conflict</source>
<translation>Conflit</translation>
</message>
<message>
<location filename="../src/models/references.py" line="122"/>
<location filename="../src/models/references.py" line="147"/>
<source>Epiphany</source>
<translation>Épiphanie</translation>
</message>
<message>
<location filename="../src/models/references.py" line="123"/>
<location filename="../src/models/references.py" line="148"/>
<source>Short summary</source>
<translation>Résumé court</translation>
</message>
<message>
<location filename="../src/models/references.py" line="124"/>
<location filename="../src/models/references.py" line="149"/>
<source>Longer summary</source>
<translation>Résumé long</translation>
</message>
<message>
<location filename="../src/models/references.py" line="26"/>
<location filename="../src/models/references.py" line="51"/>
<source>Path:</source>
<translation>Chemin:</translation>
</message>
<message>
<location filename="../src/models/references.py" line="27"/>
<location filename="../src/models/references.py" line="52"/>
<source>Stats:</source>
<translation>Stats:</translation>
</message>
<message>
<location filename="../src/models/references.py" line="28"/>
<location filename="../src/models/references.py" line="53"/>
<source>POV:</source>
<translation>POV:</translation>
</message>
<message>
<location filename="../src/models/references.py" line="29"/>
<location filename="../src/models/references.py" line="54"/>
<source>Status:</source>
<translation>Status:</translation>
</message>
<message>
<location filename="../src/models/references.py" line="30"/>
<location filename="../src/models/references.py" line="55"/>
<source>Label:</source>
<translation>Label:</translation>
</message>
<message>
<location filename="../src/models/references.py" line="31"/>
<location filename="../src/models/references.py" line="56"/>
<source>Short summary:</source>
<translation>Résumé court:</translation>
</message>
<message>
<location filename="../src/models/references.py" line="32"/>
<location filename="../src/models/references.py" line="57"/>
<source>Long summary:</source>
<translation>Résumé long:</translation>
</message>
<message>
<location filename="../src/models/references.py" line="33"/>
<location filename="../src/models/references.py" line="58"/>
<source>Notes:</source>
<translation>Notes:</translation>
</message>
</context>
<context>
<name>revisions</name>
<message>
<location filename="../src/ui/revisions_ui.ui" line="14"/>
<source>Form</source>
<translation>Form</translation>
</message>
<message>
<location filename="../src/ui/revisions_ui.ui" line="93"/>
<source>Restore</source>
<translation>Restaurer</translation>
</message>
<message>
<location filename="../src/ui/revisions_ui.ui" line="103"/>
<source>Delete</source>
<translation>Supprimer</translation>
</message>
<message>
<location filename="../src/ui/revisions.py" line="62"/>
<source>1 day ago</source>
<translation>Il y a un jour</translation>
</message>
<message>
<location filename="../src/ui/revisions.py" line="64"/>
<source>{} days ago</source>
<translation>Il y a {} jours</translation>
</message>
<message>
<location filename="../src/ui/revisions.py" line="66"/>
<source>{} hours ago</source>
<translation>Il y a {} heures</translation>
</message>
<message>
<location filename="../src/ui/revisions.py" line="68"/>
<source>{} minutes ago</source>
<translation>Il y a {} minutes</translation>
</message>
<message>
<location filename="../src/ui/revisions.py" line="70"/>
<source>{} seconds ago</source>
<translation>Il y a {} secondes</translation>
</message>
</context>
<context>
<name>settingsWindow</name>
<message>
@ -1812,7 +1865,7 @@ des lignes:</translation>
<context>
<name>textEditView</name>
<message>
<location filename="../src/ui/views/textEditView.py" line="301"/>
<location filename="../src/ui/views/textEditView.py" line="313"/>
<source>Various</source>
<translation>Différentes valeurs</translation>
</message>

View file

@ -52,4 +52,5 @@ class Outline(Enum):
setGoal = 14 # The goal set by the user, if any. Can be different from goal which can be computed
# (sum of all sub-items' goals)
textFormat = 15
revisions = 16

View file

@ -8,7 +8,8 @@ from lxml import etree as ET
from functions import *
import locale
locale.setlocale(locale.LC_ALL, '')
import time
import collections
class outlineModel(QAbstractItemModel):
@ -461,6 +462,9 @@ class outlineItem():
if Outline(column) in self._data:
return self._data[Outline(column)]
elif column == Outline.revisions.value:
return []
else:
return ""
@ -523,6 +527,9 @@ class outlineItem():
elif oldType in ["txt", "t2t"] and data == "html" and Outline.text in self._data:
self._data[Outline.text] = self._data[Outline.text].replace("\n", "<br>")
elif column == Outline.text.value:
self.addRevision()
# Setting data
self._data[Outline(column)] = data
@ -706,11 +713,16 @@ class outlineItem():
return qApp.translate("outlineModel", "{} words").format(
locale.format("%d", wc, grouping=True))
###############################################################################
# XML
###############################################################################
def toXML(self):
item = ET.Element("outlineItem")
# We don't want to write some datas (computed)
exclude = [Outline.wordCount, Outline.goal, Outline.goalPercentage]
exclude = [Outline.wordCount, Outline.goal, Outline.goalPercentage, Outline.revisions]
# We want to force some data even if they're empty
force = [Outline.compile]
@ -719,6 +731,14 @@ class outlineItem():
val = self.data(attrib.value)
if val or attrib in force:
item.set(attrib.name, str(val))
# Saving revisions
rev = self.revisions()
for r in rev:
revItem = ET.Element("revision")
revItem.set("timestamp", str(r[0]))
revItem.set("text", r[1])
item.append(revItem)
for i in self.childItems:
item.append(ET.XML(i.toXML()))
@ -736,7 +756,15 @@ class outlineItem():
self.setData(Outline.__members__[k].value, str(root.attrib[k]))
for child in root:
item = outlineItem(self._model, xml=ET.tostring(child), parent=self)
if child.tag == "outlineItem":
item = outlineItem(self._model, xml=ET.tostring(child), parent=self)
elif child.tag == "revision":
self.appendRevision(child.attrib["timestamp"], child.attrib["text"])
###############################################################################
# IDS
###############################################################################
def getUniqueID(self):
self.setData(Outline.ID.value, self._model.rootItem.findUniqueID())
@ -821,4 +849,74 @@ class outlineItem():
for c in self.children():
lst.extend(c.findItemsContaining(text, columns, mainWindow, caseSensitive))
return lst
return lst
###############################################################################
# REVISIONS
###############################################################################
def revisions(self):
return self.data(Outline.revisions.value)
def appendRevision(self, ts, text):
if not Outline.revisions in self._data:
self._data[Outline.revisions] = []
self._data[Outline.revisions].append((
int(ts),
text))
def addRevision(self):
# FIXME: only add if significantly different, or enough time span
if not Outline.text in self._data:
return
self.appendRevision(
time.time(),
self._data[Outline.text])
self.cleanRevisions()
def cleanRevisions(self):
"Keep only one some the revisions."
rev = self.revisions()
rev2 = []
now = time.time()
rule = collections.OrderedDict()
rule[5 * 60] = 60 # One per minute for the last 5mn
rule[60 * 60] = 60 * 10 # One per 10mn for the last hour
rule[60 * 60 * 24] = 60 * 60 # One per hour for the last day
rule[60 * 60 * 24 * 30] = 60 * 60 * 24 # One per day for the last month
rule[None] = 60 * 60 * 24 * 7 # One per week for eternity
revs = {}
for i in rule:
revs[i] = []
for r in rev:
for span in rule:
if not span or now - r[0] < span:
revs[span].append(r)
break
for span in revs:
sortedRev = sorted(revs[span], key=lambda x:x[0])
last = None
for r in sortedRev:
if not last:
rev2.append(r)
last = r[0]
elif r[0] - last >= rule[span]:
rev2.append(r)
last = r[0]
if rev2 != rev:
self._data[Outline.revisions] = rev2
self.emitDataChanged([Outline.revisions.value])

View file

@ -266,11 +266,21 @@ def refToLink(ref):
def linkifyAllRefs(text):
return re.sub(RegEx, lambda m: refToLink(m.group(0)), text)
def basicT2TFormat(text):
text = re.sub("\*\*(.*?)\*\*", "<b>\\1</b>", text)
text = re.sub("//(.*?)//", "<i>\\1</i>", text)
text = re.sub("__(.*?)__", "<u>\\1</u>", text)
text = text.replace("\n", "<br>")
def basicT2TFormat(text, formatting=True, EOL=True, titles=True):
if formatting:
text = re.sub("\*\*(.*?)\*\*", "<b>\\1</b>", text)
text = re.sub("//(.*?)//", "<i>\\1</i>", text)
text = re.sub("__(.*?)__", "<u>\\1</u>", text)
if titles:
for i in range(1, 6):
r1 = '^\s*{s}([^=].*[^=]){s}\s*$'.format(s="=" * i)
r2 = '^\s*{s}([^\+].*[^\+]){s}\s*$'.format(s="\\+" * i)
t = "<h{n}>\\1</h{n}>".format(n=i)
text = re.sub(r1, t, text)
text = re.sub(r2, t, text)
if EOL:
text = text.replace("\n", "<br>")
return text
def open(ref):

152
src/ui/revisions.py Normal file
View file

@ -0,0 +1,152 @@
#!/usr/bin/env python
#--!-- coding: utf8 --!--
from qt import *
from enums import *
from models.outlineModel import *
from ui.revisions_ui import *
from functions import *
import models.references as Ref
import datetime
import difflib
class revisions(QWidget, Ui_revisions):
def __init__(self, parent=None):
QWidget.__init__(self, parent)
self.setupUi(self)
self.splitter.setStretchFactor(0, 5)
self.splitter.setStretchFactor(1, 70)
self.listDelegate = listCompleterDelegate(self)
self.list.setItemDelegate(self.listDelegate)
self.list.itemActivated.connect(self.showDiff)
#self.list.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self._model = None
self._index = None
def setModel(self, model):
self._model = model
self._model.dataChanged.connect(self.updateMaybe)
def setCurrentModelIndex(self, index):
self._index = index
self.view.setText("")
self.update()
def updateMaybe(self, topLeft, bottomRight):
if self._index and \
topLeft.column() <= Outline.revisions.value <= bottomRight.column() and \
topLeft.row() <= self._index.row() <= bottomRight.row():
self.update()
def update(self):
self.list.clear()
item = self._index.internalPointer()
rev = item.revisions()
# Sort revisions
rev = sorted(rev, key=lambda x:x[0], reverse=True)
for r in rev:
timestamp = datetime.datetime.fromtimestamp(r[0]).strftime('%Y-%m-%d %H:%M:%S')
readable = self.readableDelta(r[0])
i = QListWidgetItem(readable)
i.setData(Qt.UserRole, r[0])
i.setData(Qt.UserRole+1, timestamp)
self.list.addItem(i)
def readableDelta(self, timestamp):
now = datetime.datetime.now()
delta = now - datetime.datetime.fromtimestamp(timestamp)
if delta.days == 1:
return self.tr("1 day ago")
elif delta.days > 0:
return self.tr("{} days ago").format(str(delta.days))
elif delta.seconds > 60 * 60:
return self.tr("{} hours ago").format(str(int(delta.seconds / 60 / 60)))
elif delta.seconds > 60:
return self.tr("{} minutes ago").format(str(int(delta.seconds / 60)))
else:
return self.tr("{} seconds ago").format(str(delta.seconds))
def showDiff(self):
#FIXME: doesn't work for HTML formatting.
i = self.list.currentItem()
ts = i.data(Qt.UserRole)
item = self._index.internalPointer()
textNow = item.text()
textBefore = [r[1] for r in item.revisions() if r[0] == ts][0]
textNow = textNow.splitlines()
textBefore = textBefore.splitlines()
d = difflib.Differ()
diff = list(d.compare(textBefore, textNow))
extra = "" if item.type() == "html" else "<br>"
diff = [d for d in diff if d and not d[:2] == "? "]
mydiff = ""
skip = False
for n in range(len(diff)):
l = diff[n]
op = l[:2]
txt = l[2:]
op2 = diff[n+1][:2] if n+1 < len(diff) else None
txt2 = diff[n+1][2:] if n+1 < len(diff) else None
if skip:
skip = False
continue
if op == " ":
if item.type() == "t2t":
txt = Ref.basicT2TFormat(txt)
mydiff += "{}{}".format(txt, extra)
elif op == "- " and op2 == "+ ":
s = difflib.SequenceMatcher(None, txt, txt2, autojunk=False)
for tag, i1, i2, j1, j2 in s.get_opcodes():
if tag == "equal":
mydiff += txt[i1:i2]
elif tag == "delete":
mydiff += "<span style='color:red;'>{}</span>".format(txt[i1:i2].replace(" ", ""))
elif tag == "insert":
mydiff += "<span style='color:green;'>{}</span>".format(txt2[j1:j2].replace(" ", ""))
elif tag == "replace":
mydiff += "<span style='color:red;'>{}</span>".format(txt[i1:i2].replace(" ", ""))
mydiff += "<span style='color:green;'>{}</span>".format(txt2[j1:j2].replace(" ", ""))
mydiff += extra
skip = True
elif op == "- ":
mydiff += "<span style='color:red;'>{}</span>{}".format(txt, extra)
elif op == "+ ":
mydiff += "<span style='color:green;'>{}</span>{}".format(txt, extra)
self.view.setText(mydiff)
class listCompleterDelegate(QStyledItemDelegate):
def __init__(self, parent=None):
QStyledItemDelegate.__init__(self, parent)
def paint(self, painter, option, index):
extra = index.data(Qt.UserRole+1)
if not extra:
return QStyledItemDelegate.paint(self, painter, option, index)
else:
if option.state & QStyle.State_Selected:
painter.fillRect(option.rect, option.palette.color(QPalette.Inactive, QPalette.Highlight))
title = index.data()
extra = " - {}".format(extra)
painter.drawText(option.rect, Qt.AlignLeft, title)
fm = QFontMetrics(option.font)
w = fm.width(title)
r = QRect(option.rect)
r.setLeft(r.left() + w)
painter.save()
painter.setPen(Qt.gray)
painter.drawText(r, Qt.AlignLeft, extra)
painter.restore()

74
src/ui/revisions_ui.py Normal file
View file

@ -0,0 +1,74 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'src/ui/revisions_ui.ui'
#
# Created by: PyQt5 UI code generator 5.4.1
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_revisions(object):
def setupUi(self, revisions):
revisions.setObjectName("revisions")
revisions.resize(400, 344)
self.verticalLayout_2 = QtWidgets.QVBoxLayout(revisions)
self.verticalLayout_2.setContentsMargins(0, 0, 0, 0)
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.splitter = QtWidgets.QSplitter(revisions)
self.splitter.setOrientation(QtCore.Qt.Vertical)
self.splitter.setObjectName("splitter")
self.list = QtWidgets.QListWidget(self.splitter)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.list.sizePolicy().hasHeightForWidth())
self.list.setSizePolicy(sizePolicy)
self.list.setObjectName("list")
self.scrollArea = QtWidgets.QScrollArea(self.splitter)
self.scrollArea.setWidgetResizable(True)
self.scrollArea.setObjectName("scrollArea")
self.scrollAreaWidgetContents = QtWidgets.QWidget()
self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 396, 70))
self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents")
self.verticalLayout = QtWidgets.QVBoxLayout(self.scrollAreaWidgetContents)
self.verticalLayout.setObjectName("verticalLayout")
self.view = QtWidgets.QLabel(self.scrollAreaWidgetContents)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.view.sizePolicy().hasHeightForWidth())
self.view.setSizePolicy(sizePolicy)
self.view.setText("")
self.view.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop)
self.view.setWordWrap(True)
self.view.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse|QtCore.Qt.TextSelectableByMouse)
self.view.setObjectName("view")
self.verticalLayout.addWidget(self.view)
self.scrollArea.setWidget(self.scrollAreaWidgetContents)
self.layoutWidget = QtWidgets.QWidget(self.splitter)
self.layoutWidget.setObjectName("layoutWidget")
self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.layoutWidget)
self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_2.addItem(spacerItem)
self.btnRestore = QtWidgets.QPushButton(self.layoutWidget)
self.btnRestore.setEnabled(False)
self.btnRestore.setObjectName("btnRestore")
self.horizontalLayout_2.addWidget(self.btnRestore)
self.btnDelete = QtWidgets.QPushButton(self.layoutWidget)
self.btnDelete.setEnabled(False)
self.btnDelete.setObjectName("btnDelete")
self.horizontalLayout_2.addWidget(self.btnDelete)
self.verticalLayout_2.addWidget(self.splitter)
self.retranslateUi(revisions)
QtCore.QMetaObject.connectSlotsByName(revisions)
def retranslateUi(self, revisions):
_translate = QtCore.QCoreApplication.translate
revisions.setWindowTitle(_translate("revisions", "Form"))
self.btnRestore.setText(_translate("revisions", "Restore"))
self.btnDelete.setText(_translate("revisions", "Delete"))

115
src/ui/revisions_ui.ui Normal file
View file

@ -0,0 +1,115 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>revisions</class>
<widget class="QWidget" name="revisions">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>344</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<widget class="QListWidget" name="list">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
<widget class="QScrollArea" name="scrollArea">
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>396</width>
<height>70</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="view">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
<widget class="QWidget" name="layoutWidget">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="btnRestore">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Restore</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnDelete">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Delete</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View file

@ -14,12 +14,14 @@ class metadataView(QWidget, Ui_metadataView):
self.txtSummarySentance.setColumn(Outline.summarySentance.value)
self.txtSummaryFull.setColumn(Outline.summaryFull.value)
self.txtNotes.setColumn(Outline.notes.value)
self.revisions.setEnabled(False)
def setModels(self, mdlOutline, mdlPersos, mdlLabels, mdlStatus):
self.properties.setModels(mdlOutline, mdlPersos, mdlLabels, mdlStatus)
self.txtSummarySentance.setModel(mdlOutline)
self.txtSummaryFull.setModel(mdlOutline)
self.txtNotes.setModel(mdlOutline)
self.revisions.setModel(mdlOutline)
def getIndexes(self, sourceView):
"Returns a list of indexes from list of QItemSelectionRange"
@ -42,6 +44,7 @@ class metadataView(QWidget, Ui_metadataView):
if len(indexes) == 0:
self.setEnabled(False)
self.revisions.setEnabled(False)
elif len(indexes) == 1:
self.setEnabled(True)
@ -49,12 +52,15 @@ class metadataView(QWidget, Ui_metadataView):
self.txtSummarySentance.setCurrentModelIndex(idx)
self.txtSummaryFull.setCurrentModelIndex(idx)
self.txtNotes.setCurrentModelIndex(idx)
self.revisions.setEnabled(True)
self.revisions.setCurrentModelIndex(idx)
else:
self.setEnabled(True)
self.txtSummarySentance.setCurrentModelIndexes(indexes)
self.txtSummaryFull.setCurrentModelIndexes(indexes)
self.txtNotes.setCurrentModelIndexes(indexes)
self.revisions.setEnabled(False)
self.properties.selectionChanged(sourceView)
self._lastIndexes = indexes

View file

@ -58,6 +58,18 @@ class Ui_metadataView(object):
self.txtNotes.setObjectName("txtNotes")
self.horizontalLayout_29.addWidget(self.txtNotes)
self.verticalLayout.addWidget(self.groupBox_6)
self.groupBox_7 = collapsibleGroupBox2(metadataView)
self.groupBox_7.setFlat(True)
self.groupBox_7.setCheckable(True)
self.groupBox_7.setObjectName("groupBox_7")
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.groupBox_7)
self.verticalLayout_2.setContentsMargins(0, 0, 0, 0)
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.revisions = revisions(self.groupBox_7)
self.revisions.setMinimumSize(QtCore.QSize(0, 50))
self.revisions.setObjectName("revisions")
self.verticalLayout_2.addWidget(self.revisions)
self.verticalLayout.addWidget(self.groupBox_7)
self.retranslateUi(metadataView)
QtCore.QMetaObject.connectSlotsByName(metadataView)
@ -68,10 +80,12 @@ class Ui_metadataView(object):
self.groupBox_4.setTitle(_translate("metadataView", "Properties"))
self.groupBox_5.setTitle(_translate("metadataView", "Summary"))
self.txtSummarySentance.setPlaceholderText(_translate("metadataView", "One line summary"))
self.groupBox_6.setTitle(_translate("metadataView", "Notes"))
self.groupBox_6.setTitle(_translate("metadataView", "Notes / References"))
self.groupBox_7.setTitle(_translate("metadataView", "Revisions"))
from ui.collapsibleGroupBox2 import collapsibleGroupBox2
from ui.views.propertiesView import propertiesView
from ui.views.textEditCompleter import textEditCompleter
from ui.views.textEditView import textEditView
from ui.views.propertiesView import propertiesView
from ui.views.lineEditView import lineEditView
from ui.revisions import revisions
from ui.collapsibleGroupBox2 import collapsibleGroupBox2

View file

@ -85,7 +85,7 @@
<item>
<widget class="collapsibleGroupBox2" name="groupBox_6">
<property name="title">
<string>Notes</string>
<string>Notes / References</string>
</property>
<property name="flat">
<bool>true</bool>
@ -103,6 +103,34 @@
</layout>
</widget>
</item>
<item>
<widget class="collapsibleGroupBox2" name="groupBox_7">
<property name="title">
<string>Revisions</string>
</property>
<property name="flat">
<bool>true</bool>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="revisions" name="revisions" native="true">
<property name="minimumSize">
<size>
<width>0</width>
<height>50</height>
</size>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<customwidgets>
@ -133,6 +161,12 @@
<extends>QTextEdit</extends>
<header>ui.views.textEditCompleter.h</header>
</customwidget>
<customwidget>
<class>revisions</class>
<extends>QWidget</extends>
<header>ui.revisions.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>

View file

@ -314,6 +314,7 @@ class textEditView(QTextEdit):
self._updating = False
def submit(self):
self.updateTimer.stop()
if self._updating:
return
#print("Submitting", self.objectName())
@ -350,9 +351,9 @@ class textEditView(QTextEdit):
self._updating = False
def keyPressEvent(self, event):
QTextEdit.keyPressEvent(self, event)
if event.key() == Qt.Key_Space:
self.submit()
QTextEdit.keyPressEvent(self, event)
# -----------------------------------------------------------------------------------------------------
# Resize stuff