mirror of
https://github.com/olivierkes/manuskript.git
synced 2024-09-28 15:31:38 +12:00
Adds txt2tags highlighter, from an other project -- probably buggy
This commit is contained in:
parent
a7068f1d4f
commit
03a0124093
14 changed files with 1188 additions and 18 deletions
2
makefile
2
makefile
|
@ -1,4 +1,4 @@
|
||||||
UI := $(wildcard src/ui/*.ui) $(wildcard src/ui/*.qrc)
|
UI := $(wildcard src/ui/*.ui) $(wildcard src/ui/*/*.ui) $(wildcard src/ui/*.qrc)
|
||||||
UIs= $(UI:.ui=.py) $(UI:.qrc=_rc.py)
|
UIs= $(UI:.ui=.py) $(UI:.qrc=_rc.py)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -41,3 +41,4 @@ class Outline(Enum):
|
||||||
goalPercentage = 12
|
goalPercentage = 12
|
||||||
setGoal = 13 # The goal set by the user, if any. Can be different from goal which can be computed
|
setGoal = 13 # The goal set by the user, if any. Can be different from goal which can be computed
|
||||||
# (sum of all sub-items' goals)
|
# (sum of all sub-items' goals)
|
||||||
|
textFormat = 14
|
||||||
|
|
0
src/ui/editors/__init__.py
Normal file
0
src/ui/editors/__init__.py
Normal file
68
src/ui/editors/blockUserData.py
Normal file
68
src/ui/editors/blockUserData.py
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf8 -*-
|
||||||
|
|
||||||
|
from qt import *
|
||||||
|
|
||||||
|
|
||||||
|
class blockUserData (QTextBlockUserData):
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def getUserData(block):
|
||||||
|
"Returns userData if it exists, or a blank one."
|
||||||
|
data = block.userData()
|
||||||
|
if data is None:
|
||||||
|
data = blockUserData()
|
||||||
|
return data
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def getUserState(block):
|
||||||
|
"Returns the block state."
|
||||||
|
state = block.userState()
|
||||||
|
while state >= 100:
|
||||||
|
state -= 100 # +100 means in a list
|
||||||
|
return state
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
QTextBlockUserData.__init__(self)
|
||||||
|
self._listLevel = 0
|
||||||
|
self._leadingSpaces = 0
|
||||||
|
self._emptyLinesBefore = 0
|
||||||
|
self._listSymbol = ""
|
||||||
|
|
||||||
|
def isList(self):
|
||||||
|
return self._listLevel > 0
|
||||||
|
|
||||||
|
def listLevel(self):
|
||||||
|
return self._listLevel
|
||||||
|
|
||||||
|
def setListLevel(self, level):
|
||||||
|
self._listLevel = level
|
||||||
|
|
||||||
|
def listSymbol(self):
|
||||||
|
return self._listSymbol
|
||||||
|
|
||||||
|
def setListSymbol(self, s):
|
||||||
|
self._listSymbol = s
|
||||||
|
|
||||||
|
def leadingSpaces(self):
|
||||||
|
return self._leadingSpaces
|
||||||
|
|
||||||
|
def setLeadingSpaces(self, n):
|
||||||
|
self._leadingSpaces = n
|
||||||
|
|
||||||
|
def emptyLinesBefore(self):
|
||||||
|
return self._emptyLinesBefore
|
||||||
|
|
||||||
|
def setEmptyLinesBefore(self, n):
|
||||||
|
self._emptyLinesBefore = n
|
||||||
|
|
||||||
|
def text(self):
|
||||||
|
return str(self.listLevel()) + "|" + str(self.leadingSpaces()) + "|" + str(self.emptyLinesBefore())
|
||||||
|
|
||||||
|
def __eq__(self, b):
|
||||||
|
return self._listLevel == b._listLevel and \
|
||||||
|
self._leadingSpaces == b._leadingSpaces and \
|
||||||
|
self._emptyLinesBefore == b._emptyLinesBefore
|
||||||
|
|
||||||
|
def __ne__(self, b):
|
||||||
|
return not self == b
|
31
src/ui/editors/customTextEdit.py
Normal file
31
src/ui/editors/customTextEdit.py
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
#--!-- coding: utf8 --!--
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from qt import *
|
||||||
|
from enums import *
|
||||||
|
from ui.editors.t2tHighlighter import *
|
||||||
|
try:
|
||||||
|
import enchant
|
||||||
|
except ImportError:
|
||||||
|
enchant = None
|
||||||
|
|
||||||
|
class customTextEdit(QTextEdit):
|
||||||
|
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
QTextEdit.__init__(self, parent)
|
||||||
|
|
||||||
|
self.defaultFontPointSize = 9
|
||||||
|
self.highlightWord = ""
|
||||||
|
self.highligtCS = False
|
||||||
|
|
||||||
|
self.highlighter = t2tHighlighter(self)
|
||||||
|
|
||||||
|
# Spellchecking
|
||||||
|
if enchant:
|
||||||
|
self.dict = enchant.Dict("fr_CH")
|
||||||
|
self.spellcheck = True
|
||||||
|
else:
|
||||||
|
self.spellcheck = False
|
|
@ -6,7 +6,7 @@ from __future__ import unicode_literals
|
||||||
|
|
||||||
from qt import *
|
from qt import *
|
||||||
from enums import *
|
from enums import *
|
||||||
from ui.editorWidget_ui import *
|
from ui.editors.editorWidget_ui import *
|
||||||
|
|
||||||
class GrowingTextEdit(QTextEdit):
|
class GrowingTextEdit(QTextEdit):
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Form implementation generated from reading ui file 'src/ui/editorWidget_ui.ui'
|
# Form implementation generated from reading ui file 'src/ui/editors/editorWidget_ui.ui'
|
||||||
#
|
#
|
||||||
# Created by: PyQt5 UI code generator 5.4.1
|
# Created by: PyQt5 UI code generator 5.4.1
|
||||||
#
|
#
|
||||||
|
@ -22,7 +22,7 @@ class Ui_editorWidget_ui(object):
|
||||||
self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.scene)
|
self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.scene)
|
||||||
self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0)
|
self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0)
|
||||||
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
|
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
|
||||||
self.txtRedacText = QtWidgets.QPlainTextEdit(self.scene)
|
self.txtRedacText = customTextEdit(self.scene)
|
||||||
self.txtRedacText.setObjectName("txtRedacText")
|
self.txtRedacText.setObjectName("txtRedacText")
|
||||||
self.horizontalLayout_2.addWidget(self.txtRedacText)
|
self.horizontalLayout_2.addWidget(self.txtRedacText)
|
||||||
self.stack.addWidget(self.scene)
|
self.stack.addWidget(self.scene)
|
||||||
|
@ -45,10 +45,11 @@ class Ui_editorWidget_ui(object):
|
||||||
self.horizontalLayout.addWidget(self.stack)
|
self.horizontalLayout.addWidget(self.stack)
|
||||||
|
|
||||||
self.retranslateUi(editorWidget_ui)
|
self.retranslateUi(editorWidget_ui)
|
||||||
self.stack.setCurrentIndex(1)
|
self.stack.setCurrentIndex(0)
|
||||||
QtCore.QMetaObject.connectSlotsByName(editorWidget_ui)
|
QtCore.QMetaObject.connectSlotsByName(editorWidget_ui)
|
||||||
|
|
||||||
def retranslateUi(self, editorWidget_ui):
|
def retranslateUi(self, editorWidget_ui):
|
||||||
_translate = QtCore.QCoreApplication.translate
|
_translate = QtCore.QCoreApplication.translate
|
||||||
editorWidget_ui.setWindowTitle(_translate("editorWidget_ui", "Form"))
|
editorWidget_ui.setWindowTitle(_translate("editorWidget_ui", "Form"))
|
||||||
|
|
||||||
|
from ui.editors.customTextEdit import customTextEdit
|
|
@ -20,7 +20,7 @@
|
||||||
<item>
|
<item>
|
||||||
<widget class="QStackedWidget" name="stack">
|
<widget class="QStackedWidget" name="stack">
|
||||||
<property name="currentIndex">
|
<property name="currentIndex">
|
||||||
<number>1</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
<widget class="QWidget" name="scene">
|
<widget class="QWidget" name="scene">
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||||
|
@ -28,7 +28,7 @@
|
||||||
<number>0</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QPlainTextEdit" name="txtRedacText"/>
|
<widget class="customTextEdit" name="txtRedacText"/>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
|
@ -66,6 +66,13 @@
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
|
<customwidgets>
|
||||||
|
<customwidget>
|
||||||
|
<class>customTextEdit</class>
|
||||||
|
<extends>QTextEdit</extends>
|
||||||
|
<header>ui.editors.customTextEdit.h</header>
|
||||||
|
</customwidget>
|
||||||
|
</customwidgets>
|
||||||
<resources/>
|
<resources/>
|
||||||
<connections/>
|
<connections/>
|
||||||
</ui>
|
</ui>
|
264
src/ui/editors/t2tFunctions.py
Normal file
264
src/ui/editors/t2tFunctions.py
Normal file
|
@ -0,0 +1,264 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from qt import *
|
||||||
|
|
||||||
|
|
||||||
|
def textToFormatArray(text):
|
||||||
|
"""
|
||||||
|
Take some text and returns an array of array containing informations
|
||||||
|
about how the text is formatted:
|
||||||
|
r = [ [0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0],
|
||||||
|
[0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1],
|
||||||
|
...
|
||||||
|
]
|
||||||
|
Each sub-array is for one of the beautifier:
|
||||||
|
0: bold
|
||||||
|
1: italic
|
||||||
|
2: underline
|
||||||
|
3: strike
|
||||||
|
4: code
|
||||||
|
5: tagged
|
||||||
|
|
||||||
|
Each sub-array contains an element for each character of the text, with the
|
||||||
|
value 1 if it is formatted in the specific format, -1 if it is markup, and
|
||||||
|
0 otherwise.
|
||||||
|
|
||||||
|
removeMarks returns a both the array and a string, in which all of the
|
||||||
|
formatting marks have been removed.
|
||||||
|
"""
|
||||||
|
|
||||||
|
result = []
|
||||||
|
|
||||||
|
for markup in ["\*", "/", "_", "-", "`", "\'"]:
|
||||||
|
|
||||||
|
rList = []
|
||||||
|
|
||||||
|
r = QRegExp(r'(' + markup * 2 + ')(.+)(' + markup * 2 + ')')
|
||||||
|
r.setMinimal(True)
|
||||||
|
pos = r.indexIn(text, 0)
|
||||||
|
lastPos = 0
|
||||||
|
while pos >= 0:
|
||||||
|
#We have a winner
|
||||||
|
rList += [0] * (pos - lastPos)
|
||||||
|
rList += [2] * 2
|
||||||
|
rList += [1] * len(r.cap(2))
|
||||||
|
rList += [2] * 2
|
||||||
|
lastPos = pos + len(r.cap(0))
|
||||||
|
pos = r.indexIn(text, len(rList))
|
||||||
|
|
||||||
|
if len(rList) < len(text):
|
||||||
|
rList += [0] * (len(text) - len(rList))
|
||||||
|
|
||||||
|
result.append(rList)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def textToFormatArrayNoMarkup(text):
|
||||||
|
"""
|
||||||
|
Same as textToFormatArray, except that it removes all the markup from the
|
||||||
|
text and returns two elements:
|
||||||
|
the array
|
||||||
|
the text without markup
|
||||||
|
"""
|
||||||
|
|
||||||
|
r = textToFormatArray(text)
|
||||||
|
result = [[], [], [], [], [], []]
|
||||||
|
rText = ""
|
||||||
|
|
||||||
|
for i in range(len(text)):
|
||||||
|
t = max([k[i] for k in r]) # kind of flattens all the format array
|
||||||
|
if t != 2:
|
||||||
|
rText += text[i]
|
||||||
|
[result[k].append(r[k][i]) for k in range(len(r))]
|
||||||
|
|
||||||
|
return rText, result
|
||||||
|
|
||||||
|
|
||||||
|
def translateSelectionToUnformattedText(text, start, end):
|
||||||
|
"""
|
||||||
|
Translate the start / end of selection from a formatted text to an
|
||||||
|
unformatted one.
|
||||||
|
"""
|
||||||
|
r = textToFormatArray(text)
|
||||||
|
|
||||||
|
rStart, rEnd = start, end
|
||||||
|
|
||||||
|
for i in range(len(text)):
|
||||||
|
t = max([k[i] for k in r]) # kind of flattens all the format array
|
||||||
|
if t == 2: # t == 2 means this character is markup
|
||||||
|
if i <= start: rStart -= 1
|
||||||
|
if i < end: rEnd -= 1
|
||||||
|
|
||||||
|
return rStart, rEnd
|
||||||
|
|
||||||
|
|
||||||
|
def translateSelectionToFormattedText(text, start, end):
|
||||||
|
"""
|
||||||
|
Translate the start / end of selection from a formatted text to an
|
||||||
|
unformatted one.
|
||||||
|
"""
|
||||||
|
r = textToFormatArray(text)
|
||||||
|
|
||||||
|
rStart, rEnd = start, end
|
||||||
|
|
||||||
|
for i in range(len(text)):
|
||||||
|
t = max([k[i] for k in r]) # kind of flattens all the format array
|
||||||
|
if t == 2: # t == 2 means this character is markup
|
||||||
|
if i <= start: rStart -= 1
|
||||||
|
if i < end: rEnd -= 1
|
||||||
|
|
||||||
|
return rStart, rEnd
|
||||||
|
|
||||||
|
|
||||||
|
def printArray(array):
|
||||||
|
print("".join([str(j) for j in array]))
|
||||||
|
|
||||||
|
|
||||||
|
def printArrays(arrays):
|
||||||
|
for i in arrays: printArray(i)
|
||||||
|
|
||||||
|
|
||||||
|
def reformatText(text, markupArray):
|
||||||
|
"""
|
||||||
|
Takes a text without formatting markup, and an array generated by
|
||||||
|
textToFormatArray, and adds the propper markup.
|
||||||
|
"""
|
||||||
|
|
||||||
|
rText = ""
|
||||||
|
markup = ["**", "//", "__", "--", "``", "''"]
|
||||||
|
|
||||||
|
for k in range(len(markupArray)):
|
||||||
|
m = markupArray[k]
|
||||||
|
open = False # Are we in an openned markup
|
||||||
|
d = 0
|
||||||
|
alreadySeen = []
|
||||||
|
for i in range(len(text)):
|
||||||
|
insert = False
|
||||||
|
if not open and m[i] == 1:
|
||||||
|
insert = True
|
||||||
|
open = True
|
||||||
|
|
||||||
|
if open and m[i] == 0:
|
||||||
|
insert = True
|
||||||
|
open = False
|
||||||
|
if open and m[i] > 1:
|
||||||
|
z = i
|
||||||
|
while m[z] == m[i]: z += 1
|
||||||
|
if m[z] != 1 and not m[i] in alreadySeen:
|
||||||
|
insert = True
|
||||||
|
open = False
|
||||||
|
alreadySeen.append(m[i])
|
||||||
|
if insert:
|
||||||
|
rText += markup[k]
|
||||||
|
for j in range(len(markupArray)):
|
||||||
|
# The other array still have the same length
|
||||||
|
if j > k:
|
||||||
|
#Insert 2 for bold, 3 for italic, etc.
|
||||||
|
markupArray[j].insert(i + d, k + 2)
|
||||||
|
markupArray[j].insert(i + d, k + 2)
|
||||||
|
alreadySeen = []
|
||||||
|
d += 2
|
||||||
|
rText += text[i]
|
||||||
|
if open:
|
||||||
|
rText += markup[k]
|
||||||
|
for j in range(len(markupArray)):
|
||||||
|
# The other array still have the same length
|
||||||
|
if j > k:
|
||||||
|
#Insert 2 for bold, 3 for italic, etc.
|
||||||
|
markupArray[j].insert(i + d, k + 2)
|
||||||
|
markupArray[j].insert(i + d, k + 2)
|
||||||
|
text = rText
|
||||||
|
rText = ""
|
||||||
|
|
||||||
|
## Clean up
|
||||||
|
# Exclude first and last space of the markup
|
||||||
|
for markup in ["\*", "/", "_", "-", "`", "\'"]:
|
||||||
|
r = QRegExp(r'(' + markup * 2 + ')(\s+)(.+)(' + markup * 2 + ')')
|
||||||
|
r.setMinimal(True)
|
||||||
|
text.replace(r, "\\2\\1\\3\\4")
|
||||||
|
r = QRegExp(r'(' + markup * 2 + ')(.+)(\s+)(' + markup * 2 + ')')
|
||||||
|
r.setMinimal(True)
|
||||||
|
text.replace(r, "\\1\\2\\4\\3")
|
||||||
|
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
def cleanFormat(text):
|
||||||
|
"Makes markup clean (removes doubles, etc.)"
|
||||||
|
t, a = textToFormatArrayNoMarkup(text)
|
||||||
|
return reformatText(t, a)
|
||||||
|
|
||||||
|
|
||||||
|
class State:
|
||||||
|
NORMAL = 0
|
||||||
|
TITLE_1 = 1
|
||||||
|
TITLE_2 = 2
|
||||||
|
TITLE_3 = 3
|
||||||
|
TITLE_4 = 4
|
||||||
|
TITLE_5 = 5
|
||||||
|
NUMBERED_TITLE_1 = 6
|
||||||
|
NUMBERED_TITLE_2 = 7
|
||||||
|
NUMBERED_TITLE_3 = 8
|
||||||
|
NUMBERED_TITLE_4 = 9
|
||||||
|
NUMBERED_TITLE_5 = 10
|
||||||
|
TITLES = [TITLE_1, TITLE_2, TITLE_3, TITLE_4, TITLE_5, NUMBERED_TITLE_1,
|
||||||
|
NUMBERED_TITLE_2, NUMBERED_TITLE_3, NUMBERED_TITLE_4,
|
||||||
|
NUMBERED_TITLE_5]
|
||||||
|
# AREA
|
||||||
|
COMMENT_AREA = 11
|
||||||
|
CODE_AREA = 12
|
||||||
|
RAW_AREA = 13
|
||||||
|
TAGGED_AREA = 14
|
||||||
|
# AREA MARKUP
|
||||||
|
COMMENT_AREA_BEGINS = 15
|
||||||
|
COMMENT_AREA_ENDS = 16
|
||||||
|
CODE_AREA_BEGINS = 17
|
||||||
|
CODE_AREA_ENDS = 18
|
||||||
|
RAW_AREA_BEGINS = 19
|
||||||
|
RAW_AREA_ENDS = 20
|
||||||
|
TAGGED_AREA_BEGINS = 21
|
||||||
|
TAGGED_AREA_ENDS = 22
|
||||||
|
#LINE
|
||||||
|
COMMENT_LINE = 30
|
||||||
|
CODE_LINE = 31
|
||||||
|
RAW_LINE = 32
|
||||||
|
TAGGED_LINE = 33
|
||||||
|
SETTINGS_LINE = 34
|
||||||
|
BLOCKQUOTE_LINE = 35
|
||||||
|
HORIZONTAL_LINE = 36
|
||||||
|
HEADER_LINE = 37
|
||||||
|
# LIST
|
||||||
|
LIST_BEGINS = 40
|
||||||
|
LIST_ENDS = 41
|
||||||
|
LIST_EMPTY = 42
|
||||||
|
LIST_BULLET = 43
|
||||||
|
LIST_BULLET_ENDS = 44
|
||||||
|
LIST = [40, 41, 42] + range(100, 201)
|
||||||
|
# TABLE
|
||||||
|
TABLE_LINE = 50
|
||||||
|
TABLE_HEADER = 51
|
||||||
|
#OTHER
|
||||||
|
MARKUP = 60
|
||||||
|
LINKS = 61
|
||||||
|
MACRO = 62
|
||||||
|
DEFAULT = 63
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def titleLevel(state):
|
||||||
|
"""
|
||||||
|
Returns the level of the title, from the block state.
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
State.TITLE_1: 1,
|
||||||
|
State.TITLE_2: 2,
|
||||||
|
State.TITLE_3: 3,
|
||||||
|
State.TITLE_4: 4,
|
||||||
|
State.TITLE_5: 5,
|
||||||
|
State.NUMBERED_TITLE_1: 1,
|
||||||
|
State.NUMBERED_TITLE_2: 2,
|
||||||
|
State.NUMBERED_TITLE_3: 3,
|
||||||
|
State.NUMBERED_TITLE_4: 4,
|
||||||
|
State.NUMBERED_TITLE_5: 5,
|
||||||
|
}.get(state, -1)
|
553
src/ui/editors/t2tHighlighter.py
Normal file
553
src/ui/editors/t2tHighlighter.py
Normal file
|
@ -0,0 +1,553 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf8 -*-
|
||||||
|
|
||||||
|
from qt import *
|
||||||
|
from ui.editors.t2tFunctions import *
|
||||||
|
from ui.editors.blockUserData import blockUserData
|
||||||
|
from ui.editors.t2tHighlighterStyle import t2tHighlighterStyle
|
||||||
|
import re
|
||||||
|
|
||||||
|
# This is aiming at implementing every rule from www.txt2tags.org/rules.html
|
||||||
|
# But we're not there yet.
|
||||||
|
|
||||||
|
#FIXME: macro words not hilighted properly if at the begining of a line.
|
||||||
|
|
||||||
|
#TODO: parse %!postproc et !%preproc, et si la ligne se termine par une couleur en commentaire (%#FF00FF), utiliser cette couleur pour highlighter. Permet des règles customisées par document, facilement.
|
||||||
|
|
||||||
|
|
||||||
|
class t2tHighlighter (QSyntaxHighlighter):
|
||||||
|
"""Syntax highlighter for the Txt2Tags language.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, editor, style="Default"):
|
||||||
|
QSyntaxHighlighter.__init__(self, editor.document())
|
||||||
|
|
||||||
|
self.editor = editor
|
||||||
|
|
||||||
|
# Stupid variable that fixes the loss of QTextBlockUserData.
|
||||||
|
self.thisDocument = editor.document()
|
||||||
|
self.style = t2tHighlighterStyle(self.editor, style)
|
||||||
|
|
||||||
|
self.inDocRules = []
|
||||||
|
|
||||||
|
rules = [
|
||||||
|
(r'^\s*[-=_]{20,}\s*$', State.HORIZONTAL_LINE),
|
||||||
|
(r'^\s*(\+{1})([^\+].*[^\+])(\+{1})(\[[A-Za-z0-9_-]*\])?\s*$', State.NUMBERED_TITLE_1),
|
||||||
|
(r'^\s*(\+{2})([^\+].*[^\+])(\+{2})(\[[A-Za-z0-9_-]*\])?\s*$', State.NUMBERED_TITLE_2),
|
||||||
|
(r'^\s*(\+{3})([^\+].*[^\+])(\+{3})(\[[A-Za-z0-9_-]*\])?\s*$', State.NUMBERED_TITLE_3),
|
||||||
|
(r'^\s*(\+{4})([^\+].*[^\+])(\+{4})(\[[A-Za-z0-9_-]*\])?\s*$', State.NUMBERED_TITLE_4),
|
||||||
|
(r'^\s*(\+{5})([^\+].*[^\+])(\+{5})(\[[A-Za-z0-9_-]*\])?\s*$', State.NUMBERED_TITLE_5),
|
||||||
|
(r'^\s*(={1})([^=].*[^=])(={1})(\[[A-Za-z0-9_-]*\])?\s*$', State.TITLE_1),
|
||||||
|
(r'^\s*(={2})([^=].*[^=])(={2})(\[[A-Za-z0-9_-]*\])?\s*$', State.TITLE_2),
|
||||||
|
(r'^\s*(={3})([^=].*[^=])(={3})(\[[A-Za-z0-9_-]*\])?\s*$', State.TITLE_3),
|
||||||
|
(r'^\s*(={4})([^=].*[^=])(={4})(\[[A-Za-z0-9_-]*\])?\s*$', State.TITLE_4),
|
||||||
|
(r'^\s*(={5})([^=].*[^=])(={5})(\[[A-Za-z0-9_-]*\])?\s*$', State.TITLE_5),
|
||||||
|
(r'^%!.*$', State.SETTINGS_LINE),
|
||||||
|
(r'^%[^!]?.*$', State.COMMENT_LINE),
|
||||||
|
(r'^\t.+$', State.BLOCKQUOTE_LINE),
|
||||||
|
(r'^(```)(.+)$', State.CODE_LINE),
|
||||||
|
(r'^(""")(.+)$', State.RAW_LINE),
|
||||||
|
(r'^(\'\'\')(.+)$', State.TAGGED_LINE),
|
||||||
|
(r'^\s*[-+:] [^ ].*$', State.LIST_BEGINS),
|
||||||
|
(r'^\s*[-+:]\s*$', State.LIST_ENDS),
|
||||||
|
(r'^ *\|\| .*$', State.TABLE_HEADER),
|
||||||
|
(r'^ *\| .*$', State.TABLE_LINE)
|
||||||
|
]
|
||||||
|
|
||||||
|
# Generate rules to identify blocks
|
||||||
|
State.Rules = [(QRegExp(pattern), state)
|
||||||
|
for (pattern, state) in rules]
|
||||||
|
State.Recursion = 0
|
||||||
|
|
||||||
|
def highlightBlock(self, text):
|
||||||
|
"""Apply syntax highlighting to the given block of text.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Check if syntax highlighting is enabled
|
||||||
|
if self.style is None:
|
||||||
|
default = QTextBlockFormat()
|
||||||
|
QTextCursor(self.currentBlock()).setBlockFormat(default)
|
||||||
|
return
|
||||||
|
|
||||||
|
block = self.currentBlock()
|
||||||
|
oldState = blockUserData.getUserState(block)
|
||||||
|
self.identifyBlock(block)
|
||||||
|
# formatBlock prevent undo/redo from working
|
||||||
|
# TODO: find a todo/undo compatible way of formatting block
|
||||||
|
#self.formatBlock(block)
|
||||||
|
|
||||||
|
state = blockUserData.getUserState(block)
|
||||||
|
data = blockUserData.getUserData(block)
|
||||||
|
inList = self.isList(block)
|
||||||
|
|
||||||
|
op = self.style.format(State.MARKUP)
|
||||||
|
|
||||||
|
self.setFormat(0, len(text), self.style.format(State.DEFAULT))
|
||||||
|
|
||||||
|
# InDocRules: is it a settings which might have a specific rule,
|
||||||
|
# a comment which contains color infos, or a include conf?
|
||||||
|
# r'^%!p[or][se]t?proc[^\s]*\s*:\s*\'(.*)\'\s*\'.*\''
|
||||||
|
rlist = [QRegExp(r'^%!p[or][se]t?proc[^\s]*\s*:\s*((\'[^\']*\'|\"[^\"]*\")\s*(\'[^\']*\'|\"[^\"]*\"))'), # pre/postproc
|
||||||
|
QRegExp(r'^%.*\s\((.*)\)'), # comment
|
||||||
|
QRegExp(r'^%!includeconf:\s*([^\s]*)\s*')] # includeconf
|
||||||
|
for r in rlist:
|
||||||
|
if r.indexIn(text) != -1:
|
||||||
|
self.parseInDocRules()
|
||||||
|
|
||||||
|
# Format the whole line:
|
||||||
|
for lineState in [
|
||||||
|
State.BLOCKQUOTE_LINE,
|
||||||
|
State.HORIZONTAL_LINE,
|
||||||
|
State.HEADER_LINE,
|
||||||
|
]:
|
||||||
|
if not inList and state == lineState:
|
||||||
|
self.setFormat(0, len(text), self.style.format(lineState))
|
||||||
|
|
||||||
|
for (lineState, marker) in [
|
||||||
|
(State.COMMENT_LINE, "%"),
|
||||||
|
(State.CODE_LINE, "```"),
|
||||||
|
(State.RAW_LINE, "\"\"\""),
|
||||||
|
(State.TAGGED_LINE, "'''"),
|
||||||
|
(State.SETTINGS_LINE, "%!")
|
||||||
|
]:
|
||||||
|
if state == lineState and \
|
||||||
|
not (inList and state == State.SETTINGS_LINE):
|
||||||
|
n = 0
|
||||||
|
# If it's a comment, we want to highlight all '%'.
|
||||||
|
if state == State.COMMENT_LINE:
|
||||||
|
while text[n:n + 1] == "%":
|
||||||
|
n += 1
|
||||||
|
n -= 1
|
||||||
|
|
||||||
|
# Apply Format
|
||||||
|
self.setFormat(0, len(marker) + n, op)
|
||||||
|
self.setFormat(len(marker) + n,
|
||||||
|
len(text) - len(marker) - n,
|
||||||
|
self.style.format(lineState))
|
||||||
|
|
||||||
|
# If it's a setting, we might do something
|
||||||
|
if state == State.SETTINGS_LINE:
|
||||||
|
# Target
|
||||||
|
r = QRegExp(r'^%!([^\s]+)\s*:\s*(\b\w*\b)$')
|
||||||
|
if r.indexIn(text) != -1:
|
||||||
|
setting = r.cap(1)
|
||||||
|
val = r.cap(2)
|
||||||
|
if setting == "target" and \
|
||||||
|
val in self.editor.main.targetsNames:
|
||||||
|
self.editor.fileWidget.preview.setPreferredTarget(val)
|
||||||
|
|
||||||
|
# Pre/postproc
|
||||||
|
r = QRegExp(r'^%!p[or][se]t?proc[^\s]*\s*:\s*((\'[^\']*\'|\"[^\"]*\")\s*(\'[^\']*\'|\"[^\"]*\"))')
|
||||||
|
if r.indexIn(text) != -1:
|
||||||
|
p = r.pos(1)
|
||||||
|
length = len(r.cap(1))
|
||||||
|
self.setFormat(p, length, self.style.makeFormat(base=self.format(p),
|
||||||
|
fixedPitch=True))
|
||||||
|
|
||||||
|
# Tables
|
||||||
|
for lineState in [State.TABLE_LINE, State.TABLE_HEADER]:
|
||||||
|
if state == lineState:
|
||||||
|
for i in range(len(text)):
|
||||||
|
if text[i] == "|":
|
||||||
|
self.setFormat(i, 1, op)
|
||||||
|
else:
|
||||||
|
self.setFormat(i, 1, self.style.format(lineState))
|
||||||
|
|
||||||
|
# Lists
|
||||||
|
#if text == " p": print(data.isList())
|
||||||
|
if data.isList():
|
||||||
|
r = QRegExp(r'^\s*[\+\-\:]? ?')
|
||||||
|
r.indexIn(text)
|
||||||
|
self.setFormat(0, r.matchedLength(), self.style.format(State.LIST_BULLET))
|
||||||
|
#if state == State.LIST_BEGINS:
|
||||||
|
#r = QRegExp(r'^\s*[+-:] ')
|
||||||
|
#r.indexIn(text)
|
||||||
|
#self.setFormat(0, r.matchedLength(), self.style.format(State.LIST_BULLET))
|
||||||
|
|
||||||
|
if state == State.LIST_ENDS:
|
||||||
|
self.setFormat(0, len(text), self.style.format(State.LIST_BULLET_ENDS))
|
||||||
|
|
||||||
|
# Titles
|
||||||
|
if not inList and state in State.TITLES:
|
||||||
|
r = [i for (i, s) in State.Rules if s == state][0]
|
||||||
|
pos = r.indexIn(text)
|
||||||
|
if pos >= 0:
|
||||||
|
f = self.style.format(state)
|
||||||
|
# Uncomment for markup to be same size as title
|
||||||
|
#op = self.formats(preset="markup",
|
||||||
|
#base=self.formats(preset=state))
|
||||||
|
self.setFormat(r.pos(2), r.cap(2).length(), f)
|
||||||
|
self.setFormat(r.pos(1), r.cap(1).length(), op)
|
||||||
|
self.setFormat(r.pos(3), r.cap(3).length(), op)
|
||||||
|
|
||||||
|
# Areas: comment, code, raw tagged
|
||||||
|
for (begins, middle, ends) in [
|
||||||
|
(State.COMMENT_AREA_BEGINS, State.COMMENT_AREA, State.COMMENT_AREA_ENDS),
|
||||||
|
(State.CODE_AREA_BEGINS, State.CODE_AREA, State.CODE_AREA_ENDS),
|
||||||
|
(State.RAW_AREA_BEGINS, State.RAW_AREA, State.RAW_AREA_ENDS),
|
||||||
|
(State.TAGGED_AREA_BEGINS, State.TAGGED_AREA, State.TAGGED_AREA_ENDS),
|
||||||
|
]:
|
||||||
|
|
||||||
|
if state == middle:
|
||||||
|
self.setFormat(0, len(text), self.style.format(middle))
|
||||||
|
elif state in [begins, ends]:
|
||||||
|
self.setFormat(0, len(text), op)
|
||||||
|
|
||||||
|
# Inline formatting
|
||||||
|
if state not in [
|
||||||
|
#State.COMMENT_AREA,
|
||||||
|
#State.COMMENT_LINE,
|
||||||
|
State.RAW_AREA,
|
||||||
|
State.RAW_LINE,
|
||||||
|
State.CODE_AREA,
|
||||||
|
State.CODE_LINE,
|
||||||
|
State.TAGGED_AREA,
|
||||||
|
State.TAGGED_LINE,
|
||||||
|
State.SETTINGS_LINE,
|
||||||
|
State.HORIZONTAL_LINE,
|
||||||
|
] and state not in State.TITLES:
|
||||||
|
formatArray = textToFormatArray(text)
|
||||||
|
|
||||||
|
# InDocRules
|
||||||
|
for (r, c) in self.inDocRules:
|
||||||
|
i = re.finditer(r.decode('utf8'), text, re.UNICODE)
|
||||||
|
for m in i:
|
||||||
|
f = self.format(m.start())
|
||||||
|
l = m.end() - m.start()
|
||||||
|
if "," in c:
|
||||||
|
c1, c2 = c.split(",")
|
||||||
|
self.setFormat(m.start(), l,
|
||||||
|
self.style.makeFormat(color=c1, bgcolor=c2, base=f))
|
||||||
|
else:
|
||||||
|
self.setFormat(m.start(), l,
|
||||||
|
self.style.makeFormat(color=c, base=f))
|
||||||
|
|
||||||
|
# Links
|
||||||
|
if state not in [State.COMMENT_LINE, State.COMMENT_AREA]:
|
||||||
|
r = QRegExp(r'\[(\[[^\]]*\])?[^\]]*\s*([^\s]+)\]')
|
||||||
|
r.setMinimal(False)
|
||||||
|
pos = r.indexIn(text)
|
||||||
|
links = []
|
||||||
|
while pos >= 0:
|
||||||
|
#TODO: The text should not be formatted if [**not bold**]
|
||||||
|
#if max([k[pos] for k in formatArray]) == 0 or 1 == 1:
|
||||||
|
self.setFormat(pos, 1,
|
||||||
|
self.style.format(State.MARKUP))
|
||||||
|
self.setFormat(pos + 1, r.cap(0).length() - 1,
|
||||||
|
self.style.format(State.LINKS))
|
||||||
|
self.setFormat(pos + r.cap(0).length() - 1, 1,
|
||||||
|
self.style.format(State.MARKUP))
|
||||||
|
if r.pos(2) > 0:
|
||||||
|
_f = QTextCharFormat(self.style.format(State.LINKS))
|
||||||
|
_f.setForeground(QBrush(_f.foreground()
|
||||||
|
.color().lighter()))
|
||||||
|
_f.setFontUnderline(True)
|
||||||
|
self.setFormat(r.pos(2), r.cap(2).length(), _f)
|
||||||
|
|
||||||
|
links.append([pos, r.cap(0).length()]) # To remember for the next highlighter (single links)
|
||||||
|
pos = r.indexIn(text, pos + 1)
|
||||||
|
|
||||||
|
# Links like www.theologeek.ch, http://www.fsf.org, ...
|
||||||
|
# FIXME: - "http://adresse et http://adresse" is detected also as italic
|
||||||
|
# - some error, like "http://adress.htm." also color the final "."
|
||||||
|
# - also: adresse@email.com, ftp://, www2, www3, etc.
|
||||||
|
# - But for now, does the job
|
||||||
|
r = QRegExp(r'http://[^\s]*|www\.[a-zA-Z0-9-_]+\.[a-zA-Z0-9-_]+[^\s]*')
|
||||||
|
#r.setMinimal(True)
|
||||||
|
pos = r.indexIn(text)
|
||||||
|
while pos >= 0:
|
||||||
|
for k in links:
|
||||||
|
#print pos, k[0], k[1]
|
||||||
|
if pos > k[0] and pos < k[0] + k[1]: # already highlighted
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
self.setFormat(pos, r.cap(0).length(), self.style.format(State.LINKS))
|
||||||
|
|
||||||
|
pos = r.indexIn(text, pos + 1)
|
||||||
|
|
||||||
|
# Bold, Italic, Underline, Code, Tagged, Strikeout
|
||||||
|
for i in range(len(text)):
|
||||||
|
f = self.format(i)
|
||||||
|
beautifiers = [k[i] for k in formatArray]
|
||||||
|
self.setFormat(i, 1, self.style.beautifyFormat(f, beautifiers))
|
||||||
|
|
||||||
|
# Macro words
|
||||||
|
for r in [r'(%%)\b\w+\b', r'(%%)\b\w+\b\(.+\)']:
|
||||||
|
r = QRegExp(r)
|
||||||
|
r.setMinimal(True)
|
||||||
|
pos = r.indexIn(text)
|
||||||
|
while pos >= 0:
|
||||||
|
if max([k[pos] for k in formatArray]) == 0:
|
||||||
|
self.setFormat(pos, r.cap(0).length(),
|
||||||
|
self.style.format(State.MACRO))
|
||||||
|
pos = r.indexIn(text, pos + 1)
|
||||||
|
|
||||||
|
# Highlighted word (for search)
|
||||||
|
if self.editor.highlightWord:
|
||||||
|
if self.editor.highligtCS and self.editor.highlightWord in text or \
|
||||||
|
not self.editor.highlightCs and self.editor.highlightWord.lower() in text.lower():
|
||||||
|
#if self.editor.highlightCS:
|
||||||
|
#s = self.editor.highlightWord
|
||||||
|
#else:
|
||||||
|
#s = self.editor.highlightWord.toLower()
|
||||||
|
#print(s)
|
||||||
|
p = text.indexOf(self.editor.highlightWord, cs=self.editor.highlightCS)
|
||||||
|
while p >= 0:
|
||||||
|
self.setFormat(p, len(self.editor.highlightWord),
|
||||||
|
self.style.makeFormat(preset="higlighted", base=self.format(p)))
|
||||||
|
p = text.indexOf(self.editor.highlightWord, p + 1, cs=self.editor.highlightCS)
|
||||||
|
|
||||||
|
|
||||||
|
### Highlight Selection
|
||||||
|
### TODO: way to slow, find another way.
|
||||||
|
##sel = self.editor.textCursor().selectedText()
|
||||||
|
##if len(sel) > 5: self.keywordRules.append((QRegExp(sel), "selected"))
|
||||||
|
|
||||||
|
## Do keyword formatting
|
||||||
|
#for expression, style in self.keywordRules:
|
||||||
|
#expression.setMinimal( True )
|
||||||
|
#index = expression.indexIn(text, 0)
|
||||||
|
|
||||||
|
## There might be more than one on the same line
|
||||||
|
#while index >= 0:
|
||||||
|
#length = expression.cap(0).length()
|
||||||
|
#f = self.formats(preset=style, base=self.formats(index))
|
||||||
|
#self.setFormat(index, length, f)
|
||||||
|
#index = expression.indexIn(text, index + length)
|
||||||
|
|
||||||
|
# Spell checking
|
||||||
|
# Based on http://john.nachtimwald.com/2009/08/22/qplaintextedit-with-in-line-spell-check/
|
||||||
|
WORDS = u'(?iu)[\w\']+'
|
||||||
|
if state not in [State.SETTINGS_LINE]:
|
||||||
|
if self.editor.spellcheck:
|
||||||
|
for word_object in re.finditer(WORDS, text):
|
||||||
|
if not self.editor.dict.check(word_object.group()):
|
||||||
|
format = self.format(word_object.start())
|
||||||
|
format.setUnderlineColor(Qt.red)
|
||||||
|
format.setUnderlineStyle(QTextCharFormat.SpellCheckUnderline)
|
||||||
|
self.setFormat(word_object.start(),
|
||||||
|
word_object.end() - word_object.start(), format)
|
||||||
|
|
||||||
|
# If a title was changed, we emit the corresponding signal
|
||||||
|
if oldState in State.TITLES or \
|
||||||
|
self.currentBlockState() in State.TITLES:
|
||||||
|
self.editor.structureChanged.emit()
|
||||||
|
#FIXME: si du texte est supprimé et qu'il y a un titre dedans,
|
||||||
|
# cela n'est pas détecté et le signal pas émis.
|
||||||
|
|
||||||
|
def identifyBlock(self, block):
|
||||||
|
"""Identifies what block type it is, and set userState and userData
|
||||||
|
accordingly."""
|
||||||
|
|
||||||
|
text = block.text()
|
||||||
|
data = blockUserData.getUserData(block)
|
||||||
|
|
||||||
|
# Header Lines
|
||||||
|
if block.blockNumber() == 0:
|
||||||
|
block.setUserState(State.HEADER_LINE)
|
||||||
|
return
|
||||||
|
elif block.blockNumber() in [1, 2] and \
|
||||||
|
self.document().findBlockByNumber(0).text():
|
||||||
|
block.setUserState(State.HEADER_LINE)
|
||||||
|
return
|
||||||
|
|
||||||
|
state = 0
|
||||||
|
inList = False
|
||||||
|
blankLinesBefore = 0
|
||||||
|
|
||||||
|
#if text.contains(QRegExp(r'^\s*[-+:] [^ ].*[^-+]{1}\s*$')):
|
||||||
|
if QRegExp(r'^\s*[-+:] [^ ].*[^-+]{1}\s*$').indexIn(text) <> -1:
|
||||||
|
state = State.LIST_BEGINS
|
||||||
|
|
||||||
|
# List stuff
|
||||||
|
if self.isList(block.previous()) or state == State.LIST_BEGINS:
|
||||||
|
inList = True
|
||||||
|
|
||||||
|
# listLevel and leadingSpaces
|
||||||
|
#FIXME: not behaving exactly correctly...
|
||||||
|
lastData = blockUserData.getUserData(block.previous())
|
||||||
|
if state == State.LIST_BEGINS:
|
||||||
|
leadingSpaces = QRegExp(r'[-+:]').indexIn(text, 0)
|
||||||
|
data.setLeadingSpaces(leadingSpaces)
|
||||||
|
|
||||||
|
data.setListSymbol(text[leadingSpaces])
|
||||||
|
if self.isList(block.previous()):
|
||||||
|
# The last block was also a list.
|
||||||
|
# We need to check if this is the same level, or a sublist
|
||||||
|
if leadingSpaces > lastData.leadingSpaces():
|
||||||
|
# This is a sublevel list
|
||||||
|
data.setListLevel(lastData.listLevel() + 1)
|
||||||
|
else:
|
||||||
|
# This is same level
|
||||||
|
data.setListLevel(lastData.listLevel())
|
||||||
|
else:
|
||||||
|
data.setListLevel(1)
|
||||||
|
else:
|
||||||
|
data.setListLevel(lastData.listLevel())
|
||||||
|
data.setLeadingSpaces(lastData.leadingSpaces())
|
||||||
|
data.setListSymbol(lastData.listSymbol())
|
||||||
|
|
||||||
|
# Blank lines before (two = end of list)
|
||||||
|
blankLinesBefore = self.getBlankLines(block.previous())
|
||||||
|
if not QRegExp(r'^\s*$').indexIn(block.previous().text()) <> -1 and \
|
||||||
|
not blockUserData.getUserState(block.previous()) in [State.COMMENT_LINE,
|
||||||
|
State.COMMENT_AREA, State.COMMENT_AREA_BEGINS,
|
||||||
|
State.COMMENT_AREA_ENDS]:
|
||||||
|
blankLinesBefore = 0
|
||||||
|
elif not blockUserData.getUserState(block.previous()) in \
|
||||||
|
[State.COMMENT_LINE, State.COMMENT_AREA,
|
||||||
|
State.COMMENT_AREA_BEGINS, State.COMMENT_AREA_ENDS]:
|
||||||
|
blankLinesBefore += 1
|
||||||
|
if blankLinesBefore == 2:
|
||||||
|
# End of list.
|
||||||
|
blankLinesBefore = 0
|
||||||
|
inList = False
|
||||||
|
if inList and QRegExp(r'^\s*$').indexIn(text) <> -1:
|
||||||
|
state = State.LIST_EMPTY
|
||||||
|
|
||||||
|
# Areas
|
||||||
|
for (begins, middle, ends, marker) in [
|
||||||
|
(State.COMMENT_AREA_BEGINS, State.COMMENT_AREA, State.COMMENT_AREA_ENDS, "^%%%\s*$"),
|
||||||
|
(State.CODE_AREA_BEGINS, State.CODE_AREA, State.CODE_AREA_ENDS, "^```\s*$"),
|
||||||
|
(State.RAW_AREA_BEGINS, State.RAW_AREA, State.RAW_AREA_ENDS, "^\"\"\"\s*$"),
|
||||||
|
(State.TAGGED_AREA_BEGINS, State.TAGGED_AREA, State.TAGGED_AREA_ENDS, '^\'\'\'\s*$'),
|
||||||
|
]:
|
||||||
|
|
||||||
|
if QRegExp(marker).indexIn(text) <> -1:
|
||||||
|
if blockUserData.getUserState(block.previous()) in [begins, middle]:
|
||||||
|
state = ends
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
state = begins
|
||||||
|
break
|
||||||
|
if blockUserData.getUserState(block.previous()) in [middle, begins]:
|
||||||
|
state = middle
|
||||||
|
break
|
||||||
|
|
||||||
|
# Patterns (for lines)
|
||||||
|
if not state:
|
||||||
|
for (pattern, lineState) in State.Rules:
|
||||||
|
pos = pattern.indexIn(text)
|
||||||
|
if pos >= 0:
|
||||||
|
state = lineState
|
||||||
|
break
|
||||||
|
|
||||||
|
if state in [State.BLOCKQUOTE_LINE, State.LIST_ENDS]:
|
||||||
|
#FIXME: doesn't work exactly. Closes only the current level, not
|
||||||
|
#FIXME: the whole list.
|
||||||
|
inList = False
|
||||||
|
|
||||||
|
if inList and not state == State.LIST_BEGINS:
|
||||||
|
state += 100
|
||||||
|
if blankLinesBefore:
|
||||||
|
state += 100
|
||||||
|
|
||||||
|
block.setUserState(state)
|
||||||
|
block.setUserData(data)
|
||||||
|
|
||||||
|
def formatBlock(self, block):
|
||||||
|
"""
|
||||||
|
Formats the block according to its state.
|
||||||
|
"""
|
||||||
|
#TODO: Use QTextDocument format presets, and QTextBlock's
|
||||||
|
#TODO: blockFormatIndex. And move that in t2tHighlighterStyle.
|
||||||
|
state = block.userState()
|
||||||
|
blockFormat = QTextBlockFormat()
|
||||||
|
|
||||||
|
if state in [State.BLOCKQUOTE_LINE,
|
||||||
|
State.HEADER_LINE] + State.LIST:
|
||||||
|
blockFormat = self.style.formatBlock(block, state)
|
||||||
|
|
||||||
|
QTextCursor(block).setBlockFormat(blockFormat)
|
||||||
|
|
||||||
|
def getBlankLines(self, block):
|
||||||
|
"Returns if there is a blank line before in the list."
|
||||||
|
state = block.userState()
|
||||||
|
if state >= 200:
|
||||||
|
return 1
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def isList(self, block):
|
||||||
|
"Returns TRUE if the block is in a list."
|
||||||
|
if block.userState() == State.LIST_BEGINS or\
|
||||||
|
block.userState() >= 100:
|
||||||
|
return True
|
||||||
|
|
||||||
|
def setStyle(self, style):
|
||||||
|
if style in t2tHighlighterStyle.validStyles:
|
||||||
|
self.style = t2tHighlighterStyle(self.editor, style)
|
||||||
|
else:
|
||||||
|
self.style = None
|
||||||
|
self.rehighlight()
|
||||||
|
|
||||||
|
def setFontPointSize(self, size):
|
||||||
|
self.defaultFontPointSize = size
|
||||||
|
self.style = t2tHighlighterStyle(self.editor, self.style.name)
|
||||||
|
self.rehighlight()
|
||||||
|
|
||||||
|
def parseInDocRules(self):
|
||||||
|
oldRules = self.inDocRules
|
||||||
|
self.inDocRules = []
|
||||||
|
|
||||||
|
t = self.thisDocument.toPlainText()
|
||||||
|
|
||||||
|
# Get all conf files
|
||||||
|
confs = []
|
||||||
|
lines = t.split("\n")
|
||||||
|
for l in lines:
|
||||||
|
r = QRegExp(r'^%!includeconf:\s*([^\s]*)\s*')
|
||||||
|
if r.indexIn(l) != -1:
|
||||||
|
confs.append(r.cap(1))
|
||||||
|
|
||||||
|
# Try to load conf files
|
||||||
|
for c in confs:
|
||||||
|
try:
|
||||||
|
import codecs
|
||||||
|
f = self.editor.fileWidget.file
|
||||||
|
d = QDir.cleanPath(QFileInfo(f).absoluteDir().absolutePath()+"/"+c)
|
||||||
|
file = codecs.open(d, 'r', "utf-8")
|
||||||
|
except:
|
||||||
|
print("Error: cannot open {}.".format(c))
|
||||||
|
continue
|
||||||
|
# We add the content to the current lines of the current document
|
||||||
|
lines += file.readlines() #lines.extend(file.readlines())
|
||||||
|
|
||||||
|
#b = self.thisDocument.firstBlock()
|
||||||
|
lastColor = ""
|
||||||
|
|
||||||
|
#while b.isValid():
|
||||||
|
for l in lines:
|
||||||
|
text = l #b.text()
|
||||||
|
r = QRegExp(ur'^%!p[or][se]t?proc[^\s]*\s*:\s*(\'[^\']*\'|\"[^\"]*\")\s*(\'[^\']*\'|\"[^\"]*\")')
|
||||||
|
if r.indexIn(text) != -1:
|
||||||
|
rule = r.cap(1)[1:-1]
|
||||||
|
# Check if there was a color-comment above that post/preproc bloc
|
||||||
|
if lastColor:
|
||||||
|
self.inDocRules.append((str(rule), lastColor))
|
||||||
|
# Check if previous block is a comment like it should
|
||||||
|
else:
|
||||||
|
previousText = lines[lines.indexOf(l)-1] #b.previous().text()
|
||||||
|
r = QRegExp(r'^%.*\s\((.*)\)')
|
||||||
|
if r.indexIn(previousText) != -1:
|
||||||
|
lastColor = r.cap(1)
|
||||||
|
self.inDocRules.append((str(rule), lastColor))
|
||||||
|
else:
|
||||||
|
lastColor = ""
|
||||||
|
#b = b.next()
|
||||||
|
|
||||||
|
if oldRules != self.inDocRules:
|
||||||
|
#Rules have changed, we need to rehighlight
|
||||||
|
#print("Rules have changed.", len(self.inDocRules))
|
||||||
|
#self.rehighlight() # Doesn't work (seg fault), why?
|
||||||
|
pass
|
||||||
|
#b = self.thisDocument.firstBlock()
|
||||||
|
#while b.isValid():
|
||||||
|
#for (r, c) in self.inDocRules:
|
||||||
|
#r = QRegExp(r)
|
||||||
|
#pos = r.indexIn(b.text())
|
||||||
|
#if pos >= 0:
|
||||||
|
#print("rehighlighting:", b.text())
|
||||||
|
#self.rehighlightBlock(b)
|
||||||
|
#break
|
||||||
|
#b = b.next()
|
245
src/ui/editors/t2tHighlighterStyle.py
Normal file
245
src/ui/editors/t2tHighlighterStyle.py
Normal file
|
@ -0,0 +1,245 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf8 -*-
|
||||||
|
|
||||||
|
from qt import *
|
||||||
|
from ui.editors.t2tFunctions import *
|
||||||
|
from ui.editors.blockUserData import blockUserData
|
||||||
|
|
||||||
|
#TODO: creates a general way to generate styles (and edit/import/export)
|
||||||
|
|
||||||
|
|
||||||
|
class t2tHighlighterStyle ():
|
||||||
|
"""Style for the Syntax highlighter for the Txt2Tags language.
|
||||||
|
"""
|
||||||
|
|
||||||
|
validStyles = ["Default", "Monospace"]
|
||||||
|
|
||||||
|
def __init__(self, editor, name="Default"):
|
||||||
|
|
||||||
|
self.editor = editor
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
# Defaults
|
||||||
|
self.defaultFontPointSize = self.editor.defaultFontPointSize
|
||||||
|
self.defaultFontFamily = ""
|
||||||
|
self.tabStopWidth = 40
|
||||||
|
|
||||||
|
self.setupEditor()
|
||||||
|
|
||||||
|
if self.name == "Default":
|
||||||
|
self.initDefaults()
|
||||||
|
#Temporary other theme
|
||||||
|
elif self.name == "Monospace":
|
||||||
|
self.defaultFontFamily = "Monospace"
|
||||||
|
self.initDefaults()
|
||||||
|
for i in self.styles:
|
||||||
|
f = self.styles[i]
|
||||||
|
f.setFontFixedPitch(True)
|
||||||
|
f.setFontFamily(self.defaultFontFamily)
|
||||||
|
f.setFontPointSize(self.defaultFontPointSize)
|
||||||
|
self.styles[i] = f
|
||||||
|
|
||||||
|
def setupEditor(self):
|
||||||
|
self.editor.setTabStopWidth(self.tabStopWidth)
|
||||||
|
|
||||||
|
def initDefaults(self):
|
||||||
|
self.styles = {}
|
||||||
|
for i in [State.CODE_AREA,
|
||||||
|
State.CODE_LINE,
|
||||||
|
State.COMMENT_AREA,
|
||||||
|
State.COMMENT_LINE,
|
||||||
|
State.SETTINGS_LINE,
|
||||||
|
State.BLOCKQUOTE_LINE,
|
||||||
|
State.RAW_AREA,
|
||||||
|
State.RAW_LINE,
|
||||||
|
State.TAGGED_AREA,
|
||||||
|
State.TAGGED_LINE,
|
||||||
|
State.TITLE_1,
|
||||||
|
State.TITLE_2,
|
||||||
|
State.TITLE_3,
|
||||||
|
State.TITLE_4,
|
||||||
|
State.TITLE_5,
|
||||||
|
State.NUMBERED_TITLE_1,
|
||||||
|
State.NUMBERED_TITLE_2,
|
||||||
|
State.NUMBERED_TITLE_3,
|
||||||
|
State.NUMBERED_TITLE_4,
|
||||||
|
State.NUMBERED_TITLE_5,
|
||||||
|
State.TABLE_HEADER,
|
||||||
|
State.TABLE_LINE,
|
||||||
|
State.HORIZONTAL_LINE,
|
||||||
|
State.MARKUP,
|
||||||
|
State.LIST_BULLET,
|
||||||
|
State.LIST_BULLET_ENDS,
|
||||||
|
State.LINKS,
|
||||||
|
State.MACRO,
|
||||||
|
State.DEFAULT,
|
||||||
|
State.HEADER_LINE]:
|
||||||
|
self.styles[i] = self.makeFormat(preset=i)
|
||||||
|
|
||||||
|
def format(self, state):
|
||||||
|
return self.styles[state]
|
||||||
|
|
||||||
|
def beautifyFormat(self, base, beautifiers):
|
||||||
|
"Apply beautifiers given in beautifiers array to format"
|
||||||
|
if max(beautifiers) == 2:
|
||||||
|
return self.makeFormat(preset=State.MARKUP, base=base)
|
||||||
|
else:
|
||||||
|
if beautifiers[0]: # bold
|
||||||
|
base.setFontWeight(QFont.Bold)
|
||||||
|
if beautifiers[1]: # italic
|
||||||
|
base.setFontItalic(True)
|
||||||
|
if beautifiers[2]: # underline
|
||||||
|
base.setFontUnderline(True)
|
||||||
|
if beautifiers[3]: # strikeout
|
||||||
|
base.setFontStrikeOut(True)
|
||||||
|
if beautifiers[4]: # code
|
||||||
|
base = self.makeFormat(base=base, preset=State.CODE_LINE)
|
||||||
|
if beautifiers[5]: # tagged
|
||||||
|
base = self.makeFormat(base=base, preset=State.TAGGED_LINE)
|
||||||
|
return base
|
||||||
|
|
||||||
|
def formatBlock(self, block, state):
|
||||||
|
"Apply transformation to given block."
|
||||||
|
blockFormat = QTextBlockFormat()
|
||||||
|
|
||||||
|
if state == State.BLOCKQUOTE_LINE:
|
||||||
|
# Number of tabs
|
||||||
|
n = block.text().indexOf(QRegExp(r'[^\t]'), 0)
|
||||||
|
blockFormat.setIndent(0)
|
||||||
|
blockFormat.setTextIndent(-self.tabStopWidth * n)
|
||||||
|
blockFormat.setLeftMargin(self.tabStopWidth * n)
|
||||||
|
#blockFormat.setRightMargin(self.editor.contentsRect().width()
|
||||||
|
# - self.editor.lineNumberAreaWidth()
|
||||||
|
# - fm.width("X") * self.editor.LimitLine
|
||||||
|
#+ self.editor.tabStopWidth())
|
||||||
|
blockFormat.setAlignment(Qt.AlignJustify)
|
||||||
|
if self.name == "Default" :
|
||||||
|
blockFormat.setTopMargin(5)
|
||||||
|
blockFormat.setBottomMargin(5)
|
||||||
|
elif state == State.HEADER_LINE:
|
||||||
|
blockFormat.setBackground(QColor("#EEEEEE"))
|
||||||
|
elif state in State.LIST:
|
||||||
|
data = blockUserData.getUserData(block)
|
||||||
|
if str(data.listSymbol()) in "+-":
|
||||||
|
blockFormat.setBackground(QColor("#EEFFEE"))
|
||||||
|
else:
|
||||||
|
blockFormat.setBackground(QColor("#EEEEFA"))
|
||||||
|
n = blockUserData.getUserData(block).leadingSpaces() + 1
|
||||||
|
f = QFontMetrics(QFont(self.defaultFontFamily,
|
||||||
|
self.defaultFontPointSize))
|
||||||
|
fm = f.width(" " * n +
|
||||||
|
blockUserData.getUserData(block).listSymbol())
|
||||||
|
blockFormat.setTextIndent(-fm)
|
||||||
|
blockFormat.setLeftMargin(fm)
|
||||||
|
if blockUserData.getUserState(block) == State.LIST_BEGINS and\
|
||||||
|
self.name == "Default":
|
||||||
|
blockFormat.setTopMargin(5)
|
||||||
|
return blockFormat
|
||||||
|
|
||||||
|
def makeFormat(self, color='', style='', size='', base='', fixedPitch='',
|
||||||
|
preset='', title_level='', bgcolor=''):
|
||||||
|
"""
|
||||||
|
Returns a QTextCharFormat with the given attributes, using presets.
|
||||||
|
"""
|
||||||
|
|
||||||
|
_color = QColor()
|
||||||
|
_format = QTextCharFormat()
|
||||||
|
size = self.defaultFontPointSize
|
||||||
|
_format.setFontFamily(self.defaultFontFamily)
|
||||||
|
|
||||||
|
# Base
|
||||||
|
if base: _format = base
|
||||||
|
|
||||||
|
# Presets
|
||||||
|
if preset in [State.CODE_AREA, State.CODE_LINE, "code"]:
|
||||||
|
style = "bold"
|
||||||
|
color = "black"
|
||||||
|
fixedPitch = True
|
||||||
|
_format.setBackground(QColor("#EEEEEE"))
|
||||||
|
|
||||||
|
if preset in [State.COMMENT_AREA, State.COMMENT_LINE, "comment"]:
|
||||||
|
style = "italic"
|
||||||
|
color = "darkGreen"
|
||||||
|
|
||||||
|
if preset in [State.SETTINGS_LINE, "setting", State.MACRO]:
|
||||||
|
#style = "italic"
|
||||||
|
color = "magenta"
|
||||||
|
|
||||||
|
if preset in [State.BLOCKQUOTE_LINE]:
|
||||||
|
color = "red"
|
||||||
|
|
||||||
|
if preset in [State.HEADER_LINE]:
|
||||||
|
size = size * 2
|
||||||
|
print size
|
||||||
|
|
||||||
|
if preset in [State.RAW_AREA, State.RAW_LINE, "raw"]:
|
||||||
|
color = "blue"
|
||||||
|
|
||||||
|
if preset in [State.TAGGED_AREA, State.TAGGED_LINE, "tagged"]:
|
||||||
|
color = "purple"
|
||||||
|
|
||||||
|
if preset in State.TITLES:
|
||||||
|
style = "bold"
|
||||||
|
color = "darkRed" if State.titleLevel(preset) % 2 == 1 else "blue"
|
||||||
|
size = (self.defaultFontPointSize
|
||||||
|
+ 11 - 2 * State.titleLevel(preset))
|
||||||
|
|
||||||
|
if preset == State.TABLE_HEADER:
|
||||||
|
style = "bold"
|
||||||
|
color = "darkMagenta"
|
||||||
|
|
||||||
|
if preset == State.TABLE_LINE:
|
||||||
|
color = "darkMagenta"
|
||||||
|
|
||||||
|
if preset == State.LIST_BULLET:
|
||||||
|
color = "red"
|
||||||
|
style = "bold"
|
||||||
|
fixedPitch = True
|
||||||
|
|
||||||
|
if preset == State.LIST_BULLET_ENDS:
|
||||||
|
color = "darkGray"
|
||||||
|
fixedPitch = True
|
||||||
|
|
||||||
|
if preset in [State.MARKUP, "markup"]:
|
||||||
|
color = "darkGray"
|
||||||
|
|
||||||
|
if preset in [State.HORIZONTAL_LINE]:
|
||||||
|
color = "cyan"
|
||||||
|
fixedPitch = True
|
||||||
|
|
||||||
|
if preset == State.LINKS:
|
||||||
|
color="blue"
|
||||||
|
#style="underline"
|
||||||
|
|
||||||
|
if preset == "selected":
|
||||||
|
_format.setBackground(QColor("yellow"))
|
||||||
|
|
||||||
|
if preset == "higlighted":
|
||||||
|
bgcolor = "yellow"
|
||||||
|
|
||||||
|
if preset == State.DEFAULT:
|
||||||
|
size = self.defaultFontPointSize
|
||||||
|
_format.setFontFamily(self.defaultFontFamily)
|
||||||
|
|
||||||
|
# Manual formatting
|
||||||
|
if color:
|
||||||
|
_color.setNamedColor(color)
|
||||||
|
_format.setForeground(_color)
|
||||||
|
if bgcolor:
|
||||||
|
_color.setNamedColor(bgcolor)
|
||||||
|
_format.setBackground(_color)
|
||||||
|
|
||||||
|
if 'bold' in style:
|
||||||
|
_format.setFontWeight(QFont.Bold)
|
||||||
|
if 'italic' in style:
|
||||||
|
_format.setFontItalic(True)
|
||||||
|
if 'strike' in style:
|
||||||
|
_format.setFontStrikeOut(True)
|
||||||
|
if 'underline' in style:
|
||||||
|
_format.setFontUnderline(True)
|
||||||
|
if size:
|
||||||
|
_format.setFontPointSize(size)
|
||||||
|
if fixedPitch:
|
||||||
|
_format.setFontFixedPitch(True)
|
||||||
|
|
||||||
|
return _format
|
|
@ -1103,7 +1103,7 @@ class Ui_MainWindow(object):
|
||||||
self.menubar.addAction(self.menu_Aide.menuAction())
|
self.menubar.addAction(self.menu_Aide.menuAction())
|
||||||
|
|
||||||
self.retranslateUi(MainWindow)
|
self.retranslateUi(MainWindow)
|
||||||
self.tabMain.setCurrentIndex(5)
|
self.tabMain.setCurrentIndex(6)
|
||||||
self.tabSummary.setCurrentIndex(0)
|
self.tabSummary.setCurrentIndex(0)
|
||||||
self.tabPersos.setCurrentIndex(0)
|
self.tabPersos.setCurrentIndex(0)
|
||||||
self.tabPlot.setCurrentIndex(0)
|
self.tabPlot.setCurrentIndex(0)
|
||||||
|
@ -1273,9 +1273,9 @@ class Ui_MainWindow(object):
|
||||||
self.actShowHelp.setText(_translate("MainWindow", "Afficher les &bulles d\'aide"))
|
self.actShowHelp.setText(_translate("MainWindow", "Afficher les &bulles d\'aide"))
|
||||||
self.actShowHelp.setShortcut(_translate("MainWindow", "Ctrl+Shift+B"))
|
self.actShowHelp.setShortcut(_translate("MainWindow", "Ctrl+Shift+B"))
|
||||||
|
|
||||||
from ui.collapsibleGroupBox2 import collapsibleGroupBox2
|
|
||||||
from ui.cmbOutlinePersoChoser import cmbOutlinePersoChoser
|
|
||||||
from ui.sldImportance import sldImportance
|
|
||||||
from ui.cmbOutlineStatusChoser import cmbOutlineStatusChoser
|
from ui.cmbOutlineStatusChoser import cmbOutlineStatusChoser
|
||||||
|
from ui.cmbOutlinePersoChoser import cmbOutlinePersoChoser
|
||||||
|
from ui.collapsibleGroupBox2 import collapsibleGroupBox2
|
||||||
from ui.chkOutlineCompile import chkOutlineCompile
|
from ui.chkOutlineCompile import chkOutlineCompile
|
||||||
from ui.editorWidget import editorWidget
|
from ui.sldImportance import sldImportance
|
||||||
|
from ui.editors.editorWidget import editorWidget
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
<item>
|
<item>
|
||||||
<widget class="QTabWidget" name="tabMain">
|
<widget class="QTabWidget" name="tabMain">
|
||||||
<property name="currentIndex">
|
<property name="currentIndex">
|
||||||
<number>5</number>
|
<number>6</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="documentMode">
|
<property name="documentMode">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
|
@ -2185,7 +2185,7 @@
|
||||||
<customwidget>
|
<customwidget>
|
||||||
<class>editorWidget</class>
|
<class>editorWidget</class>
|
||||||
<extends>QWidget</extends>
|
<extends>QWidget</extends>
|
||||||
<header>ui.editorWidget.h</header>
|
<header>ui.editors.editorWidget.h</header>
|
||||||
<container>1</container>
|
<container>1</container>
|
||||||
</customwidget>
|
</customwidget>
|
||||||
<customwidget>
|
<customwidget>
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<?xml version='1.0' encoding='UTF-8'?>
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
<outlineItem title="root" type="folder" compile="2" wordCount="355" setGoal="112">
|
<outlineItem title="root" type="folder" compile="2" wordCount="389" setGoal="112">
|
||||||
<outlineItem title="Nouveau" type="folder" compile="2" wordCount="15">
|
<outlineItem title="Nouveau" type="folder" compile="2" wordCount="15">
|
||||||
<outlineItem title="Nouveau" type="scene" compile="2" text="return QTextEdit.resizeEvent(self, e) ad ad ad ad adaasd ad adsdasd ad e drset" wordCount="15"/>
|
<outlineItem title="Nouveau" type="scene" compile="2" text="return QTextEdit.resizeEvent(self, e) ad ad ad ad adaasd ad adsdasd ad e drset" wordCount="15"/>
|
||||||
</outlineItem>
|
</outlineItem>
|
||||||
<outlineItem title="Parent" type="folder" status="TODO" compile="2" wordCount="123">
|
<outlineItem title="Parent" type="folder" status="TODO" compile="2" wordCount="127">
|
||||||
<outlineItem title="Nouveau" type="folder" summarySentance="asd asd asd " status="First draft" compile="2" wordCount="99">
|
<outlineItem title="Nouveau" type="folder" summarySentance="asd asd asd " status="First draft" compile="2" wordCount="99">
|
||||||
<outlineItem title="A" type="scene" compile="2" text="Du texteDu texteDu text ad ad ad ad " wordCount="8" setGoal="10"/>
|
<outlineItem title="A" type="scene" compile="2" text="Du texteDu texteDu text ad ad ad ad " wordCount="8" setGoal="10"/>
|
||||||
<outlineItem title="B" type="scene" compile="2" setGoal="3"/>
|
<outlineItem title="B" type="scene" compile="2" setGoal="3"/>
|
||||||
|
@ -14,12 +14,12 @@
|
||||||
</outlineItem>
|
</outlineItem>
|
||||||
</outlineItem>
|
</outlineItem>
|
||||||
<outlineItem title="MOIMOIMOI" type="scene" POV="1" compile="2" text="ASDASd ASD ASDASd ASD ASDASd ASD " wordCount="6" setGoal="10"/>
|
<outlineItem title="MOIMOIMOI" type="scene" POV="1" compile="2" text="ASDASd ASD ASDASd ASD ASDASd ASD " wordCount="6" setGoal="10"/>
|
||||||
<outlineItem title="Nouveau" type="scene" POV="1" compile="2" text="ASDASd ASD ASDASd ASD " wordCount="4" setGoal="10"/>
|
<outlineItem title="Nouveau" type="scene" POV="1" compile="2" text="ASDASd ASD ASDASd ASD asd sss ad ad " wordCount="8" setGoal="10"/>
|
||||||
<outlineItem title="Nouveau" type="scene" compile="2" text="ASDASd ASD ASDASd ASD " wordCount="4" setGoal="10"/>
|
<outlineItem title="Nouveau" type="scene" compile="2" text="ASDASd ASD ASDASd ASD " wordCount="4" setGoal="10"/>
|
||||||
<outlineItem title="B" type="scene" compile="2" text="asd asd asd asd asd asd asd asd asd " wordCount="10" setGoal="10"/>
|
<outlineItem title="B" type="scene" compile="2" text="asd asd asd asd asd asd asd asd asd " wordCount="10" setGoal="10"/>
|
||||||
<outlineItem title="Nouveau" type="folder" compile="2"/>
|
<outlineItem title="Nouveau" type="folder" compile="2"/>
|
||||||
</outlineItem>
|
</outlineItem>
|
||||||
<outlineItem title="MOIMOIMOI" type="scene" status="Second draft" compile="2" setGoal="2500"/>
|
<outlineItem title="MOIMOIMOI" type="scene" status="Second draft" compile="2" text="<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Oxygen-Sans'; font-size:10pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; -qt-user-state:37;">Text **gras**</p></body></html>" wordCount="30" setGoal="2500"/>
|
||||||
<outlineItem title="A" type="scene" compile="2" setGoal="146"/>
|
<outlineItem title="A" type="scene" compile="2" setGoal="146"/>
|
||||||
<outlineItem title="Nouveau A" type="folder" compile="2" wordCount="217">
|
<outlineItem title="Nouveau A" type="folder" compile="2" wordCount="217">
|
||||||
<outlineItem title="Nouveau" type="scene" compile="2" text="ASDASd ASD ASDASd ASD " wordCount="4"/>
|
<outlineItem title="Nouveau" type="scene" compile="2" text="ASDASd ASD ASDASd ASD " wordCount="4"/>
|
||||||
|
|
Loading…
Reference in a new issue