Hello community,

here is the log from the commit of package treeline for openSUSE:Factory 
checked in at 2020-06-02 14:40:11
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/treeline (Old)
 and      /work/SRC/openSUSE:Factory/.treeline.new.3606 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "treeline"

Tue Jun  2 14:40:11 2020 rev:25 rq:810596 version:3.1.2

Changes:
--------
--- /work/SRC/openSUSE:Factory/treeline/treeline.changes        2019-09-23 
12:39:06.341593296 +0200
+++ /work/SRC/openSUSE:Factory/.treeline.new.3606/treeline.changes      
2020-06-02 14:41:14.532015104 +0200
@@ -1,0 +2,49 @@
+Sun May 24 10:50:29 UTC 2020 - Gottfried Necker <[email protected]>
+
+- Update to 3.1.2
+  * Packaging:
+    - Remove pushd and popd from spec file.
+
+  * New Features:
+    - Add a simplified Chinese GUI translation
+      (thanks to Qu Ray for translating).
+    - Add a general option to extend the height of data editors with 
+      long text content. The default setting (limit the height to
+      the window size) is unchanged. The new option uses the view
+      scroll bars to access the full text length.
+
+  * Updates:
+    - Restore the cursor and scroll positions of data editors when
+      the editors are re-created after focus changes.
+    - Add an asterisk after the file name in the title bar if
+      a file has been modified.
+    - Change lettered outline numbering sequences to match standards.
+      The sequences change from ...Y, Z, AA, AB, AC... to ...Y, Z, AA, BB, 
CC...
+
+  * Bug Fixes:
+    - Fix a bug that truncated plain text exports after the first line.
+    - Enable the title list view's select in tree context menu to be used
+      on new child nodes.
+    - Modify dark mode colors to make tool tips visible.
+    - Fix error due to character encoding when importing files from Treepad.
+    - Fix an error caused by attempting to print an empty branch.
+
+- changelog for 3.1.1
+  * Updates:
+    - Added many Show Configuration Structure data fields to show detailed
+      settings for type formats and field formats.
+    - Added support for finding and replacing empty data fields using the
+      search and replace command.
+    - Updated German and Spanish GUI translations (thanks to Maria Seliger
+      and Diego).
+
+  * Bug Fixes:
+    - Fix printing problems when using the dark theme.
+    - Fix incorrect numbering updates in some situations with mixed node types.
+    - Fix problems defining a math field equation on a recently copied data 
type.
+    - Eliminate a problem defining math field equations that include
+      child count references.
+    - Fix a minor bug affecting default directories for save-as and export
+      commands when there is not already a file name set.
+
+-------------------------------------------------------------------

Old:
----
  treeline-3.1.1.tar.gz

New:
----
  treeline-3.1.2.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ treeline.spec ++++++
--- /var/tmp/diff_new_pack.cTEO0f/_old  2020-06-02 14:41:15.756018974 +0200
+++ /var/tmp/diff_new_pack.cTEO0f/_new  2020-06-02 14:41:15.756018974 +0200
@@ -17,7 +17,7 @@
 
 
 Name:           treeline
-Version:        3.1.1
+Version:        3.1.2
 Release:        0
 Summary:        Versatile Tree-Style Outliner for Defining Custom Data Schemas
 License:        GPL-2.0-or-later
@@ -76,9 +76,7 @@
    -d "%{_docdir}/%{name}" \
    -b %{buildroot}
 
-pushd %{buildroot}%{python_sitearch}
 python3 -c "import compileall; 
compileall.compile_dir('%{buildroot}%{_libexecdir}/treeline',2,ddir='%{_libexecdir}/treeline')"
-popd
 
 install -d "%{buildroot}%{_datadir}/mimelnk/application"
 install -m0644 \

++++++ treeline-3.1.1.tar.gz -> treeline-3.1.2.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/TreeLine/doc/documentation.trln 
new/TreeLine/doc/documentation.trln
--- old/TreeLine/doc/documentation.trln 2019-03-16 20:38:10.000000000 +0100
+++ new/TreeLine/doc/documentation.trln 2019-10-05 20:00:58.000000000 +0200
@@ -159,6 +159,62 @@
 {
 "children": [],
 "data": {
+"Name": "Restore data editor cursor &amp; scroll positions",
+"Text": "Restore the cursor and scroll positions of data editors when the 
editors are re-created after focus changes."
+},
+"format": "BULLETS",
+"uid": "194b052fe5d811e9a0d1a44cc8e97404"
+},
+{
+"children": [
+"3e749de3e5d811e98227a44cc8e97404",
+"194b0537e5d811e989a1a44cc8e97404",
+"194b0534e5d811e985f6a44cc8e97404"
+],
+"data": {
+"Name": "October 6, 2019 - Release 3.1.2 (stable release)"
+},
+"format": "HEADINGS",
+"uid": "194b0533e5d811e9b5fea44cc8e97404"
+},
+{
+"children": [
+"8cdb2406e5df11e9a06da44cc8e97404",
+"8e8e418ae5dc11e98404a44cc8e97404",
+"9139627ee5db11e98a21a44cc8e97404",
+"194b0536e5d811e991b3a44cc8e97404",
+"fcc1b718e5df11e9a7f9a44cc8e97404"
+],
+"data": {
+"Name": "Bug Fixes"
+},
+"format": "BULLET_HEADING",
+"uid": "194b0534e5d811e985f6a44cc8e97404"
+},
+{
+"children": [],
+"data": {
+"Name": "Treepad import",
+"Text": "Fix error due to character encoding when importing files from 
Treepad."
+},
+"format": "BULLETS",
+"uid": "194b0536e5d811e991b3a44cc8e97404"
+},
+{
+"children": [
+"194b052fe5d811e9a0d1a44cc8e97404",
+"ea0c9b24e79911e98abe7054d2175f18",
+"4212597ee5e011e99b51a44cc8e97404"
+],
+"data": {
+"Name": "Updates"
+},
+"format": "BULLET_HEADING",
+"uid": "194b0537e5d811e989a1a44cc8e97404"
+},
+{
+"children": [],
+"data": {
 "Name": "Enable evaluate HTML tags option",
 "Text": "The Evaluate HTML tags control was enabled for date, time, boolean 
and math fields.  This allows HTML tags to be evaluated or ignored in more 
field types."
 },
@@ -294,6 +350,26 @@
 {
 "children": [],
 "data": {
+"Name": "Chinese translation",
+"Text": "Add a simplified Chinese GUI translation (thanks to Qu Ray for 
translating)."
+},
+"format": "BULLETS",
+"uid": "3e749de1e5d811e9a22ba44cc8e97404"
+},
+{
+"children": [
+"3e749de1e5d811e9a22ba44cc8e97404",
+"cef1d9fee5de11e998dea44cc8e97404"
+],
+"data": {
+"Name": "New Features"
+},
+"format": "BULLET_HEADING",
+"uid": "3e749de3e5d811e98227a44cc8e97404"
+},
+{
+"children": [],
+"data": {
 "Name": "Time field AM/PM",
 "Text": "Now forces references to AM/PM in time field formats to be output 
regardless of system locale settings."
 },
@@ -303,6 +379,15 @@
 {
 "children": [],
 "data": {
+"Name": "Outline numbering",
+"Text": "Change lettered outline numbering sequences to match standards. The 
sequences change from ...Y, Z, AA, AB, AC... to ...Y, Z, AA, BB, CC..."
+},
+"format": "BULLETS",
+"uid": "4212597ee5e011e99b51a44cc8e97404"
+},
+{
+"children": [],
+"data": {
 "Name": "Title list select in tree command",
 "Text": "A Select in Tree command was added to the Title List's right-click 
context menu. It selects the node corresponding to the current line in the 
Title List editor. This makes it easier to edit or add children to an item."
 },
@@ -862,6 +947,24 @@
 {
 "children": [],
 "data": {
+"Name": "Truncated text export",
+"Text": "Fix a bug that truncated plain text exports after the first line."
+},
+"format": "BULLETS",
+"uid": "8cdb2406e5df11e9a06da44cc8e97404"
+},
+{
+"children": [],
+"data": {
+"Name": "Title list select in tree",
+"Text": "Enable the title list view's select in tree context menu to be used 
on new child nodes."
+},
+"format": "BULLETS",
+"uid": "8e8e418ae5dc11e98404a44cc8e97404"
+},
+{
+"children": [],
+"data": {
 "Name": "Copy command errors",
 "Text": "Fix errors shown when using copy commands after closing TreeLine 
windows."
 },
@@ -871,6 +974,15 @@
 {
 "children": [],
 "data": {
+"Name": "Dark mode tooltips",
+"Text": "Modify dark mode colors to make tool tips visible."
+},
+"format": "BULLETS",
+"uid": "9139627ee5db11e98a21a44cc8e97404"
+},
+{
+"children": [],
+"data": {
 "Name": "Purge old field data",
 "Text": "Data from fields that were removed from the configuration is now 
purged when a file is saved. This avoids missing matches in the Clone All 
Matched Nodes command."
 },
@@ -1167,6 +1279,15 @@
 {
 "children": [],
 "data": {
+"Name": "Unlimited data editor height option",
+"Text": "Add a general option to extend the height of data editors with long 
text content. The default setting (limit the height to the window size) is 
unchanged. The new option uses the view scroll bars to access the full text 
length."
+},
+"format": "BULLETS",
+"uid": "cef1d9fee5de11e998dea44cc8e97404"
+},
+{
+"children": [],
+"data": {
 "Name": "TreeLine 2 import / export",
 "Text": "Import and export filters are provided for older TreeLine version 2.x 
and 1.x files. The older files can be opened using the standard \"File &gt; 
Open\" command."
 },
@@ -1353,6 +1474,15 @@
 {
 "children": [],
 "data": {
+"Name": "Add modified flag",
+"Text": "Add an asterisk after the file name in the title bar if a file has 
been modified."
+},
+"format": "BULLETS",
+"uid": "ea0c9b24e79911e98abe7054d2175f18"
+},
+{
+"children": [],
+"data": {
 "Name": "Category command disable",
 "Text": "Avoid problems running data category-based commands on selections 
without any child nodes."
 },
@@ -1411,7 +1541,7 @@
 "children": [],
 "data": {
 "Name": "Version",
-"Text": "This document covers TreeLine, Version 3.1.1, a stable release, dated 
March 17, 2019 by Doug Bell."
+"Text": "This document covers TreeLine, Version 3.1.2, a stable release, dated 
October 6, 2019 by Doug Bell."
 },
 "format": "PARAGRAPH",
 "uid": "f16f82aea25a11e7b7c67054d2175f18"
@@ -1913,7 +2043,7 @@
 "children": [],
 "data": {
 "Name": "Languages",
-"Text": "The user interface is available in English, German and Spanish. 
Translations into other languages are TBD."
+"Text": "The user interface is available in simplified Chinese, English, 
German and Spanish. Translations into other languages are TBD."
 },
 "format": "BULLETS",
 "uid": "f16fbd00a25a11e7b7c67054d2175f18"
@@ -3063,6 +3193,7 @@
 },
 {
 "children": [
+"194b0533e5d811e9b5fea44cc8e97404",
 "db25cd62481e11e989f27054d2175f18",
 "800a0971305111e991cda44cc8e97404",
 "9c08f39ee80311e8a510a44cc8e97404",
@@ -5482,6 +5613,15 @@
 "uid": "f966db0cc23f11e8b80fd66a6ab671cb"
 },
 {
+"children": [],
+"data": {
+"Name": "Printing empty branches",
+"Text": "Fix an error caused by attempting to print an empty branch."
+},
+"format": "BULLETS",
+"uid": "fcc1b718e5df11e9a7f9a44cc8e97404"
+},
+{
 "children": [
 "6a6067d4482011e989f27054d2175f18",
 "b398d8ee482211e989f27054d2175f18",
@@ -5504,7 +5644,7 @@
 }
 ],
 "properties": {
-"tlversion": "3.1.1",
+"tlversion": "3.1.2",
 "topnodes": [
 "f16f7d90a25a11e7b7c67054d2175f18"
 ]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/TreeLine/source/dataeditors.py 
new/TreeLine/source/dataeditors.py
--- old/TreeLine/source/dataeditors.py  2019-02-21 03:20:44.000000000 +0100
+++ new/TreeLine/source/dataeditors.py  2019-10-05 19:16:30.000000000 +0200
@@ -46,6 +46,8 @@
     """
     dragLinkEnabled = False
     contentsChanged = pyqtSignal(QWidget)
+    editEnding = pyqtSignal(QWidget)
+    keyPressed = pyqtSignal(QWidget)
     def __init__(self, parent=None):
         """Initialize the editor class.
 
@@ -83,6 +85,24 @@
         """
         return self.textCursor().hasSelection()
 
+    def cursorPosTuple(self):
+        """Return a tuple of the current cursor position and anchor (integers).
+        """
+        cursor = self.textCursor()
+        return (cursor.anchor(), cursor.position())
+
+    def setCursorPos(self, anchor, position):
+        """Set the cursor to the given anchor and position.
+        Arguments:
+            anchor -- the cursor selection start integer
+            position -- the cursor position or select end integer
+        """
+        cursor = self.textCursor()
+        cursor.setPosition(anchor)
+        cursor.setPosition(position, QTextCursor.KeepAnchor)
+        self.setTextCursor(cursor)
+        # self.ensureCursorVisible()
+
     def setCursorPoint(self, point):
         """Set the cursor to the given point.
 
@@ -96,6 +116,19 @@
         """
         self.moveCursor(QTextCursor.End)
 
+    def scrollPosition(self):
+        """Return the current scrollbar position.
+        """
+        return self.verticalScrollBar().value()
+
+    def setScrollPosition(self, value):
+        """Set the scrollbar position to value.
+
+        Arguments:
+            value -- the new scrollbar position
+        """
+        self.verticalScrollBar().setValue(value)
+
     def signalUpdate(self):
         """Signal the delegate to update the model based on an editor change.
         """
@@ -154,6 +187,7 @@
         super().focusOutEvent(event)
         if event.reason() != Qt.PopupFocusReason:
             self.disableActions()
+            self.editEnding.emit(self)
 
     def hideEvent(self, event):
         """Reset format actions when the editor is hidden.
@@ -162,8 +196,41 @@
             event -- the hide event
         """
         self.disableActions()
+        self.editEnding.emit(self)
         super().hideEvent(event)
 
+    def keyPressEvent(self, event):
+        """Emit a signal after every key press and handle page up/down.
+
+        Needed to adjust scroll position in unlimited height editors.
+        Arguments:
+            event -- the key press event
+        """
+        if (event.key() in (Qt.Key_PageUp, Qt.Key_PageDown) and
+            not globalref.genOptions['EditorLimitHeight']):
+            pos = self.cursorRect().center()
+            if event.key() == Qt.Key_PageUp:
+                pos.setY(pos.y() - self.parent().height())
+                if pos.y() < 0:
+                    pos.setY(0)
+            else:
+                pos.setY(pos.y() + self.parent().height())
+                if pos.y() > self.height():
+                    pos.setY(self.height())
+            newCursor = self.cursorForPosition(pos)
+            if event.modifiers() == Qt.ShiftModifier:
+                cursor = self.textCursor()
+                cursor.setPosition(newCursor.position(),
+                                   QTextCursor.KeepAnchor)
+                self.setTextCursor(cursor)
+            else:
+                self.setTextCursor(newCursor)
+            event.accept()
+            self.keyPressed.emit(self)
+            return
+        super().keyPressEvent(event)
+        self.keyPressed.emit(self)
+
 
 class HtmlTextEditor(PlainTextEditor):
     """An editor for HTML fields, plain text with HTML insert commands.
@@ -829,6 +896,7 @@
     """
     dragLinkEnabled = False
     contentsChanged = pyqtSignal(QWidget)
+    editEnding = pyqtSignal(QWidget)
     contextMenuPrep = pyqtSignal()
     def __init__(self, parent=None, subControl=False):
         """Initialize the editor class.
@@ -881,6 +949,26 @@
         self.errorFlag = True
         self.update()
 
+    def cursorPosTuple(self):
+        """Return a tuple of the current cursor position and anchor (integers).
+        """
+        pos = start = self.cursorPosition()
+        if self.hasSelectedText():
+            start = self.selectionStart()
+        return (start, pos)
+
+    def setCursorPos(self, anchor, position):
+        """Set the cursor to the given anchor and position.
+        Arguments:
+            anchor -- the cursor selection start integer
+            position -- the cursor position or select end integer
+        """
+        if anchor == position:
+            self.deselect()
+            self.setCursorPosition(position)
+        else:
+            self.setSelection(anchor, position - anchor)
+
     def setCursorPoint(self, point):
         """Set the cursor to the given point.
 
@@ -895,6 +983,20 @@
         """
         self.selectAll()
 
+    def scrollPosition(self):
+        """Return the current scrollbar position.
+        """
+        return 0
+
+    def setScrollPosition(self, value):
+        """Set the scrollbar position to value.
+
+        No operation with single line editor.
+        Arguments:
+            value -- the new scrollbar position
+        """
+        pass
+
     def paintEvent(self, event):
         """Add painting of the error flag to the paint event.
         
@@ -971,6 +1073,7 @@
         super().focusOutEvent(event)
         if event.reason() != Qt.PopupFocusReason:
             self.disableActions()
+            self.editEnding.emit(self)
 
     def hideEvent(self, event):
         """Reset format actions when the editor is hidden.
@@ -979,6 +1082,7 @@
             event -- the hide event
         """
         self.disableActions()
+        self.editEnding.emit(self)
         super().hideEvent(event)
 
 
@@ -1005,6 +1109,7 @@
     """
     dragLinkEnabled = False
     contentsChanged = pyqtSignal(QWidget)
+    editEnding = pyqtSignal(QWidget)
     def __init__(self, parent=None):
         """Initialize the editor class.
 
@@ -1031,6 +1136,7 @@
         self.fieldRef = None
         self.nodeRef = None
         self.editTextChanged.connect(self.signalUpdate)
+        self.lineEdit().editEnding.connect(self.signalEditEnd)
 
     def setContents(self, text):
         """Set the contents of the editor to text.
@@ -1098,6 +1204,19 @@
         """
         self.lineEdit().selectAll()
 
+    def cursorPosTuple(self):
+        """Return a tuple of the current cursor position and anchor (integers).
+        """
+        return self.lineEdit().cursorPosTuple()
+
+    def setCursorPos(self, anchor, position):
+        """Set the cursor to the given anchor and position.
+        Arguments:
+            anchor -- the cursor selection start integer
+            position -- the cursor position or select end integer
+        """
+        self.lineEdit().setCursorPos(anchor, position)
+
     def setCursorPoint(self, point):
         """Set the cursor to the given point.
 
@@ -1111,6 +1230,20 @@
         """
         self.lineEdit().selectAll()
 
+    def scrollPosition(self):
+        """Return the current scrollbar position.
+        """
+        return 0
+
+    def setScrollPosition(self, value):
+        """Set the scrollbar position to value.
+
+        No operation with single line editor.
+        Arguments:
+            value -- the new scrollbar position
+        """
+        pass
+
     def copy(self):
         """Copy text selected in the line editor.
         """
@@ -1126,6 +1259,11 @@
         """
         self.lineEdit().paste()
 
+    def signalEditEnd(self):
+        """Emit editEnding signal based on line edit signal.
+        """
+        self.editEnding.emit(self)
+
 
 class CombinationEditor(ComboEditor):
     """An editor widget for combination and auto-combination fields.
@@ -2391,7 +2529,7 @@
         self.fixSelection()
 
     def keyPressEvent(self, event):
-        """Avoid edits or cursor movements to the statis portion of the text.
+        """Avoid edits or cursor movements to the static portion of the text.
 
         Arguments:
             event -- the mouse release event
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/TreeLine/source/dataeditview.py 
new/TreeLine/source/dataeditview.py
--- old/TreeLine/source/dataeditview.py 2019-03-15 01:46:55.000000000 +0100
+++ new/TreeLine/source/dataeditview.py 2019-10-03 02:41:40.000000000 +0200
@@ -48,6 +48,8 @@
         self.titleCellRef = titleCellRef
         self.typeCellRef = typeCellRef
         self.errorFlag = False
+        self.cursorPos = (-1, -1)
+        self.scrollPos = -1
         # store doc to speed up delegate sizeHint and paint calls
         self.doc = QTextDocument()
         self.doc.setDefaultFont(defaultFont)
@@ -71,6 +73,15 @@
         else:
             self.doc.setPlainText(self.text())
 
+    def storeEditorState(self, editor):
+        """Store the cursor & scroll positions baseed on an editor signal.
+
+        Arguments:
+            editor -- the editor that will get its state saved
+        """
+        self.cursorPos = editor.cursorPosTuple()
+        self.scrollPos = editor.scrollPosition()
+
 
 class DataEditDelegate(QStyledItemDelegate):
     """Class override for display and editing of DataEditCells.
@@ -83,6 +94,7 @@
         """
         super().__init__(parent)
         self.editorClickPos = None
+        self.tallEditScrollPos = -1
         self.lastEditor = None
         self.prevNumLines = -1
 
@@ -133,7 +145,8 @@
             doc.setTextWidth(styleOption.rect.width())
             size = doc.documentLayout().documentSize().toSize()
             maxHeight = self.parent().height() * 9 // 10  # 90% of view height
-            if size.height() > maxHeight:
+            if (size.height() > maxHeight and
+                globalref.genOptions['EditorLimitHeight']):
                 size.setHeight(maxHeight)
             if cell.field.numLines > 1:
                 minDoc = QTextDocument('\n' * (cell.field.numLines - 1))
@@ -165,6 +178,10 @@
                 editor.setErrorFlag()
             # self.parent().setFocusProxy(editor)
             editor.contentsChanged.connect(self.commitData)
+            editor.editEnding.connect(cell.storeEditorState)
+            if (not globalref.genOptions['EditorLimitHeight'] and
+                hasattr(editor, 'keyPressed')):
+                editor.keyPressed.connect(self.scrollOnKeyPress)
             if hasattr(editor, 'inLinkSelectMode'):
                 editor.inLinkSelectMode.connect(self.parent().
                                                 changeInLinkSelectMode)
@@ -205,8 +222,22 @@
             if self.editorClickPos:
                 editor.setCursorPoint(self.editorClickPos)
                 self.editorClickPos = None
-            else:
-                editor.resetCursor()
+            elif globalref.genOptions['EditorLimitHeight']:
+                if cell.cursorPos[1] >= 0:
+                    editor.setCursorPos(*cell.cursorPos)
+                    cell.cursorPos = (-1, -1)
+                    if cell.scrollPos >= 0:
+                        editor.setScrollPosition(cell.scrollPos)
+                        cell.scrollPos = -1
+                else:
+                    editor.resetCursor()
+            if (not globalref.genOptions['EditorLimitHeight'] and
+                globalref.genOptions['EditorOnHover'] and
+                self.tallEditScrollPos >= 0):
+                # maintain scroll position for unlimited height editors
+                # when hovering (use adjustScroll() for non-hovering
+                self.parent().verticalScrollBar().setValue(self.
+                                                           tallEditScrollPos)
         else:
             super().setEditorData(editor, modelIndex)
 
@@ -251,6 +282,39 @@
         editor.setMaximumSize(self.sizeHint(styleOption, modelIndex))
         super().updateEditorGeometry(editor, styleOption, modelIndex)
 
+    def adjustScroll(self):
+        """Reset the scroll back to the original for unlimited height editors.
+
+        Called from signal after any scroll change.
+        Needed for non-hovering to fix late scroll reset after editor created.
+        """
+        if (not globalref.genOptions['EditorLimitHeight'] and
+            not globalref.genOptions['EditorOnHover'] and
+            self.tallEditScrollPos >= 0):
+            self.parent().verticalScrollBar().setValue(self.tallEditScrollPos)
+            self.tallEditScrollPos = -1
+
+    def scrollOnKeyPress(self, editor):
+        """Adjust the scroll position to make cursor visible.
+
+        Needed after key presses on unlimited height editors.
+        Arguments:
+            editor -- the editor with the key press
+        """
+        if not globalref.genOptions['EditorLimitHeight']:
+            view = self.parent()
+            cursorRect = editor.cursorRect()
+            upperPos = editor.mapToGlobal(cursorRect.topLeft()).y()
+            lowerPos = editor.mapToGlobal(cursorRect.bottomLeft()).y()
+            viewRect = view.viewport().rect()
+            viewTop = view.mapToGlobal(viewRect.topLeft()).y()
+            viewBottom = view.mapToGlobal(viewRect.bottomLeft()).y()
+            bar = view.verticalScrollBar()
+            if upperPos < viewTop:
+                bar.setValue(bar.value() - (viewTop - upperPos))
+            elif lowerPos > viewBottom:
+                bar.setValue(bar.value() + (lowerPos - viewBottom))
+
     def editorEvent(self, event, model, styleOption, modelIndex):
         """Save the mouse click position in order to set the editor's cursor.
 
@@ -262,6 +326,8 @@
         """
         if event.type() == QEvent.MouseButtonPress:
             self.editorClickPos = event.globalPos()
+            # save scroll position for clicks on unlimited height editors
+            self.tallEditScrollPos = self.parent().verticalScrollBar().value()
         return super().editorEvent(event, model, styleOption, modelIndex)
 
     def eventFilter(self, editor, event):
@@ -338,6 +404,8 @@
         self.verticalHeader().hide()
         self.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel)
         self.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel)
+        self.verticalScrollBar().setSingleStep(self.fontMetrics().
+                                               lineSpacing())
         self.setSelectionMode(QAbstractItemView.SingleSelection)
         self.setItemDelegate(DataEditDelegate(self))
         self.setEditTriggers(QAbstractItemView.NoEditTriggers)
@@ -349,6 +417,8 @@
                      QApplication.palette().windowText())
         self.setPalette(pal)
         self.currentItemChanged.connect(self.moveEditor)
+        self.verticalScrollBar().valueChanged.connect(self.itemDelegate().
+                                                      adjustScroll)
 
     def updateContents(self):
         """Reload the view's content if the view is shown.
@@ -478,10 +548,13 @@
             newCell -- the new current edit cell item
             prevCell - the old current cell item
         """
-        if prevCell and hasattr(prevCell, 'updateText'):
-            self.closePersistentEditor(prevCell)
-            prevCell.updateText()
-            self.resizeRowToContents(prevCell.row())
+        try:
+            if prevCell and hasattr(prevCell, 'updateText'):
+                self.closePersistentEditor(prevCell)
+                prevCell.updateText()
+                self.resizeRowToContents(prevCell.row())
+        except RuntimeError:
+            pass   # avoid non-repeatable error involving deleted c++ object
         if newCell:
             self.openPersistentEditor(newCell)
 
@@ -697,6 +770,10 @@
             oldCell = self.currentItem()
             if (cell != oldCell and cell != self.prevHoverCell and
                 not self.inLinkSelectActive):
+                # save scroll position for unlimited height editors
+                self.itemDelegate().tallEditScrollPos = (self.
+                                                         verticalScrollBar().
+                                                         value())
                 self.prevHoverCell = cell
                 self.hoverFocusActive.emit()
                 self.setFocus()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/TreeLine/source/exports.py 
new/TreeLine/source/exports.py
--- old/TreeLine/source/exports.py      2019-03-02 21:47:44.000000000 +0100
+++ new/TreeLine/source/exports.py      2019-08-23 20:47:27.000000000 +0200
@@ -1188,7 +1188,7 @@
                   node.childList[0].formatRef.fieldNames()])
     lines.append('</tr><tr>')
     for child in node.childList:
-        cellList = [field.outputText(child, False, True) for field in
+        cellList = [field.outputText(child, False, False, True) for field in
                     child.formatRef.fields()]
         for i in range(len(cellList)):
             startPos = 0
@@ -1337,7 +1337,7 @@
     """
     nodeFormat = node.formatRef
     idField = next(iter(nodeFormat.fieldDict.values()))
-    uId = idField.outputText(node, True, nodeFormat.formatHtml)
+    uId = idField.outputText(node, True, True, nodeFormat.formatHtml)
     uId = uId.strip().split('\n', 1)[0]
     maxLength = 50
     if len(uId) > maxLength:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/TreeLine/source/fieldformat.py 
new/TreeLine/source/fieldformat.py
--- old/TreeLine/source/fieldformat.py  2019-02-21 03:20:44.000000000 +0100
+++ new/TreeLine/source/fieldformat.py  2019-08-24 04:09:06.000000000 +0200
@@ -40,6 +40,7 @@
                     MathResult.text: ''}
 _multipleSpaceRegEx = re.compile(r' {2,}')
 _lineBreakRegEx = re.compile(r'<br\s*/?>', re.I)
+_stripTagRe = re.compile('<.*?>')
 linkRegExp = re.compile(r'<a [^>]*href="([^"]+)"[^>]*>(.*?)</a>', re.I | re.S)
 linkSeparateNameRegExp = re.compile(r'(.*) \[(.*)\]\s*$')
 _imageRegExp = re.compile(r'<img [^>]*src="([^"]+)"[^>]*>', re.I | re.S)
@@ -116,12 +117,13 @@
         """
         self.format = format
 
-    def outputText(self, node, titleMode, formatHtml, spotRef=None):
+    def outputText(self, node, oneLine, noHtml, formatHtml, spotRef=None):
         """Return formatted output text for this field in this node.
 
         Arguments:
             node -- the tree item storing the data
-            titleMode -- if True, removes all HTML markup for tree title use
+            oneLine -- if True, returns only first line of output (for titles)
+            noHtml -- if True, removes all HTML markup (for titles, etc.)
             formatHtml -- if False, escapes HTML from prefix & suffix
             spotRef -- optional, used for ancestor field refs
         """
@@ -130,26 +132,28 @@
             node = node.treeStructureRef().fileInfoNode
         storedText = node.data.get(self.name, '')
         if storedText:
-            return self.formatOutput(storedText, titleMode, formatHtml)
+            return self.formatOutput(storedText, oneLine, noHtml, formatHtml)
         return ''
 
-    def formatOutput(self, storedText, titleMode, formatHtml):
+    def formatOutput(self, storedText, oneLine, noHtml, formatHtml):
         """Return formatted output text from stored text for this field.
 
         Arguments:
             storedText -- the source text to format
-            titleMode -- if True, removes all HTML markup for tree title use
+            oneLine -- if True, returns only first line of output (for titles)
+            noHtml -- if True, removes all HTML markup (for titles, etc.)
             formatHtml -- if False, escapes HTML from prefix & suffix
         """
         prefix = self.prefix
         suffix = self.suffix
-        if titleMode:
+        if oneLine:
             storedText = _lineBreakRegEx.split(storedText, 1)[0]
+        if noHtml:
             storedText = removeMarkup(storedText)
             if formatHtml:
                 prefix = removeMarkup(prefix)
                 suffix = removeMarkup(suffix)
-        elif not formatHtml:
+        if not formatHtml:
             prefix = saxutils.escape(prefix)
             suffix = saxutils.escape(suffix)
         return '{0}{1}{2}'.format(prefix, storedText, suffix)
@@ -338,16 +342,17 @@
         """
         super().__init__(name, formatData)
 
-    def formatOutput(self, storedText, titleMode, formatHtml):
+    def formatOutput(self, storedText, oneLine, noHtml, formatHtml):
         """Return formatted output text from stored text for this field.
 
         Arguments:
             storedText -- the source text to format
-            titleMode -- if True, removes all HTML markup for tree title use
+            oneLine -- if True, returns only first line of output (for titles)
+            noHtml -- if True, removes all HTML markup (for titles, etc.)
             formatHtml -- if False, escapes HTML from prefix & suffix
         """
         text = _lineBreakRegEx.split(storedText, 1)[0]
-        return super().formatOutput(text, titleMode, formatHtml)
+        return super().formatOutput(text, oneLine, noHtml, formatHtml)
 
     def formatEditorText(self, storedText):
         """Return text formatted for use in the data editor.
@@ -379,17 +384,18 @@
         """
         super().__init__(name, formatData)
 
-    def formatOutput(self, storedText, titleMode, formatHtml):
+    def formatOutput(self, storedText, oneLine, noHtml, formatHtml):
         """Return formatted output text from stored text for this field.
 
         Arguments:
             storedText -- the source text to format
-            titleMode -- if True, removes all HTML markup for tree title use
+            oneLine -- if True, returns only first line of output (for titles)
+            noHtml -- if True, removes all HTML markup (for titles, etc.)
             formatHtml -- if False, escapes HTML from prefix & suffix
         """
         if storedText:
             storedText = '<pre>{0}</pre>'.format(storedText)
-        return super().formatOutput(storedText, titleMode, formatHtml)
+        return super().formatOutput(storedText, oneLine, noHtml, formatHtml)
 
     def formatEditorText(self, storedText):
         """Return text formatted for use in the data editor.
@@ -453,19 +459,20 @@
         """
         super().__init__(name, formatData)
 
-    def formatOutput(self, storedText, titleMode, formatHtml):
+    def formatOutput(self, storedText, oneLine, noHtml, formatHtml):
         """Return formatted output text from stored text for this field.
 
         Arguments:
             storedText -- the source text to format
-            titleMode -- if True, removes all HTML markup for tree title use
+            oneLine -- if True, returns only first line of output (for titles)
+            noHtml -- if True, removes all HTML markup (for titles, etc.)
             formatHtml -- if False, escapes HTML from prefix & suffix
         """
         try:
             text = gennumber.GenNumber(storedText).numStr(self.format)
         except ValueError:
             text = _errorStr
-        return super().formatOutput(text, titleMode, formatHtml)
+        return super().formatOutput(text, oneLine, noHtml, formatHtml)
 
     def formatEditorText(self, storedText):
         """Return text formatted for use in the data editor.
@@ -569,12 +576,13 @@
             self.resultType = MathResult.number
         super().setFormat(format)
 
-    def formatOutput(self, storedText, titleMode, formatHtml):
+    def formatOutput(self, storedText, oneLine, noHtml, formatHtml):
         """Return formatted output text from stored text for this field.
 
         Arguments:
             storedText -- the source text to format
-            titleMode -- if True, removes all HTML markup for tree title use
+            oneLine -- if True, returns only first line of output (for titles)
+            noHtml -- if True, removes all HTML markup (for titles, etc.)
             formatHtml -- if False, escapes HTML from prefix & suffix
         """
         text = storedText
@@ -593,7 +601,7 @@
                 text =  genboolean.GenBoolean(text).boolStr(self.format)
         except ValueError:
             text = _errorStr
-        return super().formatOutput(text, titleMode, formatHtml)
+        return super().formatOutput(text, oneLine, noHtml, formatHtml)
 
     def formatEditorText(self, storedText):
         """Return text formatted for use in the data editor.
@@ -791,19 +799,20 @@
         self.numFormat = numbering.NumberingGroup(format)
         super().setFormat(format)
 
-    def formatOutput(self, storedText, titleMode, formatHtml):
+    def formatOutput(self, storedText, oneLine, noHtml, formatHtml):
         """Return formatted output text from stored text for this field.
 
         Arguments:
             storedText -- the source text to format
-            titleMode -- if True, removes all HTML markup for tree title use
+            oneLine -- if True, returns only first line of output (for titles)
+            noHtml -- if True, removes all HTML markup (for titles, etc.)
             formatHtml -- if False, escapes HTML from prefix & suffix
         """
         try:
             text = self.numFormat.numString(storedText)
         except ValueError:
             text = _errorStr
-        return super().formatOutput(text, titleMode, formatHtml)
+        return super().formatOutput(text, oneLine, noHtml, formatHtml)
 
     def formatEditorText(self, storedText):
         """Return text formatted for use in the data editor.
@@ -877,12 +886,13 @@
         """
         super().__init__(name, formatData)
 
-    def formatOutput(self, storedText, titleMode, formatHtml):
+    def formatOutput(self, storedText, oneLine, noHtml, formatHtml):
         """Return formatted output text from stored text for this field.
 
         Arguments:
             storedText -- the source text to format
-            titleMode -- if True, removes all HTML markup for tree title use
+            oneLine -- if True, returns only first line of output (for titles)
+            noHtml -- if True, removes all HTML markup (for titles, etc.)
             formatHtml -- if False, escapes HTML from prefix & suffix
         """
         try:
@@ -893,7 +903,7 @@
             text = _errorStr
         if not self.evalHtml:
             text = saxutils.escape(text)
-        return super().formatOutput(text, titleMode, formatHtml)
+        return super().formatOutput(text, oneLine, noHtml, formatHtml)
 
     def formatEditorText(self, storedText):
         """Return text formatted for use in the data editor.
@@ -1044,12 +1054,13 @@
         """
         super().__init__(name, formatData)
 
-    def formatOutput(self, storedText, titleMode, formatHtml):
+    def formatOutput(self, storedText, oneLine, noHtml, formatHtml):
         """Return formatted output text from stored text for this field.
 
         Arguments:
             storedText -- the source text to format
-            titleMode -- if True, removes all HTML markup for tree title use
+            oneLine -- if True, returns only first line of output (for titles)
+            noHtml -- if True, removes all HTML markup (for titles, etc.)
             formatHtml -- if False, escapes HTML from prefix & suffix
         """
         try:
@@ -1062,7 +1073,7 @@
             text = _errorStr
         if not self.evalHtml:
             text = saxutils.escape(text)
-        return super().formatOutput(text, titleMode, formatHtml)
+        return super().formatOutput(text, oneLine, noHtml, formatHtml)
 
     def formatEditorText(self, storedText):
         """Return text formatted for use in the data editor.
@@ -1252,12 +1263,13 @@
         """
         super().__init__(name, formatData)
 
-    def formatOutput(self, storedText, titleMode, formatHtml):
+    def formatOutput(self, storedText, oneLine, noHtml, formatHtml):
         """Return formatted output text from stored text for this field.
 
         Arguments:
             storedText -- the source text to format
-            titleMode -- if True, removes all HTML markup for tree title use
+            oneLine -- if True, returns only first line of output (for titles)
+            noHtml -- if True, removes all HTML markup (for titles, etc.)
             formatHtml -- if False, escapes HTML from prefix & suffix
         """
         try:
@@ -1270,7 +1282,7 @@
             text = _errorStr
         if not self.evalHtml:
             text = saxutils.escape(text)
-        return super().formatOutput(text, titleMode, formatHtml)
+        return super().formatOutput(text, oneLine, noHtml, formatHtml)
 
     def formatEditorText(self, storedText):
         """Return text formatted for use in the data editor.
@@ -1445,17 +1457,18 @@
             self.choices = set([saxutils.escape(choice) for choice in
                                 self.choiceList])
 
-    def formatOutput(self, storedText, titleMode, formatHtml):
+    def formatOutput(self, storedText, oneLine, noHtml, formatHtml):
         """Return formatted output text from stored text for this field.
 
         Arguments:
             storedText -- the source text to format
-            titleMode -- if True, removes all HTML markup for tree title use
+            oneLine -- if True, returns only first line of output (for titles)
+            noHtml -- if True, removes all HTML markup (for titles, etc.)
             formatHtml -- if False, escapes HTML from prefix & suffix
         """
         if storedText not in self.choices:
             storedText = _errorStr
-        return super().formatOutput(storedText, titleMode, formatHtml)
+        return super().formatOutput(storedText, oneLine, noHtml, formatHtml)
 
     def formatEditorText(self, storedText):
         """Return text formatted for use in the data editor.
@@ -1608,25 +1621,27 @@
         self.choices = set(self.choiceList)
         self.outputSep = ''
 
-    def outputText(self, node, titleMode, formatHtml, spotRef=None):
+    def outputText(self, node, oneLine, noHtml, formatHtml, spotRef=None):
         """Return formatted output text for this field in this node.
 
         Sets output separator prior to calling base class methods.
         Arguments:
             node -- the tree item storing the data
-            titleMode -- if True, removes all HTML markup for tree title use
+            oneLine -- if True, returns only first line of output (for titles)
+            noHtml -- if True, removes all HTML markup (for titles, etc.)
             formatHtml -- if False, escapes HTML from prefix & suffix
             spotRef -- optional, used for ancestor field refs
         """
         self.outputSep = node.formatRef.outputSeparator
-        return super().outputText(node, titleMode, formatHtml, spotRef)
+        return super().outputText(node, oneLine, noHtml, formatHtml, spotRef)
 
-    def formatOutput(self, storedText, titleMode, formatHtml):
+    def formatOutput(self, storedText, oneLine, noHtml, formatHtml):
         """Return formatted output text from stored text for this field.
 
         Arguments:
             storedText -- the source text to format
-            titleMode -- if True, removes all HTML markup for tree title use
+            oneLine -- if True, returns only first line of output (for titles)
+            noHtml -- if True, removes all HTML markup (for titles, etc.)
             formatHtml -- if False, escapes HTML from prefix & suffix
         """
         selections, valid = self.sortedSelections(storedText)
@@ -1634,7 +1649,8 @@
             result = self.outputSep.join(selections)
         else:
             result = _errorStr
-        return TextField.formatOutput(self, result, titleMode, formatHtml)
+        return TextField.formatOutput(self, result, oneLine, noHtml,
+                                      formatHtml)
 
     def formatEditorText(self, storedText):
         """Return text formatted for use in the data editor.
@@ -1731,29 +1747,32 @@
         self.choices = set()
         self.outputSep = ''
 
-    def outputText(self, node, titleMode, formatHtml, spotRef=None):
+    def outputText(self, node, oneLine, noHtml, formatHtml, spotRef=None):
         """Return formatted output text for this field in this node.
 
         Sets output separator prior to calling base class methods.
         Arguments:
             node -- the tree item storing the data
-            titleMode -- if True, removes all HTML markup for tree title use
+            oneLine -- if True, returns only first line of output (for titles)
+            noHtml -- if True, removes all HTML markup (for titles, etc.)
             formatHtml -- if False, escapes HTML from prefix & suffix
             spotRef -- optional, used for ancestor field refs
         """
         self.outputSep = node.formatRef.outputSeparator
-        return super().outputText(node, titleMode, formatHtml, spotRef)
+        return super().outputText(node, oneLine, noHtml, formatHtml, spotRef)
 
-    def formatOutput(self, storedText, titleMode, formatHtml):
+    def formatOutput(self, storedText, oneLine, noHtml, formatHtml):
         """Return formatted output text from stored text for this field.
 
         Arguments:
             storedText -- the source text to format
-            titleMode -- if True, removes all HTML markup for tree title use
+            oneLine -- if True, returns only first line of output (for titles)
+            noHtml -- if True, removes all HTML markup (for titles, etc.)
             formatHtml -- if False, escapes HTML from prefix & suffix
         """
         result = self.outputSep.join(self.splitText(storedText))
-        return TextField.formatOutput(self, result, titleMode, formatHtml)
+        return TextField.formatOutput(self, result, oneLine, noHtml,
+                                      formatHtml)
 
     def formatEditorText(self, storedText):
         """Return text formatted for use in the data editor.
@@ -1859,12 +1878,13 @@
         HtmlTextField.setFormat(self, format)
         self.strippedFormat = removeMarkup(self.format)
 
-    def formatOutput(self, storedText, titleMode, formatHtml):
+    def formatOutput(self, storedText, oneLine, noHtml, formatHtml):
         """Return formatted output text from stored text for this field.
 
         Arguments:
             storedText -- the source text to format
-            titleMode -- if True, removes all HTML markup for tree title use
+            oneLine -- if True, returns only first line of output (for titles)
+            noHtml -- if True, removes all HTML markup (for titles, etc.)
             formatHtml -- if False, escapes HTML from prefix & suffix
         """
         try:
@@ -1873,7 +1893,8 @@
             text = _errorStr
         if not self.evalHtml:
             text = saxutils.escape(text)
-        return HtmlTextField.formatOutput(self, text, titleMode, formatHtml)
+        return HtmlTextField.formatOutput(self, text, oneLine, noHtml,
+                                          formatHtml)
 
     def formatEditorText(self, storedText):
         """Return text formatted for use in the data editor.
@@ -1994,22 +2015,23 @@
         address, name = linkMatch.groups()
         return (address, name)
 
-    def formatOutput(self, storedText, titleMode, formatHtml):
+    def formatOutput(self, storedText, oneLine, noHtml, formatHtml):
         """Return formatted output text from stored text for this field.
 
         Arguments:
             storedText -- the source text to format
-            titleMode -- if True, removes all HTML markup for tree title use
+            oneLine -- if True, returns only first line of output (for titles)
+            noHtml -- if True, removes all HTML markup (for titles, etc.)
             formatHtml -- if False, escapes HTML from prefix & suffix
         """
-        if titleMode:
+        if noHtml:
             linkMatch = linkRegExp.search(storedText)
             if linkMatch:
                 address, name = linkMatch.groups()
                 storedText = name.strip()
                 if not storedText:
                     storedText = address.lstrip('#')
-        return super().formatOutput(storedText, titleMode, formatHtml)
+        return super().formatOutput(storedText, oneLine, noHtml, formatHtml)
 
     def formatEditorText(self, storedText):
         """Return text formatted for use in the data editor.
@@ -2156,20 +2178,21 @@
         """
         super().__init__(name, formatData)
 
-    def formatOutput(self, storedText, titleMode, formatHtml):
+    def formatOutput(self, storedText, oneLine, noHtml, formatHtml):
         """Return formatted output text from stored text for this field.
 
         Arguments:
             storedText -- the source text to format
-            titleMode -- if True, removes all HTML markup for tree title use
+            oneLine -- if True, returns only first line of output (for titles)
+            noHtml -- if True, removes all HTML markup (for titles, etc.)
             formatHtml -- if False, escapes HTML from prefix & suffix
         """
-        if titleMode:
+        if noHtml:
             linkMatch = _imageRegExp.search(storedText)
             if linkMatch:
                 address = linkMatch.group(1)
                 storedText = address.strip()
-        return super().formatOutput(storedText, titleMode, formatHtml)
+        return super().formatOutput(storedText, oneLine, noHtml, formatHtml)
 
     def formatEditorText(self, storedText):
         """Return text formatted for use in the data editor.
@@ -2266,12 +2289,13 @@
             raise ValueError
         super().setFormat(format)
 
-    def formatOutput(self, storedText, titleMode, formatHtml):
+    def formatOutput(self, storedText, oneLine, noHtml, formatHtml):
         """Return formatted output text from stored text for this field.
 
         Arguments:
             storedText -- the source text to format
-            titleMode -- if True, removes all HTML markup for tree title use
+            oneLine -- if True, returns only first line of output (for titles)
+            noHtml -- if True, removes all HTML markup (for titles, etc.)
             formatHtml -- if False, escapes HTML from prefix & suffix
         """
         match = re.fullmatch(self.format, saxutils.unescape(storedText))
@@ -2279,7 +2303,7 @@
             text = storedText
         else:
             text = _errorStr
-        return super().formatOutput(text, titleMode, formatHtml)
+        return super().formatOutput(text, oneLine, noHtml, formatHtml)
 
     def formatEditorText(self, storedText):
         """Return text formatted for use in the data editor.
@@ -2324,13 +2348,14 @@
         super().__init__(name, {})
         self.ancestorLevel = ancestorLevel
 
-    def outputText(self, node, titleMode, formatHtml, spotRef=None):
+    def outputText(self, node, oneLine, noHtml, formatHtml, spotRef=None):
         """Return formatted output text for this field in this node.
 
         Finds the appropriate ancestor node to get the field text.
         Arguments:
             node -- the tree node to start from
-            titleMode -- if True, removes all HTML markup for tree title use
+            oneLine -- if True, returns only first line of output (for titles)
+            noHtml -- if True, removes all HTML markup (for titles, etc.)
             formatHtml -- if False, escapes HTML from prefix & suffix
             spotRef -- optional, used for ancestor field refs
         """
@@ -2344,7 +2369,7 @@
             field = spotRef.nodeRef.formatRef.fieldDict[self.name]
         except (AttributeError, KeyError):
             return ''
-        return field.outputText(spotRef.nodeRef, titleMode, formatHtml,
+        return field.outputText(spotRef.nodeRef, oneLine, noHtml, formatHtml,
                                 spotRef)
 
     def sepName(self):
@@ -2365,13 +2390,14 @@
         """
         super().__init__(name, {})
 
-    def outputText(self, node, titleMode, formatHtml, spotRef=None):
+    def outputText(self, node, oneLine, noHtml, formatHtml, spotRef=None):
         """Return formatted output text for this field in this node.
 
         Finds the appropriate ancestor node to get the field text.
         Arguments:
             node -- the tree node to start from
-            titleMode -- if True, removes all HTML markup for tree title use
+            oneLine -- if True, returns only first line of output (for titles)
+            noHtml -- if True, removes all HTML markup (for titles, etc.)
             formatHtml -- if False, escapes HTML from prefix & suffix
             spotRef -- optional, used for ancestor field refs
         """
@@ -2384,8 +2410,8 @@
             except (AttributeError, KeyError):
                 pass
             else:
-                return field.outputText(spotRef.nodeRef, titleMode, formatHtml,
-                                        spotRef)
+                return field.outputText(spotRef.nodeRef, oneLine, noHtml,
+                                        formatHtml, spotRef)
         return ''
 
     def sepName(self):
@@ -2406,13 +2432,14 @@
         """
         super().__init__(name, {})
 
-    def outputText(self, node, titleMode, formatHtml, spotRef=None):
+    def outputText(self, node, oneLine, noHtml, formatHtml, spotRef=None):
         """Return formatted output text for this field in this node.
 
         Returns a joined list of matching child field data.
         Arguments:
             node -- the tree node to start from
-            titleMode -- if True, removes all HTML markup for tree title use
+            oneLine -- if True, returns only first line of output (for titles)
+            noHtml -- if True, removes all HTML markup (for titles, etc.)
             formatHtml -- if False, escapes HTML from prefix & suffix
             spotRef -- optional, used for ancestor field refs
         """
@@ -2423,8 +2450,8 @@
             except KeyError:
                 pass
             else:
-                result.append(field.outputText(child, titleMode, formatHtml,
-                                               spotRef))
+                result.append(field.outputText(child, oneLine, noHtml,
+                                               formatHtml, spotRef))
         outputSep = node.formatRef.outputSeparator
         return outputSep.join(result)
 
@@ -2448,13 +2475,14 @@
         super().__init__(name, {})
         self.descendantLevel = descendantLevel
 
-    def outputText(self, node, titleMode, formatHtml, spotRef=None):
+    def outputText(self, node, oneLine, noHtml, formatHtml, spotRef=None):
         """Return formatted output text for this field in this node.
 
         Returns a count of descendants at the approriate level.
         Arguments:
             node -- the tree node to start from
-            titleMode -- if True, removes all HTML markup for tree title use
+            oneLine -- if True, returns only first line of output (for titles)
+            noHtml -- if True, removes all HTML markup (for titles, etc.)
             formatHtml -- if False, escapes HTML from prefix & suffix
             spotRef -- optional, used for ancestor field refs
         """
@@ -2474,11 +2502,12 @@
 
 ####  Utility Functions  ####
 
-_stripTagRe = re.compile('<.*?>')
-
 def removeMarkup(text):
     """Return text with all HTML Markup removed and entities unescaped.
+
+    Any <br /> tags are replaced with newlines.
     """
+    text = _lineBreakRegEx.sub('\n', text)
     text = _stripTagRe.sub('', text)
     return saxutils.unescape(text)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/TreeLine/source/imports.py 
new/TreeLine/source/imports.py
--- old/TreeLine/source/imports.py      2019-02-21 03:20:44.000000000 +0100
+++ new/TreeLine/source/imports.py      2019-09-07 20:18:41.000000000 +0200
@@ -4,7 +4,7 @@
 # imports.py, provides classes for a file import dialog and import functions
 #
 # TreeLine, an information storage program
-# Copyright (C) 2018, Douglas W. Bell
+# Copyright (C) 2019, Douglas W. Bell
 #
 # This is free software; you can redistribute it and/or modify it under the
 # terms of the GNU General Public License, either Version 2 or any later
@@ -639,8 +639,14 @@
         tpFormat = structure.treeFormats[treeformats.defaultTypeName]
         tpFormat.addFieldList([textFieldName], False, True)
         tpFormat.fieldDict[textFieldName].changeType('SpacedText')
-        with self.pathObj.open(encoding=globalref.localTextEncoding) as f:
-            textList = f.read().split('<end node> 5P9i0s8y19Z')
+        try:
+            with self.pathObj.open(encoding=globalref.localTextEncoding) as f:
+                textList = f.read().split('<end node> 5P9i0s8y19Z')
+        except UnicodeDecodeError:
+            with self.pathObj.open(encoding='latin-1') as f:
+                textList = f.read().split('<end node> 5P9i0s8y19Z')
+        except UnicodeDecodeError:
+            return None
         nodeList = []
         for text in textList:
             text = text.strip()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/TreeLine/source/nodeformat.py 
new/TreeLine/source/nodeformat.py
--- old/TreeLine/source/nodeformat.py   2019-02-21 03:20:44.000000000 +0100
+++ new/TreeLine/source/nodeformat.py   2019-08-23 20:00:27.000000000 +0200
@@ -4,7 +4,7 @@
 # nodeformat.py, provides a class to handle node format objects
 #
 # TreeLine, an information storage program
-# Copyright (C) 2018, Douglas W. Bell
+# Copyright (C) 2019, Douglas W. Bell
 #
 # This is free software; you can redistribute it and/or modify it under the
 # terms of the GNU General Public License, either Version 2 or any later
@@ -166,7 +166,7 @@
             node -- the node used to get data for fields
             spotRef -- optional, used for ancestor field refs
         """
-        line = ''.join([part.outputText(node, True, self.formatHtml)
+        line = ''.join([part.outputText(node, True, True, self.formatHtml)
                         if hasattr(part, 'outputText') else part
                         for part in self.titleLine])
         return line.strip()
@@ -188,7 +188,8 @@
             numFullFields = 0
             for part in lineData:
                 if hasattr(part, 'outputText'):
-                    text = part.outputText(node, plainText, self.formatHtml)
+                    text = part.outputText(node, False, plainText,
+                                           self.formatHtml)
                     if text:
                         numFullFields += 1
                     else:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/TreeLine/source/numbering.py 
new/TreeLine/source/numbering.py
--- old/TreeLine/source/numbering.py    2018-10-21 18:54:47.000000000 +0200
+++ new/TreeLine/source/numbering.py    2019-10-03 02:34:09.000000000 +0200
@@ -4,7 +4,7 @@
 # numbering.py, provides classes to format node numbering
 #
 # TreeLine, an information storage program
-# Copyright (C) 2013, Douglas W. Bell
+# Copyright (C) 2019, Douglas W. Bell
 #
 # This is free software; you can redistribute it and/or modify it under the
 # terms of the GNU General Public License, either Version 2 or any later
@@ -137,6 +137,7 @@
 def _alphaFromNum(num, upperCase=True):
     """Return an alphabetic string from an integer.
 
+    Sequence is 'A', 'B' ... 'Z', 'AA', 'BB' ... 'ZZ', 'AAA', 'BBB' ...
     Arguments:
         num -- the integer to convert
         upperCase -- return an upper case string if true
@@ -144,10 +145,10 @@
     if num <= 0:
         return ''
     result = ''
-    while num:
-        digit = (num - 1) % 26
-        result = chr(digit + ord('A')) + result
-        num = (num - digit - 1) // 26
+    charPos = (num - 1) % 26
+    char = chr(charPos + ord('A'))
+    qty = (num - 1) // 26 + 1
+    result = char * qty
     if not upperCase:
         result = result.lower()
     return result
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/TreeLine/source/optiondefaults.py 
new/TreeLine/source/optiondefaults.py
--- old/TreeLine/source/optiondefaults.py       2019-02-21 03:20:44.000000000 
+0100
+++ new/TreeLine/source/optiondefaults.py       2019-05-31 02:53:55.000000000 
+0200
@@ -57,6 +57,9 @@
     BoolOptionItem(generalOptions, 'EditorOnHover', True,
                    _('Features Available'),
                    _('Activate data editors on mouse hover'))
+    BoolOptionItem(generalOptions, 'EditorLimitHeight', True,
+                   _('Features Available'),
+                   _('Limit data editor height to window size'))
     BoolOptionItem(generalOptions, 'ClickRename', True,
                    _('Features Available'), _('Click node to rename'))
     BoolOptionItem(generalOptions, 'RenameNewNodes', True,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/TreeLine/source/printdata.py 
new/TreeLine/source/printdata.py
--- old/TreeLine/source/printdata.py    2019-03-01 03:10:09.000000000 +0100
+++ new/TreeLine/source/printdata.py    2019-09-17 03:16:15.000000000 +0200
@@ -357,7 +357,10 @@
         """Paint data to be printed to the printer.
         """
         pageNum = 1
-        maxPageNum = self.outputGroup[-1].pageNum
+        try:
+            maxPageNum = self.outputGroup[-1].pageNum
+        except IndexError:   # printing empty branch
+            maxPageNum = 1
         if self.printer.printRange() != QPrinter.AllPages:
             pageNum = self.printer.fromPage()
             maxPageNum = self.printer.toPage()
@@ -385,7 +388,10 @@
         # set context text color to black to wrok with dark app themes
         paintContext.palette = QPalette()
         paintContext.palette.setColor(QPalette.Text, Qt.black)
-        totalNumPages = self.outputGroup[-1].pageNum
+        try:
+            totalNumPages = self.outputGroup[-1].pageNum
+        except IndexError:   # printing empty branch
+            totalNumPages = 1
         headerDoc = self.headerFooterDoc(True, pageNum, totalNumPages)
         if headerDoc:
             layout = headerDoc.documentLayout()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/TreeLine/source/titlelistview.py 
new/TreeLine/source/titlelistview.py
--- old/TreeLine/source/titlelistview.py        2019-02-21 03:20:44.000000000 
+0100
+++ new/TreeLine/source/titlelistview.py        2019-05-09 03:38:23.000000000 
+0200
@@ -70,8 +70,6 @@
             return
         if self.isChildView:
             selSpots = selSpots[0].childSpots()
-        self.treeSelectAction.setEnabled(self.isChildView and
-                                         len(selSpots) > 0)
         self.blockSignals(True)
         if selSpots:
             self.setPlainText('\n'.join(spot.nodeRef.title(spot) for spot in
@@ -206,6 +204,8 @@
         menu.removeAction(menu.actions()[0])
         menu.insertSeparator(menu.actions()[0])
         menu.insertAction(menu.actions()[0], self.treeSelectAction)
+        self.treeSelectAction.setEnabled(self.isChildView and
+                                         len(self.toPlainText().strip()) > 0)
         menu.exec_(event.globalPos())
 
     def keyPressEvent(self, event):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/TreeLine/source/treeline.py 
new/TreeLine/source/treeline.py
--- old/TreeLine/source/treeline.py     2019-03-16 20:06:29.000000000 +0100
+++ new/TreeLine/source/treeline.py     2019-10-04 02:45:32.000000000 +0200
@@ -13,7 +13,7 @@
 #******************************************************************************
 
 __progname__ = 'TreeLine'
-__version__ = '3.1.1'
+__version__ = '3.1.2'
 __author__ = 'Doug Bell'
 
 docPath = None         # modified by install script if required
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/TreeLine/source/treelocalcontrol.py 
new/TreeLine/source/treelocalcontrol.py
--- old/TreeLine/source/treelocalcontrol.py     2019-03-15 01:46:55.000000000 
+0100
+++ new/TreeLine/source/treelocalcontrol.py     2019-10-05 19:57:48.000000000 
+0200
@@ -117,7 +117,7 @@
             self.setWindowSignals(window, True)
             window.updateActions(self.allActions)
             self.windowList.append(window)
-            window.setCaption(self.filePathObj)
+            self.updateWindowCaptions()
             self.activeWindow = window
         if fileObj and self.structure.childRefErrorNodes:
             msg = _('Warning - file corruption!\n'
@@ -303,7 +303,7 @@
         """Update the caption for all windows.
         """
         for window in self.windowList:
-            window.setCaption(self.filePathObj)
+            window.setCaption(self.filePathObj, self.modified)
 
     def setModified(self, modified=True):
         """Set the modified flag on this file and update commands available.
@@ -314,6 +314,7 @@
         if modified != self.modified:
             self.modified = modified
             self.allActions['FileSave'].setEnabled(modified)
+            self.updateWindowCaptions()
             self.resetAutoSave()
 
     def expandRootNodes(self, maxNum=5):
@@ -1682,7 +1683,7 @@
         self.setWindowSignals(window)
         window.winMinimized.connect(globalref.mainControl.trayMinimize)
         self.windowList.append(window)
-        window.setCaption(self.filePathObj)
+        self.updateWindowCaptions()
         oldControl = globalref.mainControl.activeControl
         if oldControl:
             oldControl.activeWindow.saveWindowGeom()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/TreeLine/source/treemaincontrol.py 
new/TreeLine/source/treemaincontrol.py
--- old/TreeLine/source/treemaincontrol.py      2019-02-21 03:20:44.000000000 
+0100
+++ new/TreeLine/source/treemaincontrol.py      2019-04-14 19:31:15.000000000 
+0200
@@ -1078,8 +1078,8 @@
         palette.setColor(QPalette.WindowText, Qt.white)
         palette.setColor(QPalette.Base, myVeryDarkGray)
         palette.setColor(QPalette.AlternateBase, myDarkGray)
-        palette.setColor(QPalette.ToolTipBase, Qt.white)
-        palette.setColor(QPalette.ToolTipText, Qt.white)
+        palette.setColor(QPalette.ToolTipBase, Qt.darkBlue)
+        palette.setColor(QPalette.ToolTipText, Qt.lightGray)
         palette.setColor(QPalette.Text, Qt.white)
         palette.setColor(QPalette.Button, myDarkGray)
         palette.setColor(QPalette.ButtonText, Qt.white)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/TreeLine/source/treewindow.py 
new/TreeLine/source/treewindow.py
--- old/TreeLine/source/treewindow.py   2019-02-21 03:20:44.000000000 +0100
+++ new/TreeLine/source/treewindow.py   2019-10-05 19:52:55.000000000 +0200
@@ -265,15 +265,17 @@
         self.activateWindow()
         self.raise_()
 
-    def setCaption(self, pathObj=None):
+    def setCaption(self, pathObj=None, modified=False):
         """Change the window caption title based on the file name and path.
 
         Arguments:
             pathObj - a path object for the current file
         """
+        modFlag = '*' if modified else ''
         if pathObj:
-            caption = '{0} [{1}] - TreeLine'.format(str(pathObj.name),
-                                                    str(pathObj.parent))
+            caption = '{0}{1} [{2}] - TreeLine'.format(str(pathObj.name),
+                                                       modFlag,
+                                                       str(pathObj.parent))
         else:
             caption = '- TreeLine'
         self.setWindowTitle(caption)
Binary files old/TreeLine/translations/qt_zh.qm and 
new/TreeLine/translations/qt_zh.qm differ
Binary files old/TreeLine/translations/treeline_zh.qm and 
new/TreeLine/translations/treeline_zh.qm differ


Reply via email to