diff -Nru treeline-2.0.2/debian/changelog treeline-3.0.1/debian/changelog --- treeline-2.0.2/debian/changelog 2018-05-24 19:07:35.000000000 +0000 +++ treeline-3.0.1/debian/changelog 2018-10-17 13:06:15.000000000 +0000 @@ -1,3 +1,11 @@ +treeline (3.0.1-1) unstable; urgency=medium + + * New Upstream Release. + * Runs on Python3 and Qt5 now. Closes: #875210 + * Upgraded Standards-Version from 4.1.4 to 4.2.1.2 + + -- Miriam Ruiz Wed, 17 Oct 2018 15:06:15 +0200 + treeline (2.0.2-2) unstable; urgency=medium * Going back to Qt4 because of some unresolved segmentation faults. diff -Nru treeline-2.0.2/debian/control treeline-3.0.1/debian/control --- treeline-2.0.2/debian/control 2018-05-24 19:07:35.000000000 +0000 +++ treeline-3.0.1/debian/control 2018-10-17 13:06:15.000000000 +0000 @@ -2,15 +2,16 @@ Section: utils Priority: optional Maintainer: Miriam Ruiz -Build-Depends: debhelper, dh-buildinfo, quilt, -Build-Depends-Indep: python3, dh-python, libqt4-dev (>= 4.4) -Standards-Version: 4.1.4 +Build-Depends: debhelper, dh-buildinfo, quilt +Build-Depends-Indep: python3, dh-python, qttools5-dev-tools, + qtchooser, qttranslations5-l10n +Standards-Version: 4.2.1.2 Homepage: http://treeline.bellz.org/ Package: treeline Architecture: all -Depends: ${python3:Depends}, python3-pyqt4, ${misc:Depends} -Recommends: aspell | ispell +Depends: ${python3:Depends}, python3-pyqt5, ${misc:Depends} +Recommends: aspell | ispell, qttranslations5-l10n Description: versatile tree-like structured custom data manager TreeLine is a versatile tool for working with all kind of information that fits into a tree-like structure. diff -Nru treeline-2.0.2/debian/copyright treeline-3.0.1/debian/copyright --- treeline-2.0.2/debian/copyright 2011-05-17 19:04:18.000000000 +0000 +++ treeline-3.0.1/debian/copyright 2018-10-17 13:06:15.000000000 +0000 @@ -5,7 +5,7 @@ Copyright: - Copyright (C) 2006 Doug Bell + Copyright (C) 2006-2018 Doug Bell p3.py: Copyright (C) 2002-2003 Paul Rubin diff -Nru treeline-2.0.2/debian/patches/pop_empty_list.patch treeline-3.0.1/debian/patches/pop_empty_list.patch --- treeline-2.0.2/debian/patches/pop_empty_list.patch 2018-05-24 08:35:54.000000000 +0000 +++ treeline-3.0.1/debian/patches/pop_empty_list.patch 2018-10-17 13:06:15.000000000 +0000 @@ -2,17 +2,15 @@ =================================================================== --- TreeLine.orig/source/treeformats.py +++ TreeLine/source/treeformats.py -@@ -210,7 +210,12 @@ class TreeFormats(dict): +@@ -232,7 +232,10 @@ class TreeFormats(dict): formats = set() for node in nodes: - formats.add(node.formatName) + formats.add(node.formatRef.name) - firstFields = self[formats.pop()].fieldNames() -+ + try: + firstFields = self[formats.pop()].fieldNames() + except KeyError: + return [] -+ commonFields = set(firstFields) for formatName in formats: commonFields.intersection_update(self[formatName].fieldNames()) diff -Nru treeline-2.0.2/debian/patches/qt4_to_qt5.patch treeline-3.0.1/debian/patches/qt4_to_qt5.patch --- treeline-2.0.2/debian/patches/qt4_to_qt5.patch 2018-05-24 08:35:54.000000000 +0000 +++ treeline-3.0.1/debian/patches/qt4_to_qt5.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,6792 +0,0 @@ -Index: TreeLine/install.py -=================================================================== ---- TreeLine.orig/install.py -+++ TreeLine/install.py -@@ -229,7 +229,7 @@ def main(): - .format(pyVersion)) - sys.exit(3) - try: -- from PyQt4 import QtCore, QtGui -+ from PyQt5 import QtCore - except: - print(' PyQt not found -> Sorry, PyQt 4.x is required' - ' and must be built for Python 3') -Index: TreeLine/source/conditional.py -=================================================================== ---- TreeLine.orig/source/conditional.py -+++ TreeLine/source/conditional.py -@@ -13,7 +13,7 @@ - #****************************************************************************** - - import re --from PyQt4 import QtCore, QtGui -+from PyQt5 import QtCore, QtGui, QtWidgets - import treeformats - import configdialog - import undo -@@ -191,7 +191,7 @@ class StringOps(str): - return False - - --class ConditionDialog(QtGui.QDialog): -+class ConditionDialog(QtWidgets.QDialog): - """Dialog for defining field condition tests. - - Used for defining conditional types (modal), for finding by condition -@@ -219,7 +219,7 @@ class ConditionDialog(QtGui.QDialog): - self.fieldNames = [] - if nodeFormat: - self.fieldNames = nodeFormat.fieldNames() -- topLayout = QtGui.QVBoxLayout(self) -+ topLayout = QtWidgets.QVBoxLayout(self) - - if dialogType == ConditionDialog.typeDialog: - self.setWindowFlags(QtCore.Qt.Dialog | QtCore.Qt.WindowTitleHint | -@@ -228,64 +228,64 @@ class ConditionDialog(QtGui.QDialog): - self.setAttribute(QtCore.Qt.WA_QuitOnClose, False) - self.setWindowFlags(QtCore.Qt.Window | - QtCore.Qt.WindowStaysOnTopHint) -- typeBox = QtGui.QGroupBox(_('Node Type')) -+ typeBox = QtWidgets.QGroupBox(_('Node Type')) - topLayout.addWidget(typeBox) -- typeLayout = QtGui.QVBoxLayout(typeBox) -- self.typeCombo = QtGui.QComboBox() -+ typeLayout = QtWidgets.QVBoxLayout(typeBox) -+ self.typeCombo = QtWidgets.QComboBox() - typeLayout.addWidget(self.typeCombo) - self.typeCombo.currentIndexChanged.connect(self.updateDataType) - -- self.mainLayout = QtGui.QVBoxLayout() -+ self.mainLayout = QtWidgets.QVBoxLayout() - topLayout.addLayout(self.mainLayout) - -- upCtrlLayout = QtGui.QHBoxLayout() -+ upCtrlLayout = QtWidgets.QHBoxLayout() - topLayout.addLayout(upCtrlLayout) - upCtrlLayout.addStretch() -- addButton = QtGui.QPushButton(_('&Add New Rule')) -+ addButton = QtWidgets.QPushButton(_('&Add New Rule')) - upCtrlLayout.addWidget(addButton) - addButton.clicked.connect(self.addNewRule) -- self.removeButton = QtGui.QPushButton(_('&Remove Rule')) -+ self.removeButton = QtWidgets.QPushButton(_('&Remove Rule')) - upCtrlLayout.addWidget(self.removeButton) - self.removeButton.clicked.connect(self.removeRule) - - if dialogType == ConditionDialog.typeDialog: -- okButton = QtGui.QPushButton(_('&OK')) -+ okButton = QtWidgets.QPushButton(_('&OK')) - upCtrlLayout.addWidget(okButton) - okButton.clicked.connect(self.accept) -- cancelButton = QtGui.QPushButton(_('&Cancel')) -+ cancelButton = QtWidgets.QPushButton(_('&Cancel')) - upCtrlLayout.addWidget(cancelButton) - cancelButton.clicked.connect(self.reject) - else: - self.removeButton.setEnabled(False) -- self.retrieveButton = QtGui.QPushButton(_('R&etrieve Rules...')) -+ self.retrieveButton = QtWidgets.QPushButton(_('R&etrieve Rules...')) - upCtrlLayout.addWidget(self.retrieveButton) - self.retrieveButton.clicked.connect(self.retrieveRules) -- saveButton = QtGui.QPushButton(_('&Save Rules...')) -+ saveButton = QtWidgets.QPushButton(_('&Save Rules...')) - upCtrlLayout.addWidget(saveButton) - saveButton.clicked.connect(self.saveRules) - -- lowCtrlLayout = QtGui.QHBoxLayout() -+ lowCtrlLayout = QtWidgets.QHBoxLayout() - topLayout.addLayout(lowCtrlLayout) - lowCtrlLayout.addStretch() - if dialogType == ConditionDialog.findDialog: -- previousButton = QtGui.QPushButton(_('Find &Previous')) -+ previousButton = QtWidgets.QPushButton(_('Find &Previous')) - lowCtrlLayout.addWidget(previousButton) - previousButton.clicked.connect(self.findPrevious) -- nextButton = QtGui.QPushButton(_('Find &Next')) -+ nextButton = QtWidgets.QPushButton(_('Find &Next')) - nextButton.setDefault(True) - lowCtrlLayout.addWidget(nextButton) - nextButton.clicked.connect(self.findNext) -- self.resultLabel = QtGui.QLabel() -+ self.resultLabel = QtWidgets.QLabel() - topLayout.addWidget(self.resultLabel) - else: -- filterButton = QtGui.QPushButton(_('&Filter')) -+ filterButton = QtWidgets.QPushButton(_('&Filter')) - lowCtrlLayout.addWidget(filterButton) - filterButton.clicked.connect(self.startFilter) -- self.endFilterButton = QtGui.QPushButton(_('&End Filter')) -+ self.endFilterButton = QtWidgets.QPushButton(_('&End Filter')) - lowCtrlLayout.addWidget(self.endFilterButton) - self.endFilterButton.setEnabled(False) - self.endFilterButton.clicked.connect(self.endFilter) -- closeButton = QtGui.QPushButton(_('&Close')) -+ closeButton = QtWidgets.QPushButton(_('&Close')) - lowCtrlLayout.addWidget(closeButton) - closeButton.clicked.connect(self.close) - origTypeName = nodeFormat.name if nodeFormat else '' -@@ -301,7 +301,7 @@ class ConditionDialog(QtGui.QDialog): - combineBool -- the boolean op for combining with the previous rule - """ - if self.ruleList: -- boolBox = QtGui.QComboBox() -+ boolBox = QtWidgets.QComboBox() - boolBox.setEditable(False) - self.combiningBoxes.append(boolBox) - boolBox.addItems([_(op) for op in _boolOper]) -@@ -446,7 +446,7 @@ class ConditionDialog(QtGui.QDialog): - savedRules = nodeFormats.savedConditions() - ruleNames = sorted(list(savedRules.keys())) - dlg = RuleRetrieveDialog(ruleNames, self) -- if dlg.exec_() == QtGui.QDialog.Accepted: -+ if dlg.exec_() == QtWidgets.QDialog.Accepted: - if dlg.selectedRule: - conditional = savedRules[dlg.selectedRule] - self.setCondition(conditional, conditional.origNodeFormatName) -@@ -474,7 +474,7 @@ class ConditionDialog(QtGui.QDialog): - dlg = configdialog.NameEntryDialog(_('Save Rules'), - _('Enter a descriptive name'), '', - '', usedNames, self) -- if dlg.exec_() == QtGui.QDialog.Accepted: -+ if dlg.exec_() == QtWidgets.QDialog.Accepted: - undo.FormatUndo(modelRef.undoList, nodeFormats, - treeformats.TreeFormats()) - typeName = self.typeCombo.currentText() -@@ -536,7 +536,7 @@ class ConditionDialog(QtGui.QDialog): - self.dialogShown.emit(False) - - --class ConditionRule(QtGui.QGroupBox): -+class ConditionRule(QtWidgets.QGroupBox): - """Group boxes for conditional rules in the ConditionDialog. - """ - def __init__(self, num, fieldNames, parent=None): -@@ -550,19 +550,19 @@ class ConditionRule(QtGui.QGroupBox): - super().__init__(parent) - self.fieldNames = fieldNames - self.setTitle(_('Rule {0}').format(num)) -- layout = QtGui.QHBoxLayout(self) -- self.fieldBox = QtGui.QComboBox() -+ layout = QtWidgets.QHBoxLayout(self) -+ self.fieldBox = QtWidgets.QComboBox() - self.fieldBox.setEditable(False) - self.fieldBox.addItems(fieldNames) - layout.addWidget(self.fieldBox) - -- self.operBox = QtGui.QComboBox() -+ self.operBox = QtWidgets.QComboBox() - self.operBox.setEditable(False) - self.operBox.addItems([_(op) for op in _operators]) - layout.addWidget(self.operBox) - self.operBox.currentIndexChanged.connect(self.changeOper) - -- self.editor = QtGui.QLineEdit() -+ self.editor = QtWidgets.QLineEdit() - layout.addWidget(self.editor) - self.fieldBox.setFocus() - -@@ -613,7 +613,7 @@ class ConditionRule(QtGui.QGroupBox): - self.fieldBox.setEnabled(realOp) - - --class RuleRetrieveDialog(QtGui.QDialog): -+class RuleRetrieveDialog(QtWidgets.QDialog): - """Dialog to select saved conditional rules for retrieval or removal. - """ - def __init__(self, ruleNames, parent=None): -@@ -631,25 +631,25 @@ class RuleRetrieveDialog(QtGui.QDialog): - self.selectedRule = '' - self.removedRules = [] - -- topLayout = QtGui.QVBoxLayout(self) -- label = QtGui.QLabel(_('Select rule set to retrieve:')) -+ topLayout = QtWidgets.QVBoxLayout(self) -+ label = QtWidgets.QLabel(_('Select rule set to retrieve:')) - topLayout.addWidget(label) -- self.listBox = QtGui.QListWidget() -+ self.listBox = QtWidgets.QListWidget() - topLayout.addWidget(self.listBox) - self.listBox.addItems(ruleNames) - self.listBox.setCurrentRow (0) - self.listBox.itemDoubleClicked.connect(self.accept) - -- ctrlLayout = QtGui.QHBoxLayout() -+ ctrlLayout = QtWidgets.QHBoxLayout() - topLayout.addLayout(ctrlLayout) -- removeButton = QtGui.QPushButton(_('Remove Rule')) -+ removeButton = QtWidgets.QPushButton(_('Remove Rule')) - ctrlLayout.addWidget(removeButton) - removeButton.clicked.connect(self.removeRule) - ctrlLayout.addStretch() -- okButton = QtGui.QPushButton(_('&OK')) -+ okButton = QtWidgets.QPushButton(_('&OK')) - ctrlLayout.addWidget(okButton) - okButton.clicked.connect(self.accept) -- cancelButton = QtGui.QPushButton(_('&Cancel')) -+ cancelButton = QtWidgets.QPushButton(_('&Cancel')) - ctrlLayout.addWidget(cancelButton) - cancelButton.clicked.connect(self.reject) - -Index: TreeLine/source/configdialog.py -=================================================================== ---- TreeLine.orig/source/configdialog.py -+++ TreeLine/source/configdialog.py -@@ -15,7 +15,7 @@ - import re - import copy - import operator --from PyQt4 import QtCore, QtGui -+from PyQt5 import QtCore, QtGui, QtWidgets - import nodeformat - import fieldformat - import icondict -@@ -24,7 +24,7 @@ import matheval - import globalref - - --class ConfigDialog(QtGui.QDialog): -+class ConfigDialog(QtWidgets.QDialog): - """Class override for the main config dialog - - Contains the tabbed pages that handle the actual settings. -@@ -47,10 +47,10 @@ class ConfigDialog(QtGui.QDialog): - self.prevPage = None - self.selectionModel = None - -- topLayout = QtGui.QVBoxLayout(self) -+ topLayout = QtWidgets.QVBoxLayout(self) - self.setLayout(topLayout) - -- self.tabs = QtGui.QTabWidget() -+ self.tabs = QtWidgets.QTabWidget() - topLayout.addWidget(self.tabs) - typeListPage = TypeListPage(self) - self.tabs.addTab(typeListPage, _('T&ype List')) -@@ -64,23 +64,23 @@ class ConfigDialog(QtGui.QDialog): - self.tabs.addTab(outputPage, _('O&utput')) - self.tabs.currentChanged.connect(self.updatePage) - -- ctrlLayout = QtGui.QHBoxLayout() -+ ctrlLayout = QtWidgets.QHBoxLayout() - topLayout.addLayout(ctrlLayout) -- self.advancedButton = QtGui.QPushButton(_('&Show Advanced')) -+ self.advancedButton = QtWidgets.QPushButton(_('&Show Advanced')) - ctrlLayout.addWidget(self.advancedButton) - self.advancedButton.setCheckable(True) - self.advancedButton.clicked.connect(self.toggleAdavanced) - ctrlLayout.addStretch() -- okButton = QtGui.QPushButton(_('&OK')) -+ okButton = QtWidgets.QPushButton(_('&OK')) - ctrlLayout.addWidget(okButton) - okButton.clicked.connect(self.applyAndClose) -- self.applyButton = QtGui.QPushButton(_('&Apply')) -+ self.applyButton = QtWidgets.QPushButton(_('&Apply')) - ctrlLayout.addWidget(self.applyButton) - self.applyButton.clicked.connect(self.applyChanges) -- self.resetButton = QtGui.QPushButton(_('&Reset')) -+ self.resetButton = QtWidgets.QPushButton(_('&Reset')) - ctrlLayout.addWidget(self.resetButton) - self.resetButton.clicked.connect(self.reset) -- cancelButton = QtGui.QPushButton(_('&Cancel')) -+ cancelButton = QtWidgets.QPushButton(_('&Cancel')) - ctrlLayout.addWidget(cancelButton) - cancelButton.clicked.connect(self.resetAndClose) - -@@ -175,7 +175,7 @@ class ConfigDialog(QtGui.QDialog): - try: - ConfigDialog.modelRef.applyConfigDialogFormats() - except matheval.CircularMathError: -- QtGui.QMessageBox.warning(self, 'TreeLine', -+ QtWidgets.QMessageBox.warning(self, 'TreeLine', - _('Error - circular reference in math field equations')) - return False - ConfigDialog.formatsRef = (ConfigDialog.modelRef. -@@ -208,7 +208,7 @@ class ConfigDialog(QtGui.QDialog): - self.dialogShown.emit(False) - - --class ConfigPage(QtGui.QWidget): -+class ConfigPage(QtWidgets.QWidget): - """Abstract base class for config dialog tabbed pages. - """ - def __init__(self, parent=None): -@@ -277,26 +277,26 @@ class TypeListPage(ConfigPage): - parent -- the parent overall dialog - """ - super().__init__(parent) -- topLayout = QtGui.QVBoxLayout(self) -- box = QtGui.QGroupBox(_('Add or Remove Data Types')) -+ topLayout = QtWidgets.QVBoxLayout(self) -+ box = QtWidgets.QGroupBox(_('Add or Remove Data Types')) - topLayout.addWidget(box) -- horizLayout = QtGui.QHBoxLayout(box) -- self.listBox = QtGui.QListWidget() -+ horizLayout = QtWidgets.QHBoxLayout(box) -+ self.listBox = QtWidgets.QListWidget() - horizLayout.addWidget(self.listBox) - self.listBox.currentTextChanged.connect(self.changeCurrentType) - -- buttonLayout = QtGui.QVBoxLayout() -+ buttonLayout = QtWidgets.QVBoxLayout() - horizLayout.addLayout(buttonLayout) -- newButton = QtGui.QPushButton(_('&New Type...')) -+ newButton = QtWidgets.QPushButton(_('&New Type...')) - buttonLayout.addWidget(newButton) - newButton.clicked.connect(self.newType) -- copyButton = QtGui.QPushButton(_('Co&py Type...')) -+ copyButton = QtWidgets.QPushButton(_('Co&py Type...')) - buttonLayout.addWidget(copyButton) - copyButton.clicked.connect(self.copyType) -- renameButton = QtGui.QPushButton(_('Rena&me Type...')) -+ renameButton = QtWidgets.QPushButton(_('Rena&me Type...')) - buttonLayout.addWidget(renameButton) - renameButton.clicked.connect(self.renameType) -- deleteButton = QtGui.QPushButton(_('&Delete Type')) -+ deleteButton = QtWidgets.QPushButton(_('&Delete Type')) - buttonLayout.addWidget(deleteButton) - deleteButton.clicked.connect(self.deleteType) - -@@ -315,7 +315,7 @@ class TypeListPage(ConfigPage): - """ - dlg = NameEntryDialog(_('Add Type'), _('Enter new type name:'), '', '', - ConfigDialog.formatsRef.typeNames(), self) -- if dlg.exec_() == QtGui.QDialog.Accepted: -+ if dlg.exec_() == QtWidgets.QDialog.Accepted: - newFormat = nodeformat.NodeFormat(dlg.text, - ConfigDialog.formatsRef, {}, - True) -@@ -335,7 +335,7 @@ class TypeListPage(ConfigPage): - ConfigDialog.formatsRef.typeNames(), self) - if currentFormat.genericType: - dlg.extraCheckBox.setEnabled(False) -- if dlg.exec_() == QtGui.QDialog.Accepted: -+ if dlg.exec_() == QtWidgets.QDialog.Accepted: - newFormat = copy.deepcopy(currentFormat) - newFormat.name = dlg.text - ConfigDialog.formatsRef[dlg.text] = newFormat -@@ -353,7 +353,7 @@ class TypeListPage(ConfigPage): - dlg = NameEntryDialog(_('Rename Type'), - _('Rename from {} to:').format(oldName), oldName, - '', ConfigDialog.formatsRef.typeNames(), self) -- if dlg.exec_() == QtGui.QDialog.Accepted: -+ if dlg.exec_() == QtWidgets.QDialog.Accepted: - currentType = ConfigDialog.formatsRef[oldName] - currentType.name = dlg.text - del ConfigDialog.formatsRef[oldName] -@@ -381,7 +381,7 @@ class TypeListPage(ConfigPage): - """Delete the selected type based on button signal. - """ - if ConfigDialog.modelRef.root.usesType(ConfigDialog.currentTypeName): -- QtGui.QMessageBox.warning(self, 'TreeLine', -+ QtWidgets.QMessageBox.warning(self, 'TreeLine', - _('Cannot delete data type being used by nodes')) - return - del ConfigDialog.formatsRef[ConfigDialog.currentTypeName] -@@ -411,86 +411,86 @@ class TypeConfigPage(ConfigPage): - parent -- the parent overall dialog - """ - super().__init__(parent) -- topLayout = QtGui.QGridLayout(self) -- typeBox = QtGui.QGroupBox(_('&Data Type')) -+ topLayout = QtWidgets.QGridLayout(self) -+ typeBox = QtWidgets.QGroupBox(_('&Data Type')) - topLayout.addWidget(typeBox, 0, 0) -- typeLayout = QtGui.QVBoxLayout(typeBox) -- self.typeCombo = QtGui.QComboBox() -+ typeLayout = QtWidgets.QVBoxLayout(typeBox) -+ self.typeCombo = QtWidgets.QComboBox() - typeLayout.addWidget(self.typeCombo) - self.typeCombo.currentIndexChanged[str].connect(self.changeCurrentType) - -- childBox = QtGui.QGroupBox(_('Default Child &Type')) -+ childBox = QtWidgets.QGroupBox(_('Default Child &Type')) - topLayout.addWidget(childBox, 0, 1) -- childLayout = QtGui.QVBoxLayout(childBox) -- self.childCombo = QtGui.QComboBox() -+ childLayout = QtWidgets.QVBoxLayout(childBox) -+ self.childCombo = QtWidgets.QComboBox() - childLayout.addWidget(self.childCombo) - self.childCombo.currentIndexChanged.connect(self.mainDialogRef. - setModified) - -- iconBox = QtGui.QGroupBox(_('Icon')) -+ iconBox = QtWidgets.QGroupBox(_('Icon')) - topLayout.addWidget(iconBox, 1, 1) -- iconLayout = QtGui.QHBoxLayout(iconBox) -- self.iconImage = QtGui.QLabel() -+ iconLayout = QtWidgets.QHBoxLayout(iconBox) -+ self.iconImage = QtWidgets.QLabel() - iconLayout.addWidget(self.iconImage) - self.iconImage.setAlignment(QtCore.Qt.AlignCenter) -- iconButton = QtGui.QPushButton(_('Change &Icon')) -+ iconButton = QtWidgets.QPushButton(_('Change &Icon')) - iconLayout.addWidget(iconButton) - iconButton.clicked.connect(self.changeIcon) - -- optionsBox = QtGui.QGroupBox(_('Output Options')) -+ optionsBox = QtWidgets.QGroupBox(_('Output Options')) - topLayout.addWidget(optionsBox, 1, 0, 2, 1) -- optionsLayout = QtGui.QVBoxLayout(optionsBox) -- self.blanksButton = QtGui.QCheckBox(_('Add &blank lines between ' -+ optionsLayout = QtWidgets.QVBoxLayout(optionsBox) -+ self.blanksButton = QtWidgets.QCheckBox(_('Add &blank lines between ' - 'nodes')) - optionsLayout.addWidget(self.blanksButton) - self.blanksButton.toggled.connect(self.mainDialogRef.setModified) -- self.htmlButton = QtGui.QCheckBox(_('Allow &HTML rich text in format')) -+ self.htmlButton = QtWidgets.QCheckBox(_('Allow &HTML rich text in format')) - optionsLayout.addWidget(self.htmlButton) - self.htmlButton.toggled.connect(self.mainDialogRef.setModified) -- self.bulletButton = QtGui.QCheckBox(_('Add text bullet&s')) -+ self.bulletButton = QtWidgets.QCheckBox(_('Add text bullet&s')) - optionsLayout.addWidget(self.bulletButton) - self.bulletButton.toggled.connect(self.changeUseBullets) -- self.tableButton = QtGui.QCheckBox(_('Use a table for field &data')) -+ self.tableButton = QtWidgets.QCheckBox(_('Use a table for field &data')) - optionsLayout.addWidget(self.tableButton) - self.tableButton.toggled.connect(self.changeUseTable) - - # advanced widgets -- outputSepBox = QtGui.QGroupBox(_('Combination && Child List Output ' -+ outputSepBox = QtWidgets.QGroupBox(_('Combination && Child List Output ' - '&Separator')) - topLayout.addWidget(outputSepBox, 2, 1) - self.advancedWidgets.append(outputSepBox) -- outputSepLayout = QtGui.QVBoxLayout(outputSepBox) -- self.outputSepEdit = QtGui.QLineEdit() -+ outputSepLayout = QtWidgets.QVBoxLayout(outputSepBox) -+ self.outputSepEdit = QtWidgets.QLineEdit() - outputSepLayout.addWidget(self.outputSepEdit) - sizePolicy = self.outputSepEdit.sizePolicy() -- sizePolicy.setHorizontalPolicy(QtGui.QSizePolicy.Preferred) -+ sizePolicy.setHorizontalPolicy(QtWidgets.QSizePolicy.Preferred) - self.outputSepEdit.setSizePolicy(sizePolicy) - self.outputSepEdit.textEdited.connect(self.mainDialogRef.setModified) - -- idFieldBox = QtGui.QGroupBox(_('Uni&que ID Reference Field')) -+ idFieldBox = QtWidgets.QGroupBox(_('Uni&que ID Reference Field')) - topLayout.addWidget(idFieldBox, 3, 0) - self.advancedWidgets.append(idFieldBox) -- idFieldLayout = QtGui.QVBoxLayout(idFieldBox) -- self.idFieldCombo = QtGui.QComboBox() -+ idFieldLayout = QtWidgets.QVBoxLayout(idFieldBox) -+ self.idFieldCombo = QtWidgets.QComboBox() - idFieldLayout.addWidget(self.idFieldCombo) - self.idFieldCombo.currentIndexChanged.connect(self.mainDialogRef. - setModified) - -- genericBox = QtGui.QGroupBox(_('Derived from &Generic Type')) -+ genericBox = QtWidgets.QGroupBox(_('Derived from &Generic Type')) - topLayout.addWidget(genericBox, 3, 1) - self.advancedWidgets.append(genericBox) -- genericLayout = QtGui.QVBoxLayout(genericBox) -- self.genericCombo = QtGui.QComboBox() -+ genericLayout = QtWidgets.QVBoxLayout(genericBox) -+ self.genericCombo = QtWidgets.QComboBox() - genericLayout.addWidget(self.genericCombo) - self.genericCombo.currentIndexChanged.connect(self.setGenericIdRef) - self.genericCombo.currentIndexChanged.connect(self.mainDialogRef. - setModified) - -- conditionBox = QtGui.QGroupBox(_('Automatic Types')) -+ conditionBox = QtWidgets.QGroupBox(_('Automatic Types')) - topLayout.addWidget(conditionBox, 4, 1) - self.advancedWidgets.append(conditionBox) -- conditionLayout = QtGui.QVBoxLayout(conditionBox) -- self.conditionButton = QtGui.QPushButton() -+ conditionLayout = QtWidgets.QVBoxLayout(conditionBox) -+ self.conditionButton = QtWidgets.QPushButton() - conditionLayout.addWidget(self.conditionButton) - self.conditionButton.clicked.connect(self.showConditionDialog) - -@@ -573,7 +573,7 @@ class TypeConfigPage(ConfigPage): - """ - currentFormat = ConfigDialog.formatsRef[ConfigDialog.currentTypeName] - dlg = IconSelectDialog(currentFormat, self) -- if (dlg.exec_() == QtGui.QDialog.Accepted and -+ if (dlg.exec_() == QtWidgets.QDialog.Accepted and - dlg.currentIconName != currentFormat.iconName): - currentFormat.iconName = dlg.currentIconName - self.mainDialogRef.setModified() -@@ -650,7 +650,7 @@ class TypeConfigPage(ConfigPage): - currentFormat) - if currentFormat.conditional: - dialog.setCondition(currentFormat.conditional) -- if dialog.exec_() == QtGui.QDialog.Accepted: -+ if dialog.exec_() == QtWidgets.QDialog.Accepted: - currentFormat.conditional = dialog.conditional() - ConfigDialog.formatsRef.updateDerivedRefs() - self.mainDialogRef.setModified() -@@ -703,18 +703,18 @@ class FieldListPage(ConfigPage): - parent -- the parent overall dialog - """ - super().__init__(parent) -- topLayout = QtGui.QVBoxLayout(self) -- typeBox = QtGui.QGroupBox(_('&Data Type')) -+ topLayout = QtWidgets.QVBoxLayout(self) -+ typeBox = QtWidgets.QGroupBox(_('&Data Type')) - topLayout.addWidget(typeBox) -- typeLayout = QtGui.QVBoxLayout(typeBox) -- self.typeCombo = QtGui.QComboBox() -+ typeLayout = QtWidgets.QVBoxLayout(typeBox) -+ self.typeCombo = QtWidgets.QComboBox() - typeLayout.addWidget(self.typeCombo) - self.typeCombo.currentIndexChanged[str].connect(self.changeCurrentType) - -- fieldBox = QtGui.QGroupBox(_('Modify &Field List')) -+ fieldBox = QtWidgets.QGroupBox(_('Modify &Field List')) - topLayout.addWidget(fieldBox) -- horizLayout = QtGui.QHBoxLayout(fieldBox) -- self.fieldListBox = QtGui.QTreeWidget() -+ horizLayout = QtWidgets.QHBoxLayout(fieldBox) -+ self.fieldListBox = QtWidgets.QTreeWidget() - horizLayout.addWidget(self.fieldListBox) - self.fieldListBox.setRootIsDecorated(False) - self.fieldListBox.setColumnCount(3) -@@ -722,24 +722,24 @@ class FieldListPage(ConfigPage): - _('Sort Key')]) - self.fieldListBox.currentItemChanged.connect(self.changeField) - -- buttonLayout = QtGui.QVBoxLayout() -+ buttonLayout = QtWidgets.QVBoxLayout() - horizLayout.addLayout(buttonLayout) -- self.upButton = QtGui.QPushButton(_('Move U&p')) -+ self.upButton = QtWidgets.QPushButton(_('Move U&p')) - buttonLayout.addWidget(self.upButton) - self.upButton.clicked.connect(self.moveUp) -- self.downButton = QtGui.QPushButton(_('Move Do&wn')) -+ self.downButton = QtWidgets.QPushButton(_('Move Do&wn')) - buttonLayout.addWidget(self.downButton) - self.downButton.clicked.connect(self.moveDown) -- self.newButton = QtGui.QPushButton(_('&New Field...')) -+ self.newButton = QtWidgets.QPushButton(_('&New Field...')) - buttonLayout.addWidget(self.newButton) - self.newButton.clicked.connect(self.newField) -- self.renameButton = QtGui.QPushButton(_('Rena&me Field...')) -+ self.renameButton = QtWidgets.QPushButton(_('Rena&me Field...')) - buttonLayout.addWidget(self.renameButton) - self.renameButton.clicked.connect(self.renameField) -- self.deleteButton = QtGui.QPushButton(_('Dele&te Field')) -+ self.deleteButton = QtWidgets.QPushButton(_('Dele&te Field')) - buttonLayout.addWidget(self.deleteButton) - self.deleteButton.clicked.connect(self.deleteField) -- sortKeyButton = QtGui.QPushButton(_('Sort &Keys...')) -+ sortKeyButton = QtWidgets.QPushButton(_('Sort &Keys...')) - buttonLayout.addWidget(sortKeyButton) - sortKeyButton.clicked.connect(self.defineSortKeys) - -@@ -769,13 +769,13 @@ class FieldListPage(ConfigPage): - sortKey = '{0} ({1})'.format(sortKey, sortDir) - except ValueError: - sortKey = '' -- QtGui.QTreeWidgetItem(self.fieldListBox, -+ QtWidgets.QTreeWidgetItem(self.fieldListBox, - [field.name, _(field.typeName), sortKey]) - selectNum = currentFormat.fieldNames().index(ConfigDialog. - currentFieldName) - selectItem = self.fieldListBox.topLevelItem(selectNum) - self.fieldListBox.setCurrentItem(selectItem) -- self.fieldListBox.setItemSelected(selectItem, True) -+ selectItem.setSelected(True) - width = self.fieldListBox.viewport().width() - self.fieldListBox.setColumnWidth(0, width // 2.5) - self.fieldListBox.setColumnWidth(1, width // 2.5) -@@ -831,7 +831,7 @@ class FieldListPage(ConfigPage): - currentFormat = ConfigDialog.formatsRef[ConfigDialog.currentTypeName] - dlg = NameEntryDialog(_('Add Field'), _('Enter new field name:'), '', - '', currentFormat.fieldNames(), self) -- if dlg.exec_() == QtGui.QDialog.Accepted: -+ if dlg.exec_() == QtWidgets.QDialog.Accepted: - currentFormat.addField(dlg.text) - ConfigDialog.currentFieldName = dlg.text - currentFormat.updateDerivedTypes() -@@ -847,7 +847,7 @@ class FieldListPage(ConfigPage): - dlg = NameEntryDialog(_('Rename Field'), - _('Rename from {} to:').format(oldName), oldName, - '', fieldList, self) -- if dlg.exec_() == QtGui.QDialog.Accepted: -+ if dlg.exec_() == QtWidgets.QDialog.Accepted: - num = fieldList.index(oldName) - fieldList[num] = dlg.text - for nodeFormat in [currentFormat] + currentFormat.derivedTypes: -@@ -898,7 +898,7 @@ class FieldListPage(ConfigPage): - """ - currentFormat = ConfigDialog.formatsRef[ConfigDialog.currentTypeName] - dlg = SortKeyDialog(currentFormat.fieldDict, self) -- if dlg.exec_() == QtGui.QDialog.Accepted: -+ if dlg.exec_() == QtWidgets.QDialog.Accepted: - self.updateContent() - self.mainDialogRef.setModified() - -@@ -917,87 +917,87 @@ class FieldConfigPage(ConfigPage): - super().__init__(parent) - self.currentFileInfoField = '' - -- topLayout = QtGui.QGridLayout(self) -- typeBox = QtGui.QGroupBox(_('&Data Type')) -+ topLayout = QtWidgets.QGridLayout(self) -+ typeBox = QtWidgets.QGroupBox(_('&Data Type')) - topLayout.addWidget(typeBox, 0, 0) -- typeLayout = QtGui.QVBoxLayout(typeBox) -- self.typeCombo = QtGui.QComboBox() -+ typeLayout = QtWidgets.QVBoxLayout(typeBox) -+ self.typeCombo = QtWidgets.QComboBox() - typeLayout.addWidget(self.typeCombo) - self.typeCombo.currentIndexChanged[str].connect(self.changeCurrentType) - -- fieldBox = QtGui.QGroupBox(_('F&ield')) -+ fieldBox = QtWidgets.QGroupBox(_('F&ield')) - topLayout.addWidget(fieldBox, 0, 1) -- fieldLayout = QtGui.QVBoxLayout(fieldBox) -- self.fieldCombo = QtGui.QComboBox() -+ fieldLayout = QtWidgets.QVBoxLayout(fieldBox) -+ self.fieldCombo = QtWidgets.QComboBox() - fieldLayout.addWidget(self.fieldCombo) - self.fieldCombo.currentIndexChanged[str].connect(self. - changeCurrentField) - -- fieldTypeBox = QtGui.QGroupBox(_('&Field Type')) -+ fieldTypeBox = QtWidgets.QGroupBox(_('&Field Type')) - topLayout.addWidget(fieldTypeBox, 1, 0) -- fieldTypeLayout = QtGui.QVBoxLayout(fieldTypeBox) -- self.fieldTypeCombo = QtGui.QComboBox() -+ fieldTypeLayout = QtWidgets.QVBoxLayout(fieldTypeBox) -+ self.fieldTypeCombo = QtWidgets.QComboBox() - fieldTypeLayout.addWidget(self.fieldTypeCombo) - self.fieldTypeCombo.addItems([_(name) for name in - fieldformat.fieldTypes]) - self.fieldTypeCombo.currentIndexChanged.connect(self.changeFieldType) - -- self.formatBox = QtGui.QGroupBox(_('Outpu&t Format')) -+ self.formatBox = QtWidgets.QGroupBox(_('Outpu&t Format')) - topLayout.addWidget(self.formatBox, 1, 1) -- formatLayout = QtGui.QHBoxLayout(self.formatBox) -- self.formatEdit = QtGui.QLineEdit() -+ formatLayout = QtWidgets.QHBoxLayout(self.formatBox) -+ self.formatEdit = QtWidgets.QLineEdit() - formatLayout.addWidget(self.formatEdit) - self.formatEdit.textEdited.connect(self.mainDialogRef.setModified) -- self.helpButton = QtGui.QPushButton(_('Format &Help')) -+ self.helpButton = QtWidgets.QPushButton(_('Format &Help')) - formatLayout.addWidget(self.helpButton) - self.helpButton.clicked.connect(self.formatHelp) - -- extraBox = QtGui.QGroupBox(_('Extra Text')) -+ extraBox = QtWidgets.QGroupBox(_('Extra Text')) - topLayout.addWidget(extraBox, 2, 0, 2, 1) -- extraLayout = QtGui.QVBoxLayout(extraBox) -+ extraLayout = QtWidgets.QVBoxLayout(extraBox) - extraLayout.setSpacing(0) -- prefixLabel = QtGui.QLabel(_('&Prefix')) -+ prefixLabel = QtWidgets.QLabel(_('&Prefix')) - extraLayout.addWidget(prefixLabel) -- self.prefixEdit = QtGui.QLineEdit() -+ self.prefixEdit = QtWidgets.QLineEdit() - extraLayout.addWidget(self.prefixEdit) - prefixLabel.setBuddy(self.prefixEdit) - self.prefixEdit.textEdited.connect(self.mainDialogRef.setModified) - extraLayout.addSpacing(8) -- suffixLabel = QtGui.QLabel(_('Suffi&x')) -+ suffixLabel = QtWidgets.QLabel(_('Suffi&x')) - extraLayout.addWidget(suffixLabel) -- self.suffixEdit = QtGui.QLineEdit() -+ self.suffixEdit = QtWidgets.QLineEdit() - extraLayout.addWidget(self.suffixEdit) - suffixLabel.setBuddy(self.suffixEdit) - self.suffixEdit.textEdited.connect(self.mainDialogRef.setModified) - -- defaultBox = QtGui.QGroupBox(_('Default &Value for New Nodes')) -+ defaultBox = QtWidgets.QGroupBox(_('Default &Value for New Nodes')) - topLayout.addWidget(defaultBox, 2, 1) -- defaultLayout = QtGui.QVBoxLayout(defaultBox) -- self.defaultCombo = QtGui.QComboBox() -+ defaultLayout = QtWidgets.QVBoxLayout(defaultBox) -+ self.defaultCombo = QtWidgets.QComboBox() - defaultLayout.addWidget(self.defaultCombo) - self.defaultCombo.setEditable(True) - self.defaultCombo.editTextChanged.connect(self.mainDialogRef. - setModified) - -- self.heightBox = QtGui.QGroupBox(_('Editor Height')) -+ self.heightBox = QtWidgets.QGroupBox(_('Editor Height')) - topLayout.addWidget(self.heightBox, 3, 1) -- heightLayout = QtGui.QHBoxLayout(self.heightBox) -- heightLabel = QtGui.QLabel(_('Num&ber of text lines')) -+ heightLayout = QtWidgets.QHBoxLayout(self.heightBox) -+ heightLabel = QtWidgets.QLabel(_('Num&ber of text lines')) - heightLayout.addWidget(heightLabel) -- self.heightCtrl = QtGui.QSpinBox() -+ self.heightCtrl = QtWidgets.QSpinBox() - heightLayout.addWidget(self.heightCtrl) - self.heightCtrl.setMinimum(1) - self.heightCtrl.setMaximum(999) - heightLabel.setBuddy(self.heightCtrl) - self.heightCtrl.valueChanged.connect(self.mainDialogRef.setModified) - -- self.equationBox = QtGui.QGroupBox(_('Math Equation')) -+ self.equationBox = QtWidgets.QGroupBox(_('Math Equation')) - topLayout.addWidget(self.equationBox, 4, 0, 1, 2) -- equationLayout = QtGui.QHBoxLayout(self.equationBox) -- self.equationViewer = QtGui.QLineEdit() -+ equationLayout = QtWidgets.QHBoxLayout(self.equationBox) -+ self.equationViewer = QtWidgets.QLineEdit() - equationLayout.addWidget(self.equationViewer) - self.equationViewer.setReadOnly(True) -- equationButton = QtGui.QPushButton(_('Define Equation')) -+ equationButton = QtWidgets.QPushButton(_('Define Equation')) - equationLayout.addWidget(equationButton) - equationButton.clicked.connect(self.defineMathEquation) - -@@ -1055,7 +1055,7 @@ class FieldConfigPage(ConfigPage): - self.heightCtrl.blockSignals(False) - self.heightBox.setEnabled(not self.currentFileInfoField and - issubclass(currentField.editorClass, -- QtGui.QTextEdit)) -+ QtWidgets.QTextEdit)) - - if currentField.typeName == 'Math': - self.equationBox.show() -@@ -1128,7 +1128,7 @@ class FieldConfigPage(ConfigPage): - prevEqnText = currentField.equationText() - prevResultType = currentField.resultType - dlg = MathEquationDialog(currentFormat, currentField, self) -- if (dlg.exec_() == QtGui.QDialog.Accepted and -+ if (dlg.exec_() == QtWidgets.QDialog.Accepted and - (currentField.equationText() != prevEqnText or - currentField.resultType != prevResultType)): - self.mainDialogRef.setModified() -@@ -1138,7 +1138,7 @@ class FieldConfigPage(ConfigPage): - """Provide a format help menu based on a button signal. - """ - currentFormat, currentField = self.currentFormatAndField() -- menu = QtGui.QMenu(self) -+ menu = QtWidgets.QMenu(self) - self.formatHelpDict = {} - for descript, key in currentField.getFormatHelpMenuList(): - if descript: -@@ -1204,64 +1204,64 @@ class OutputPage(ConfigPage): - self.refLevelFlag = '' - self.refLevelType = None - -- topLayout = QtGui.QGridLayout(self) -- typeBox = QtGui.QGroupBox(_('&Data Type')) -+ topLayout = QtWidgets.QGridLayout(self) -+ typeBox = QtWidgets.QGroupBox(_('&Data Type')) - topLayout.addWidget(typeBox, 0, 0) -- typeLayout = QtGui.QVBoxLayout(typeBox) -- self.typeCombo = QtGui.QComboBox() -+ typeLayout = QtWidgets.QVBoxLayout(typeBox) -+ self.typeCombo = QtWidgets.QComboBox() - typeLayout.addWidget(self.typeCombo) - self.typeCombo.currentIndexChanged[str].connect(self.changeCurrentType) - -- fieldBox = QtGui.QGroupBox(_('F&ield List')) -+ fieldBox = QtWidgets.QGroupBox(_('F&ield List')) - topLayout.addWidget(fieldBox, 1, 0, 2, 1) -- boxLayout = QtGui.QVBoxLayout(fieldBox) -- self.fieldListBox = QtGui.QTreeWidget() -+ boxLayout = QtWidgets.QVBoxLayout(fieldBox) -+ self.fieldListBox = QtWidgets.QTreeWidget() - boxLayout.addWidget(self.fieldListBox) - self.fieldListBox.setRootIsDecorated(False) - self.fieldListBox.setColumnCount(2) - self.fieldListBox.setHeaderLabels([_('Name'), _('Type')]) - self.fieldListBox.currentItemChanged.connect(self.changeField) - -- titleButtonLayout = QtGui.QVBoxLayout() -+ titleButtonLayout = QtWidgets.QVBoxLayout() - topLayout.addLayout(titleButtonLayout, 1, 1) -- self.toTitleButton = QtGui.QPushButton('>>') -+ self.toTitleButton = QtWidgets.QPushButton('>>') - titleButtonLayout.addWidget(self.toTitleButton) - self.toTitleButton.setMaximumWidth(self.toTitleButton. - sizeHint().height()) - self.toTitleButton.clicked.connect(self.fieldToTitle) -- self.delTitleButton = QtGui.QPushButton('<<') -+ self.delTitleButton = QtWidgets.QPushButton('<<') - titleButtonLayout.addWidget(self.delTitleButton) - self.delTitleButton.setMaximumWidth(self.delTitleButton. - sizeHint().height()) - self.delTitleButton.clicked.connect(self.delTitleField) - -- titleBox = QtGui.QGroupBox(_('&Title Format')) -+ titleBox = QtWidgets.QGroupBox(_('&Title Format')) - topLayout.addWidget(titleBox, 1, 2) -- titleLayout = QtGui.QVBoxLayout(titleBox) -+ titleLayout = QtWidgets.QVBoxLayout(titleBox) - self.titleEdit = TitleEdit() - titleLayout.addWidget(self.titleEdit) - self.titleEdit.cursorPositionChanged.connect(self. - setControlAvailability) - self.titleEdit.textEdited.connect(self.mainDialogRef.setModified) - -- outputButtonLayout = QtGui.QVBoxLayout() -+ outputButtonLayout = QtWidgets.QVBoxLayout() - topLayout.addLayout(outputButtonLayout, 2, 1) -- self.toOutputButton = QtGui.QPushButton('>>') -+ self.toOutputButton = QtWidgets.QPushButton('>>') - outputButtonLayout.addWidget(self.toOutputButton) - self.toOutputButton.setMaximumWidth(self.toOutputButton. - sizeHint().height()) - self.toOutputButton.clicked.connect(self.fieldToOutput) -- self.delOutputButton = QtGui.QPushButton('<<') -+ self.delOutputButton = QtWidgets.QPushButton('<<') - outputButtonLayout.addWidget(self.delOutputButton) - self.delOutputButton.setMaximumWidth(self.delOutputButton. - sizeHint().height()) - self.delOutputButton.clicked.connect(self.delOutputField) - -- outputBox = QtGui.QGroupBox(_('Out&put Format')) -+ outputBox = QtWidgets.QGroupBox(_('Out&put Format')) - topLayout.addWidget(outputBox, 2, 2) -- outputLayout = QtGui.QVBoxLayout(outputBox) -- self.outputEdit = QtGui.QTextEdit() -- self.outputEdit.setLineWrapMode(QtGui.QTextEdit.NoWrap) -+ outputLayout = QtWidgets.QVBoxLayout(outputBox) -+ self.outputEdit = QtWidgets.QTextEdit() -+ self.outputEdit.setLineWrapMode(QtWidgets.QTextEdit.NoWrap) - outputLayout.addWidget(self.outputEdit) - self.outputEdit.setTabChangesFocus(True) - self.outputEdit.cursorPositionChanged.connect(self. -@@ -1269,26 +1269,26 @@ class OutputPage(ConfigPage): - self.outputEdit.textChanged.connect(self.mainDialogRef.setModified) - - # advanced widgets -- otherBox = QtGui.QGroupBox(_('Other Field References')) -+ otherBox = QtWidgets.QGroupBox(_('Other Field References')) - topLayout.addWidget(otherBox, 0, 2) - self.advancedWidgets.append(otherBox) -- otherLayout = QtGui.QHBoxLayout(otherBox) -- levelLayout = QtGui.QVBoxLayout() -+ otherLayout = QtWidgets.QHBoxLayout(otherBox) -+ levelLayout = QtWidgets.QVBoxLayout() - otherLayout.addLayout(levelLayout) - levelLayout.setSpacing(0) -- levelLabel = QtGui.QLabel(_('Reference Le&vel')) -+ levelLabel = QtWidgets.QLabel(_('Reference Le&vel')) - levelLayout.addWidget(levelLabel) -- levelCombo = QtGui.QComboBox() -+ levelCombo = QtWidgets.QComboBox() - levelLayout.addWidget(levelCombo) - levelLabel.setBuddy(levelCombo) - levelCombo.addItems(_refLevelList) - levelCombo.currentIndexChanged.connect(self.changeRefLevel) -- refTypeLayout = QtGui.QVBoxLayout() -+ refTypeLayout = QtWidgets.QVBoxLayout() - otherLayout.addLayout(refTypeLayout) - refTypeLayout.setSpacing(0) -- refTypeLabel = QtGui.QLabel(_('Refere&nce Type')) -+ refTypeLabel = QtWidgets.QLabel(_('Refere&nce Type')) - refTypeLayout.addWidget(refTypeLabel) -- self.refTypeCombo = QtGui.QComboBox() -+ self.refTypeCombo = QtWidgets.QComboBox() - refTypeLayout.addWidget(self.refTypeCombo) - refTypeLabel.setBuddy(self.refTypeCombo) - self.refTypeCombo.currentIndexChanged.connect(self.changeRefType) -@@ -1355,10 +1355,10 @@ class OutputPage(ConfigPage): - self.fieldListBox.clear() - for field in activeFormat.fields(): - if field.showInDialog: -- QtGui.QTreeWidgetItem(self.fieldListBox, -+ QtWidgets.QTreeWidgetItem(self.fieldListBox, - [field.name, field.typeName]) - if self.refLevelFlag == '!': -- QtGui.QTreeWidgetItem(self.fieldListBox, -+ QtWidgets.QTreeWidgetItem(self.fieldListBox, - [nodeformat.uniqueIdFieldName, 'Text']) - selectList = self.fieldListBox.findItems(ConfigDialog.currentFieldName, - QtCore.Qt.MatchFixedString | -@@ -1366,7 +1366,7 @@ class OutputPage(ConfigPage): - selectItem = (selectList[0] if selectList else - self.fieldListBox.topLevelItem(0)) - self.fieldListBox.setCurrentItem(selectItem) -- self.fieldListBox.setItemSelected(selectItem, True) -+ selectItem.setSelected(True) - self.fieldListBox.setColumnWidth(0, self.fieldListBox.width() // 2) - self.fieldListBox.blockSignals(False) - -@@ -1533,10 +1533,10 @@ class OutputPage(ConfigPage): - not currentFormat.formatHtml) - - --class TitleEdit(QtGui.QLineEdit): -+class TitleEdit(QtWidgets.QLineEdit): - """LineEdit that avoids changing the selection on focus changes. - """ -- focusIn = QtCore.pyqtSignal(QtGui.QWidget) -+ focusIn = QtCore.pyqtSignal(QtWidgets.QWidget) - def __init__(self, parent=None): - """Initialize the config dialog page. - -@@ -1579,7 +1579,7 @@ class TitleEdit(QtGui.QLineEdit): - - _illegalRe = re.compile(r'[^\w_\-.]') - --class NameEntryDialog(QtGui.QDialog): -+class NameEntryDialog(QtWidgets.QDialog): - """Dialog to handle user entry of a type or field name. - - Restricts entry to alpha-numerics, underscores, dashes and periods. -@@ -1604,28 +1604,28 @@ class NameEntryDialog(QtGui.QDialog): - self.setWindowFlags(QtCore.Qt.Dialog | QtCore.Qt.WindowTitleHint | - QtCore.Qt.WindowSystemMenuHint) - self.setWindowTitle(caption) -- topLayout = QtGui.QVBoxLayout(self) -- label = QtGui.QLabel(labelText) -+ topLayout = QtWidgets.QVBoxLayout(self) -+ label = QtWidgets.QLabel(labelText) - topLayout.addWidget(label) -- self.entry = QtGui.QLineEdit(defaultText) -+ self.entry = QtWidgets.QLineEdit(defaultText) - topLayout.addWidget(self.entry) - self.entry.setFocus() - self.entry.returnPressed.connect(self.accept) - - self.extraChecked = False - if addCheckBox: -- self.extraCheckBox = QtGui.QCheckBox(addCheckBox) -+ self.extraCheckBox = QtWidgets.QCheckBox(addCheckBox) - topLayout.addWidget(self.extraCheckBox) - else: - self.extraCheckBox = None - -- ctrlLayout = QtGui.QHBoxLayout() -+ ctrlLayout = QtWidgets.QHBoxLayout() - topLayout.addLayout(ctrlLayout) - ctrlLayout.addStretch() -- okButton = QtGui.QPushButton(_('&OK')) -+ okButton = QtWidgets.QPushButton(_('&OK')) - ctrlLayout.addWidget(okButton) - okButton.clicked.connect(self.accept) -- cancelButton = QtGui.QPushButton(_('&Cancel')) -+ cancelButton = QtWidgets.QPushButton(_('&Cancel')) - ctrlLayout.addWidget(cancelButton) - cancelButton.clicked.connect(self.reject) - -@@ -1651,10 +1651,10 @@ class NameEntryDialog(QtGui.QDialog): - if self.extraCheckBox: - self.extraChecked = self.extraCheckBox.isChecked() - return super().accept() -- QtGui.QMessageBox.warning(self, 'TreeLine', error) -+ QtWidgets.QMessageBox.warning(self, 'TreeLine', error) - - --class IconSelectDialog(QtGui.QDialog): -+class IconSelectDialog(QtWidgets.QDialog): - """Dialog for selecting icons for a format type. - """ - dialogSize = () -@@ -1674,26 +1674,26 @@ class IconSelectDialog(QtGui.QDialog): - self.setWindowFlags(QtCore.Qt.Dialog | QtCore.Qt.WindowTitleHint | - QtCore.Qt.WindowSystemMenuHint) - self.setWindowTitle(_('Set Data Type Icon')) -- topLayout = QtGui.QVBoxLayout(self) -- self.iconView = QtGui.QListWidget() -- self.iconView.setViewMode(QtGui.QListView.ListMode) -- self.iconView.setMovement(QtGui.QListView.Static) -- self.iconView.setResizeMode(QtGui.QListView.Adjust) -+ topLayout = QtWidgets.QVBoxLayout(self) -+ self.iconView = QtWidgets.QListWidget() -+ self.iconView.setViewMode(QtWidgets.QListView.ListMode) -+ self.iconView.setMovement(QtWidgets.QListView.Static) -+ self.iconView.setSectionResizeMode(QtWidgets.QListView.Adjust) - self.iconView.setWrapping(True) - self.iconView.setGridSize(QtCore.QSize(112, 32)) - topLayout.addWidget(self.iconView) - self.iconView.itemDoubleClicked.connect(self.accept) - -- ctrlLayout = QtGui.QHBoxLayout() -+ ctrlLayout = QtWidgets.QHBoxLayout() - topLayout.addLayout(ctrlLayout) - ctrlLayout.addStretch() -- clearButton = QtGui.QPushButton(_('Clear &Select')) -+ clearButton = QtWidgets.QPushButton(_('Clear &Select')) - ctrlLayout.addWidget(clearButton) - clearButton.clicked.connect(self.iconView.clearSelection) -- okButton = QtGui.QPushButton(_('&OK')) -+ okButton = QtWidgets.QPushButton(_('&OK')) - ctrlLayout.addWidget(okButton) - okButton.clicked.connect(self.accept) -- cancelButton = QtGui.QPushButton(_('&Cancel')) -+ cancelButton = QtWidgets.QPushButton(_('&Cancel')) - ctrlLayout.addWidget(cancelButton) - cancelButton.clicked.connect(self.reject) - if IconSelectDialog.dialogSize: -@@ -1710,14 +1710,14 @@ class IconSelectDialog(QtGui.QDialog): - globalref.treeIcons.loadAllIcons() - for name, icon in globalref.treeIcons.items(): - if icon: -- item = QtGui.QListWidgetItem(icon, name, self.iconView) -+ item = QtWidgets.QListWidgetItem(icon, name, self.iconView) - if name == self.currentIconName: - self.iconView.setCurrentItem(item) - self.iconView.sortItems() - selectedItem = self.iconView.currentItem() - if selectedItem: - self.iconView.scrollToItem(selectedItem, -- QtGui.QAbstractItemView.PositionAtCenter) -+ QtWidgets.QAbstractItemView.PositionAtCenter) - - def saveSize(self): - """Record dialog size at close. -@@ -1745,7 +1745,7 @@ class IconSelectDialog(QtGui.QDialog): - super().reject() - - --class SortKeyDialog(QtGui.QDialog): -+class SortKeyDialog(QtWidgets.QDialog): - """Dialog for defining sort key fields and directions. - """ - directionNameDict = {True: _('forward'), False: _('reverse')} -@@ -1764,60 +1764,60 @@ class SortKeyDialog(QtGui.QDialog): - self.setWindowFlags(QtCore.Qt.Dialog | QtCore.Qt.WindowTitleHint | - QtCore.Qt.WindowSystemMenuHint) - self.setWindowTitle(_('Sort Key Fields')) -- topLayout = QtGui.QVBoxLayout(self) -- horizLayout = QtGui.QHBoxLayout() -+ topLayout = QtWidgets.QVBoxLayout(self) -+ horizLayout = QtWidgets.QHBoxLayout() - topLayout.addLayout(horizLayout) -- fieldBox = QtGui.QGroupBox(_('Available &Fields')) -+ fieldBox = QtWidgets.QGroupBox(_('Available &Fields')) - horizLayout.addWidget(fieldBox) -- boxLayout = QtGui.QVBoxLayout(fieldBox) -- self.fieldListBox = QtGui.QTreeWidget() -+ boxLayout = QtWidgets.QVBoxLayout(fieldBox) -+ self.fieldListBox = QtWidgets.QTreeWidget() - boxLayout.addWidget(self.fieldListBox) - self.fieldListBox.setRootIsDecorated(False) - self.fieldListBox.setColumnCount(2) - self.fieldListBox.setHeaderLabels([_('Name'), _('Type')]) - -- midButtonLayout = QtGui.QVBoxLayout() -+ midButtonLayout = QtWidgets.QVBoxLayout() - horizLayout.addLayout(midButtonLayout) -- self.addFieldButton = QtGui.QPushButton('>>') -+ self.addFieldButton = QtWidgets.QPushButton('>>') - midButtonLayout.addWidget(self.addFieldButton) - self.addFieldButton.setMaximumWidth(self.addFieldButton. - sizeHint().height()) - self.addFieldButton.clicked.connect(self.addField) -- self.removeFieldButton = QtGui.QPushButton('<<') -+ self.removeFieldButton = QtWidgets.QPushButton('<<') - midButtonLayout.addWidget(self.removeFieldButton) - self.removeFieldButton.setMaximumWidth(self.removeFieldButton. - sizeHint().height()) - self.removeFieldButton.clicked.connect(self.removeField) - -- sortBox = QtGui.QGroupBox(_('&Sort Criteria')) -+ sortBox = QtWidgets.QGroupBox(_('&Sort Criteria')) - horizLayout.addWidget(sortBox) -- boxLayout = QtGui.QVBoxLayout(sortBox) -- self.sortListBox = QtGui.QTreeWidget() -+ boxLayout = QtWidgets.QVBoxLayout(sortBox) -+ self.sortListBox = QtWidgets.QTreeWidget() - boxLayout.addWidget(self.sortListBox) - self.sortListBox.setRootIsDecorated(False) - self.sortListBox.setColumnCount(3) - self.sortListBox.setHeaderLabels(['#', _('Field'), _('Direction')]) - self.sortListBox.currentItemChanged.connect(self.setControlsAvail) - -- rightButtonLayout = QtGui.QVBoxLayout() -+ rightButtonLayout = QtWidgets.QVBoxLayout() - horizLayout.addLayout(rightButtonLayout) -- self.upButton = QtGui.QPushButton(_('Move &Up')) -+ self.upButton = QtWidgets.QPushButton(_('Move &Up')) - rightButtonLayout.addWidget(self.upButton) - self.upButton.clicked.connect(self.moveUp) -- self.downButton = QtGui.QPushButton(_('&Move Down')) -+ self.downButton = QtWidgets.QPushButton(_('&Move Down')) - rightButtonLayout.addWidget(self.downButton) - self.downButton.clicked.connect(self.moveDown) -- self.flipButton = QtGui.QPushButton(_('Flip &Direction')) -+ self.flipButton = QtWidgets.QPushButton(_('Flip &Direction')) - rightButtonLayout.addWidget(self.flipButton) - self.flipButton.clicked.connect(self.flipDirection) - -- ctrlLayout = QtGui.QHBoxLayout() -+ ctrlLayout = QtWidgets.QHBoxLayout() - topLayout.addLayout(ctrlLayout) - ctrlLayout.addStretch() -- self.okButton = QtGui.QPushButton(_('&OK')) -+ self.okButton = QtWidgets.QPushButton(_('&OK')) - ctrlLayout.addWidget(self.okButton) - self.okButton.clicked.connect(self.accept) -- cancelButton = QtGui.QPushButton(_('&Cancel')) -+ cancelButton = QtWidgets.QPushButton(_('&Cancel')) - ctrlLayout.addWidget(cancelButton) - cancelButton.clicked.connect(self.reject) - self.updateContent() -@@ -1833,7 +1833,7 @@ class SortKeyDialog(QtGui.QDialog): - self.fieldListBox.clear() - for field in self.fieldDict.values(): - if field not in sortFields: -- QtGui.QTreeWidgetItem(self.fieldListBox, -+ QtWidgets.QTreeWidgetItem(self.fieldListBox, - [field.name, field.typeName]) - if self.fieldListBox.topLevelItemCount() > 0: - self.fieldListBox.setCurrentItem(self.fieldListBox.topLevelItem(0)) -@@ -1843,7 +1843,7 @@ class SortKeyDialog(QtGui.QDialog): - self.sortListBox.clear() - for num, field in enumerate(sortFields, 1): - sortDir = SortKeyDialog.directionNameDict[field.sortKeyForward] -- QtGui.QTreeWidgetItem(self.sortListBox, -+ QtWidgets.QTreeWidgetItem(self.sortListBox, - [repr(num), field.name, sortDir]) - self.sortListBox.setCurrentItem(self.sortListBox.topLevelItem(0)) - self.sortListBox.blockSignals(False) -@@ -1877,7 +1877,7 @@ class SortKeyDialog(QtGui.QDialog): - sortNum = self.sortListBox.topLevelItemCount() + 1 - sortDir = SortKeyDialog.directionNameDict[field.sortKeyForward] - self.sortListBox.blockSignals(True) -- sortItem = QtGui.QTreeWidgetItem(self.sortListBox, -+ sortItem = QtWidgets.QTreeWidgetItem(self.sortListBox, - [repr(sortNum), fieldName, sortDir]) - self.sortListBox.setCurrentItem(sortItem) - self.sortListBox.blockSignals(False) -@@ -1900,7 +1900,7 @@ class SortKeyDialog(QtGui.QDialog): - fieldList = [field for field in self.fieldDict.values() if - field.name not in sortFieldNames] - pos = fieldList.index(field) -- fieldItem = QtGui.QTreeWidgetItem([fieldName, field.typeName]) -+ fieldItem = QtWidgets.QTreeWidgetItem([fieldName, field.typeName]) - self.fieldListBox.insertTopLevelItem(pos, fieldItem) - self.setControlsAvail() - self.numChanges += 1 -@@ -2028,7 +2028,7 @@ _textOperators = [('+', _('concatenate t - # _operatorLists correspond to _operatorTypes - _operatorLists = [_arithmeticOperators, _comparisonOperators, _textOperators] - --class MathEquationDialog(QtGui.QDialog): -+class MathEquationDialog(QtWidgets.QDialog): - """Dialog for defining equations for Math fields. - """ - def __init__(self, nodeFormat, field, parent=None): -@@ -2047,71 +2047,71 @@ class MathEquationDialog(QtGui.QDialog): - QtCore.Qt.WindowSystemMenuHint) - self.setWindowTitle(_('Define Math Field Equation')) - -- topLayout = QtGui.QGridLayout(self) -- fieldBox = QtGui.QGroupBox(_('Field References')) -+ topLayout = QtWidgets.QGridLayout(self) -+ fieldBox = QtWidgets.QGroupBox(_('Field References')) - topLayout.addWidget(fieldBox, 0, 0, 2, 1) -- fieldLayout = QtGui.QVBoxLayout(fieldBox) -- innerLayout = QtGui.QVBoxLayout() -+ fieldLayout = QtWidgets.QVBoxLayout(fieldBox) -+ innerLayout = QtWidgets.QVBoxLayout() - innerLayout.setSpacing(0) - fieldLayout.addLayout(innerLayout) -- levelLabel = QtGui.QLabel(_('Reference &Level')) -+ levelLabel = QtWidgets.QLabel(_('Reference &Level')) - innerLayout.addWidget(levelLabel) -- levelCombo = QtGui.QComboBox() -+ levelCombo = QtWidgets.QComboBox() - innerLayout.addWidget(levelCombo) - levelLabel.setBuddy(levelCombo) - levelCombo.addItems(_mathRefLevels) - levelCombo.currentIndexChanged.connect(self.changeRefLevel) -- innerLayout = QtGui.QVBoxLayout() -+ innerLayout = QtWidgets.QVBoxLayout() - innerLayout.setSpacing(0) - fieldLayout.addLayout(innerLayout) -- typeLabel = QtGui.QLabel(_('Reference &Type')) -+ typeLabel = QtWidgets.QLabel(_('Reference &Type')) - innerLayout.addWidget(typeLabel) -- self.typeCombo = QtGui.QComboBox() -+ self.typeCombo = QtWidgets.QComboBox() - innerLayout.addWidget(self.typeCombo) - typeLabel.setBuddy(self.typeCombo) - self.typeCombo.addItems(self.typeFormats.typeNames()) - self.typeCombo.currentIndexChanged.connect(self.updateFieldList) -- innerLayout = QtGui.QVBoxLayout() -+ innerLayout = QtWidgets.QVBoxLayout() - innerLayout.setSpacing(0) - fieldLayout.addLayout(innerLayout) -- fieldLabel = QtGui.QLabel(_('Available &Field List')) -+ fieldLabel = QtWidgets.QLabel(_('Available &Field List')) - innerLayout.addWidget(fieldLabel) -- self.fieldListBox = QtGui.QTreeWidget() -+ self.fieldListBox = QtWidgets.QTreeWidget() - innerLayout.addWidget(self.fieldListBox) - fieldLabel.setBuddy(self.fieldListBox) - self.fieldListBox.setRootIsDecorated(False) - self.fieldListBox.setColumnCount(2) - self.fieldListBox.setHeaderLabels([_('Name'), _('Type')]) - -- resultTypeBox = QtGui.QGroupBox(_('&Result Type')) -+ resultTypeBox = QtWidgets.QGroupBox(_('&Result Type')) - topLayout.addWidget(resultTypeBox, 0, 1) -- resultTypeLayout = QtGui.QVBoxLayout(resultTypeBox) -- self.resultTypeCombo = QtGui.QComboBox() -+ resultTypeLayout = QtWidgets.QVBoxLayout(resultTypeBox) -+ self.resultTypeCombo = QtWidgets.QComboBox() - resultTypeLayout.addWidget(self.resultTypeCombo) - self.resultTypeCombo.addItems([_(str) for str in _mathResultTypes]) - results = [s.split(' ', 1)[0].lower() for s in _mathResultTypes] - resultStr = fieldformat.mathResultStr[self.field.resultType] - self.resultTypeCombo.setCurrentIndex(results.index(resultStr)) - -- operBox = QtGui.QGroupBox(_('Operations')) -+ operBox = QtWidgets.QGroupBox(_('Operations')) - topLayout.addWidget(operBox, 1, 1) -- operLayout = QtGui.QVBoxLayout(operBox) -- innerLayout = QtGui.QVBoxLayout() -+ operLayout = QtWidgets.QVBoxLayout(operBox) -+ innerLayout = QtWidgets.QVBoxLayout() - innerLayout.setSpacing(0) - operLayout.addLayout(innerLayout) -- operTypeLabel = QtGui.QLabel(_('O&perator Type')) -+ operTypeLabel = QtWidgets.QLabel(_('O&perator Type')) - innerLayout.addWidget(operTypeLabel) -- operTypeCombo = QtGui.QComboBox() -+ operTypeCombo = QtWidgets.QComboBox() - innerLayout.addWidget(operTypeCombo) - operTypeLabel.setBuddy(operTypeCombo) - operTypeCombo.addItems(_operatorTypes) - operTypeCombo.currentIndexChanged.connect(self.replaceOperatorList) -- innerLayout = QtGui.QVBoxLayout() -+ innerLayout = QtWidgets.QVBoxLayout() - innerLayout.setSpacing(0) - operLayout.addLayout(innerLayout) -- operListLabel = QtGui.QLabel(_('Oper&ator List')) -+ operListLabel = QtWidgets.QLabel(_('Oper&ator List')) - innerLayout.addWidget(operListLabel) -- self.operListBox = QtGui.QTreeWidget() -+ self.operListBox = QtWidgets.QTreeWidget() - innerLayout.addWidget(self.operListBox) - operListLabel.setBuddy(self.operListBox) - self.operListBox.setRootIsDecorated(False) -@@ -2119,46 +2119,46 @@ class MathEquationDialog(QtGui.QDialog): - self.operListBox.setHeaderLabels([_('Name'), _('Description')]) - self.replaceOperatorList(0) - -- buttonLayout = QtGui.QHBoxLayout() -+ buttonLayout = QtWidgets.QHBoxLayout() - topLayout.addLayout(buttonLayout, 2, 0) - buttonLayout.addStretch() -- self.addFieldButton = QtGui.QPushButton('\u25bc') -+ self.addFieldButton = QtWidgets.QPushButton('\u25bc') - buttonLayout.addWidget(self.addFieldButton) - self.addFieldButton.setMaximumWidth(self.addFieldButton. - sizeHint().height()) - self.addFieldButton.clicked.connect(self.addField) -- self.delFieldButton = QtGui.QPushButton('\u25b2') -+ self.delFieldButton = QtWidgets.QPushButton('\u25b2') - buttonLayout.addWidget(self.delFieldButton) - self.delFieldButton.setMaximumWidth(self.delFieldButton. - sizeHint().height()) - self.delFieldButton.clicked.connect(self.deleteField) - buttonLayout.addStretch() - -- buttonLayout = QtGui.QHBoxLayout() -+ buttonLayout = QtWidgets.QHBoxLayout() - topLayout.addLayout(buttonLayout, 2, 1) -- self.addOperButton = QtGui.QPushButton('\u25bc') -+ self.addOperButton = QtWidgets.QPushButton('\u25bc') - buttonLayout.addWidget(self.addOperButton) - self.addOperButton.setMaximumWidth(self.addOperButton. - sizeHint().height()) - self.addOperButton.clicked.connect(self.addOperator) - -- equationBox = QtGui.QGroupBox(_('&Equation')) -+ equationBox = QtWidgets.QGroupBox(_('&Equation')) - topLayout.addWidget(equationBox, 3, 0, 1, 2) -- equationLayout = QtGui.QVBoxLayout(equationBox) -+ equationLayout = QtWidgets.QVBoxLayout(equationBox) - self.equationEdit = TitleEdit() - equationLayout.addWidget(self.equationEdit) - self.equationEdit.setText(self.field.equationText()) - self.equationEdit.cursorPositionChanged.connect(self. - setControlAvailability) - -- ctrlLayout = QtGui.QHBoxLayout() -+ ctrlLayout = QtWidgets.QHBoxLayout() - topLayout.addLayout(ctrlLayout, 4, 0, 1, 2) - ctrlLayout.addStretch() -- okButton = QtGui.QPushButton(_('&OK')) -+ okButton = QtWidgets.QPushButton(_('&OK')) - ctrlLayout.addWidget(okButton) - okButton.setDefault(True) - okButton.clicked.connect(self.accept) -- cancelButton = QtGui.QPushButton(_('&Cancel')) -+ cancelButton = QtWidgets.QPushButton(_('&Cancel')) - ctrlLayout.addWidget(cancelButton) - cancelButton.clicked.connect(self.reject) - self.changeRefLevel(0) -@@ -2173,15 +2173,15 @@ class MathEquationDialog(QtGui.QDialog): - for field in currentFormat.fields(): - if (hasattr(field, 'mathValue') and field.showInDialog - and (self.refLevelFlag or field != self.field)): -- QtGui.QTreeWidgetItem(self.fieldListBox, -+ QtWidgets.QTreeWidgetItem(self.fieldListBox, - [field.name, _(field.typeName)]) - else: -- QtGui.QTreeWidgetItem(self.fieldListBox, -+ QtWidgets.QTreeWidgetItem(self.fieldListBox, - ['Count', 'Number of Children']) - if self.fieldListBox.topLevelItemCount(): - selectItem = self.fieldListBox.topLevelItem(0) - self.fieldListBox.setCurrentItem(selectItem) -- self.fieldListBox.setItemSelected(selectItem, True) -+ selectItem.setSelected(True) - self.fieldListBox.resizeColumnToContents(0) - self.fieldListBox.setColumnWidth(0, - self.fieldListBox.columnWidth(0) * 2) -@@ -2281,14 +2281,14 @@ class MathEquationDialog(QtGui.QDialog): - """ - self.operListBox.clear() - for oper, descr in _operatorLists[num]: -- QtGui.QTreeWidgetItem(self.operListBox, [oper, descr]) -+ QtWidgets.QTreeWidgetItem(self.operListBox, [oper, descr]) - self.operListBox.resizeColumnToContents(0) - self.operListBox.setColumnWidth(0, - self.operListBox.columnWidth(0) * 1.2) - self.operListBox.resizeColumnToContents(1) - selectItem = self.operListBox.topLevelItem(0) - self.operListBox.setCurrentItem(selectItem) -- self.operListBox.setItemSelected(selectItem, True) -+ selectItem.setSelected(True) - - def accept(self): - """Verify the equation and close the dialog if acceptable. -@@ -2299,7 +2299,7 @@ class MathEquationDialog(QtGui.QDialog): - try: - eqn.validate() - except ValueError as err: -- QtGui.QMessageBox.warning(self, 'TreeLine', -+ QtWidgets.QMessageBox.warning(self, 'TreeLine', - _('Equation error: {}').format(err)) - return - self.typeFormats.emptiedMathDict.setdefault(self.nodeFormat.name, -Index: TreeLine/source/dataeditors.py -=================================================================== ---- TreeLine.orig/source/dataeditors.py -+++ TreeLine/source/dataeditors.py -@@ -16,7 +16,7 @@ import xml.sax.saxutils - import os.path - import sys - import subprocess --from PyQt4 import QtCore, QtGui -+from PyQt5 import QtCore, QtGui, QtWidgets - import dataeditview - import fieldformat - import urltools -@@ -24,11 +24,11 @@ import globalref - import optiondefaults - - --class PlainTextEditor(QtGui.QTextEdit): -+class PlainTextEditor(QtWidgets.QTextEdit): - """An editor widget for multi-line plain text fields. - """ - dragLinkEnabled = False -- contentsChanged = QtCore.pyqtSignal(QtGui.QWidget) -+ contentsChanged = QtCore.pyqtSignal(QtWidgets.QWidget) - def __init__(self, parent=None): - """Initialize the editor class. - -@@ -37,7 +37,7 @@ class PlainTextEditor(QtGui.QTextEdit): - """ - super().__init__(parent) - self.setAcceptRichText(False) -- self.setPalette(QtGui.QApplication.palette()) -+ self.setPalette(QtWidgets.QApplication.palette()) - self.setStyleSheet('QTextEdit {border: 2px solid palette(highlight)}') - self.setTabChangesFocus(True) - self.cursorPositionChanged.connect(self.updateActions) -@@ -90,7 +90,7 @@ class PlainTextEditor(QtGui.QTextEdit): - """ - self.allActions['EditCut'].setEnabled(True) - self.allActions['EditCopy'].setEnabled(True) -- mime = QtGui.QApplication.clipboard().mimeData() -+ mime = QtWidgets.QApplication.clipboard().mimeData() - self.allActions['EditPaste'].setEnabled(len(mime.data('text/xml') or - mime.data('text/plain')) - > 0) -@@ -101,7 +101,7 @@ class PlainTextEditor(QtGui.QTextEdit): - hasSelection = self.textCursor().hasSelection() - self.allActions['EditCut'].setEnabled(hasSelection) - self.allActions['EditCopy'].setEnabled(hasSelection) -- mime = QtGui.QApplication.clipboard().mimeData() -+ mime = QtWidgets.QApplication.clipboard().mimeData() - self.allActions['EditPaste'].setEnabled(len(mime.data('text/plain')) - > 0) - -@@ -111,7 +111,7 @@ class PlainTextEditor(QtGui.QTextEdit): - Arguments: - event -- the menu event - """ -- menu = QtGui.QMenu(self) -+ menu = QtWidgets.QMenu(self) - menu.addAction(self.allActions['EditSelectAll']) - menu.addSeparator() - menu.addAction(self.allActions['EditCut']) -@@ -252,7 +252,7 @@ class HtmlTextEditor(PlainTextEditor): - if self.hasFocus(): - charFormat = self.currentCharFormat() - oldColor = charFormat.foreground().color() -- newColor = QtGui.QColorDialog.getColor(oldColor, self) -+ newColor = QtWidgets.QColorDialog.getColor(oldColor, self) - if newColor.isValid(): - self.insertTagText(''. - format(newColor.name()), '') -@@ -269,7 +269,7 @@ class HtmlTextEditor(PlainTextEditor): - if address.startswith('#'): - address = name = '' - dialog.setFromComponents(address, name) -- if dialog.exec_() == QtGui.QDialog.Accepted: -+ if dialog.exec_() == QtWidgets.QDialog.Accepted: - self.insertPlainText(dialog.htmlText()) - except RuntimeError: - pass # avoid calling a deleted C++ editor object -@@ -298,7 +298,7 @@ class HtmlTextEditor(PlainTextEditor): - Arguments: - resultCode -- the result from the dialog (OK or cancel) - """ -- if resultCode == QtGui.QDialog.Accepted: -+ if resultCode == QtWidgets.QDialog.Accepted: - self.addedIntLinkFlag = True - self.insertPlainText(self.intLinkDialog.htmlText()) - self.intLinkDialog = None -@@ -391,7 +391,7 @@ class HtmlTextEditor(PlainTextEditor): - Arguments: - event -- the menu event - """ -- menu = QtGui.QMenu(self) -+ menu = QtWidgets.QMenu(self) - menu.addAction(self.allActions['EditBoldFont']) - menu.addAction(self.allActions['EditItalicFont']) - menu.addAction(self.allActions['EditUnderlineFont']) -@@ -571,7 +571,7 @@ class RichTextEditor(HtmlTextEditor): - if self.hasFocus(): - charFormat = self.currentCharFormat() - oldColor = charFormat.foreground().color() -- newColor = QtGui.QColorDialog.getColor(oldColor, self) -+ newColor = QtWidgets.QColorDialog.getColor(oldColor, self) - if newColor.isValid(): - charFormat.setForeground(QtGui.QBrush(newColor)) - self.setCurrentCharFormat(charFormat) -@@ -602,7 +602,7 @@ class RichTextEditor(HtmlTextEditor): - if address.startswith('#'): - address = name = '' - dialog.setFromComponents(address, name) -- if dialog.exec_() == QtGui.QDialog.Accepted: -+ if dialog.exec_() == QtWidgets.QDialog.Accepted: - if self.textCursor().hasSelection(): - self.insertHtml(dialog.htmlText()) - else: -@@ -616,7 +616,7 @@ class RichTextEditor(HtmlTextEditor): - Arguments: - resultCode -- the result from the dialog (OK or cancel) - """ -- if resultCode == QtGui.QDialog.Accepted: -+ if resultCode == QtWidgets.QDialog.Accepted: - self.addedIntLinkFlag = True - if self.textCursor().hasSelection(): - self.insertHtml(self.intLinkDialog.htmlText()) -@@ -682,7 +682,7 @@ class RichTextEditor(HtmlTextEditor): - def pastePlain(self): - """Paste non-formatted text from the clipboard. - """ -- text = QtGui.QApplication.clipboard().mimeData().text() -+ text = QtWidgets.QApplication.clipboard().mimeData().text() - if text and self.hasFocus(): - self.insertPlainText(text) - -@@ -709,7 +709,7 @@ class RichTextEditor(HtmlTextEditor): - sizeNum = 1 # default size - fontSizeSubMenu.actions()[sizeNum].setChecked(True) - self.allActions['EditClearFormat'].setEnabled(True) -- mime = QtGui.QApplication.clipboard().mimeData() -+ mime = QtWidgets.QApplication.clipboard().mimeData() - self.allActions['EditPastePlain'].setEnabled(len(mime. - data('text/plain')) - > 0) -@@ -720,7 +720,7 @@ class RichTextEditor(HtmlTextEditor): - Arguments: - event -- the menu event - """ -- menu = QtGui.QMenu(self) -+ menu = QtWidgets.QMenu(self) - menu.addAction(self.allActions['EditBoldFont']) - menu.addAction(self.allActions['EditItalicFont']) - menu.addAction(self.allActions['EditUnderlineFont']) -@@ -801,13 +801,13 @@ class OneLineTextEditor(RichTextEditor): - super().keyPressEvent(event) - - --class LineEditor(QtGui.QLineEdit): -+class LineEditor(QtWidgets.QLineEdit): - """An editor widget for unformatted single-line fields. - - Used both stand-alone and as part of the combo box editor. - """ - dragLinkEnabled = False -- contentsChanged = QtCore.pyqtSignal(QtGui.QWidget) -+ contentsChanged = QtCore.pyqtSignal(QtWidgets.QWidget) - contextMenuPrep = QtCore.pyqtSignal() - def __init__(self, parent=None, subControl=False): - """Initialize the editor class. -@@ -818,7 +818,7 @@ class LineEditor(QtGui.QLineEdit): - subcontrol -- true if used inside a combo box (no border or signal) - """ - super().__init__(parent) -- self.setPalette(QtGui.QApplication.palette()) -+ self.setPalette(QtWidgets.QApplication.palette()) - self.cursorPositionChanged.connect(self.updateActions) - self.selectionChanged.connect(self.updateActions) - try: -@@ -887,14 +887,14 @@ class LineEditor(QtGui.QLineEdit): - path.lineTo(0, 10) - path.lineTo(10, 0) - path.closeSubpath() -- painter.fillPath(path, QtGui.QApplication.palette().highlight()) -+ painter.fillPath(path, QtWidgets.QApplication.palette().highlight()) - - def disableActions(self): - """Reset action availability after focus is lost. - """ - self.allActions['EditCut'].setEnabled(True) - self.allActions['EditCopy'].setEnabled(True) -- mime = QtGui.QApplication.clipboard().mimeData() -+ mime = QtWidgets.QApplication.clipboard().mimeData() - self.allActions['EditPaste'].setEnabled(len(mime.data('text/xml') or - mime.data('text/plain')) - > 0) -@@ -905,7 +905,7 @@ class LineEditor(QtGui.QLineEdit): - hasSelection = self.hasSelectedText() - self.allActions['EditCut'].setEnabled(hasSelection) - self.allActions['EditCopy'].setEnabled(hasSelection) -- mime = QtGui.QApplication.clipboard().mimeData() -+ mime = QtWidgets.QApplication.clipboard().mimeData() - self.allActions['EditPaste'].setEnabled(len(mime.data('text/plain')) - > 0) - -@@ -916,7 +916,7 @@ class LineEditor(QtGui.QLineEdit): - event -- the menu event - """ - self.contextMenuPrep.emit() -- menu = QtGui.QMenu(self) -+ menu = QtWidgets.QMenu(self) - if self.extraMenuActions: - for action in self.extraMenuActions: - menu.addAction(action) -@@ -975,13 +975,13 @@ class ReadOnlyEditor(LineEditor): - self.setReadOnly(True) - - --class ComboEditor(QtGui.QComboBox): -+class ComboEditor(QtWidgets.QComboBox): - """A general combo box editor widget. - - Uses the LineEditor class to paint the error flag. - """ - dragLinkEnabled = False -- contentsChanged = QtCore.pyqtSignal(QtGui.QWidget) -+ contentsChanged = QtCore.pyqtSignal(QtWidgets.QWidget) - def __init__(self, parent=None): - """Initialize the editor class. - -@@ -990,16 +990,16 @@ class ComboEditor(QtGui.QComboBox): - parent -- the parent, if given - """ - super().__init__(parent) -- self.setPalette(QtGui.QApplication.palette()) -+ self.setPalette(QtWidgets.QApplication.palette()) - self.setStyleSheet('QComboBox {border: 2px solid palette(highlight)}') - self.setEditable(True) - self.setLineEdit(LineEditor(self, True)) -- self.listView = QtGui.QTreeWidget() -+ self.listView = QtWidgets.QTreeWidget() - self.listView.setColumnCount(2) - self.listView.header().hide() - self.listView.setRootIsDecorated(False) -- self.listView.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) -- self.listView.header().setResizeMode(QtGui.QHeaderView. -+ self.listView.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) -+ self.listView.header().setSectionResizeMode(QtWidgets.QHeaderView. - ResizeToContents) - self.setModel(self.listView.model()) - self.setView(self.listView) -@@ -1044,7 +1044,7 @@ class ComboEditor(QtGui.QComboBox): - else: - annotatedChoices = self.fieldRef.annotatedComboChoices(text) - for choice, annot in annotatedChoices: -- QtGui.QTreeWidgetItem(self.listView, [choice, annot]) -+ QtWidgets.QTreeWidgetItem(self.listView, [choice, annot]) - choices = [choice for (choice, annot) in annotatedChoices] - try: - self.setCurrentIndex(choices.index(text)) -@@ -1136,7 +1136,7 @@ class CombinationEditor(ComboEditor): - self.checkBoxDialog.show() - pos = self.mapToGlobal(self.rect().bottomRight()) - pos.setX(pos.x() - self.checkBoxDialog.width() + 1) -- screenBottom = (QtGui.QApplication.desktop().screenGeometry(self). -+ screenBottom = (QtWidgets.QApplication.desktop().screenGeometry(self). - bottom()) - if pos.y() + self.checkBoxDialog.height() > screenBottom: - pos.setY(pos.y() - self.rect().height() - -@@ -1158,7 +1158,7 @@ class CombinationEditor(ComboEditor): - selectList())) - - --class CombinationDialog(QtGui.QDialog): -+class CombinationDialog(QtWidgets.QDialog): - """A popup dialog box for combination and auto-combination fields. - """ - buttonChanged = QtCore.pyqtSignal() -@@ -1171,19 +1171,19 @@ class CombinationDialog(QtGui.QDialog): - """ - super().__init__(parent) - self.setWindowFlags(QtCore.Qt.Popup) -- topLayout = QtGui.QVBoxLayout(self) -+ topLayout = QtWidgets.QVBoxLayout(self) - topLayout.setContentsMargins(0, 0, 0, 0) -- scrollArea = QtGui.QScrollArea() -+ scrollArea = QtWidgets.QScrollArea() - scrollArea.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - topLayout.addWidget(scrollArea) -- innerWidget = QtGui.QWidget() -- innerLayout = QtGui.QVBoxLayout(innerWidget) -+ innerWidget = QtWidgets.QWidget() -+ innerLayout = QtWidgets.QVBoxLayout(innerWidget) - selected = set(selectList) -- self.buttonGroup = QtGui.QButtonGroup(self) -+ self.buttonGroup = QtWidgets.QButtonGroup(self) - self.buttonGroup.setExclusive(False) - self.buttonGroup.buttonClicked.connect(self.buttonChanged) - for text in choiceList: -- button = QtGui.QCheckBox(text, innerWidget) -+ button = QtWidgets.QCheckBox(text, innerWidget) - if text in selected: - button.setChecked(True) - self.buttonGroup.addButton(button) -@@ -1216,7 +1216,7 @@ class DateEditor(ComboEditor): - """ - super().__init__(parent) - self.calendar = None -- todayAction = QtGui.QAction(_('Today\'s &Date'), self) -+ todayAction = QtWidgets.QAction(_('Today\'s &Date'), self) - todayAction.triggered.connect(self.setToday) - self.lineEdit().extraMenuActions = [todayAction] - -@@ -1224,12 +1224,12 @@ class DateEditor(ComboEditor): - """Override to show a calendar widget in place of a list view. - """ - if not self.calendar: -- self.calendar = QtGui.QCalendarWidget(self) -+ self.calendar = QtWidgets.QCalendarWidget(self) - self.calendar.setWindowFlags(QtCore.Qt.Popup) - weekStart = optiondefaults.daysOfWeek.index(globalref.genOptions. - getValue('WeekStart')) - self.calendar.setFirstDayOfWeek(weekStart + 1) -- self.calendar.setVerticalHeaderFormat(QtGui.QCalendarWidget. -+ self.calendar.setVerticalHeaderFormat(QtWidgets.QCalendarWidget. - NoVerticalHeader) - self.calendar.clicked.connect(self.setDate) - editorFormat = globalref.genOptions.getValue('EditDateFormat') -@@ -1241,7 +1241,7 @@ class DateEditor(ComboEditor): - self.calendar.show() - pos = self.mapToGlobal(self.rect().bottomRight()) - pos.setX(pos.x() - self.calendar.width()) -- screenBottom = (QtGui.QApplication.desktop().screenGeometry(self). -+ screenBottom = (QtWidgets.QApplication.desktop().screenGeometry(self). - bottom()) - if pos.y() + self.calendar.height() > screenBottom: - pos.setY(pos.y() - self.rect().height() - self.calendar.height()) -@@ -1287,9 +1287,9 @@ class ExtLinkEditor(ComboEditor): - super().__init__(parent) - self.setAcceptDrops(True) - self.dialog = None -- openAction = QtGui.QAction(_('&Open Link'), self) -+ openAction = QtWidgets.QAction(_('&Open Link'), self) - openAction.triggered.connect(self.openLink) -- folderAction = QtGui.QAction(_('Open &Folder'), self) -+ folderAction = QtWidgets.QAction(_('Open &Folder'), self) - folderAction.triggered.connect(self.openFolder) - self.lineEdit().extraMenuActions = [openAction, folderAction] - self.lineEdit().contextMenuPrep.connect(self.updateActions) -@@ -1303,7 +1303,7 @@ class ExtLinkEditor(ComboEditor): - self.dialog.show() - pos = self.mapToGlobal(self.rect().bottomRight()) - pos.setX(pos.x() - self.dialog.width() + 1) -- screenBottom = (QtGui.QApplication.desktop().screenGeometry(self). -+ screenBottom = (QtWidgets.QApplication.desktop().screenGeometry(self). - bottom()) - if pos.y() + self.dialog.height() > screenBottom: - pos.setY(pos.y() - self.rect().height() - self.dialog.height()) -@@ -1402,7 +1402,7 @@ _extLinkSchemes = ('http://', 'https://' - _extLinkSchemeDict = {proto.split(':', 1)[0]: proto for proto in - _extLinkSchemes} - --class ExtLinkDialog(QtGui.QDialog): -+class ExtLinkDialog(QtWidgets.QDialog): - """A popup or normal dialog box for external link editing. - """ - contentsChanged = QtCore.pyqtSignal() -@@ -1417,18 +1417,18 @@ class ExtLinkDialog(QtGui.QDialog): - self.setWindowFlags(QtCore.Qt.Dialog | QtCore.Qt.WindowTitleHint | - QtCore.Qt.WindowSystemMenuHint) - self.setWindowTitle(_('External Link')) -- vertLayout = QtGui.QVBoxLayout(self) -+ vertLayout = QtWidgets.QVBoxLayout(self) - vertLayout.setSpacing(1) -- schemeLabel = QtGui.QLabel(_('Scheme')) -+ schemeLabel = QtWidgets.QLabel(_('Scheme')) - vertLayout.addWidget(schemeLabel) -- schemeLayout = QtGui.QHBoxLayout() -+ schemeLayout = QtWidgets.QHBoxLayout() - vertLayout.addLayout(schemeLayout) - schemeLayout.setSpacing(8) -- self.schemeButtons = QtGui.QButtonGroup(self) -+ self.schemeButtons = QtWidgets.QButtonGroup(self) - self.schemeButtonDict = {} - for scheme in _extLinkSchemes: - scheme = scheme.split(':', 1)[0] -- button = QtGui.QRadioButton(scheme) -+ button = QtWidgets.QRadioButton(scheme) - self.schemeButtons.addButton(button) - self.schemeButtonDict[scheme] = button - schemeLayout.addWidget(button) -@@ -1436,51 +1436,51 @@ class ExtLinkDialog(QtGui.QDialog): - self.schemeButtons.buttonClicked.connect(self.updateScheme) - vertLayout.addSpacing(8) - -- self.browseButton = QtGui.QPushButton(_('&Browse for File')) -+ self.browseButton = QtWidgets.QPushButton(_('&Browse for File')) - self.browseButton.setAutoDefault(False) - self.browseButton.clicked.connect(self.fileBrowse) - vertLayout.addWidget(self.browseButton) - vertLayout.addSpacing(8) - -- self.pathTypeLabel = QtGui.QLabel(_('File Path Type')) -+ self.pathTypeLabel = QtWidgets.QLabel(_('File Path Type')) - vertLayout.addWidget(self.pathTypeLabel) -- pathTypeLayout = QtGui.QHBoxLayout() -+ pathTypeLayout = QtWidgets.QHBoxLayout() - vertLayout.addLayout(pathTypeLayout) - pathTypeLayout.setSpacing(8) -- pathTypeButtons = QtGui.QButtonGroup(self) -- self.absoluteButton = QtGui.QRadioButton(_('Absolute')) -+ pathTypeButtons = QtWidgets.QButtonGroup(self) -+ self.absoluteButton = QtWidgets.QRadioButton(_('Absolute')) - pathTypeButtons.addButton(self.absoluteButton) - pathTypeLayout.addWidget(self.absoluteButton) -- self.relativeButton = QtGui.QRadioButton(_('Relative')) -+ self.relativeButton = QtWidgets.QRadioButton(_('Relative')) - pathTypeButtons.addButton(self.relativeButton) - pathTypeLayout.addWidget(self.relativeButton) - self.absoluteButton.setChecked(True) - pathTypeButtons.buttonClicked.connect(self.updatePathType) - vertLayout.addSpacing(8) - -- addressLabel = QtGui.QLabel(_('Address')) -+ addressLabel = QtWidgets.QLabel(_('Address')) - vertLayout.addWidget(addressLabel) -- self.addressEdit = QtGui.QLineEdit() -+ self.addressEdit = QtWidgets.QLineEdit() - self.addressEdit.textEdited.connect(self.checkAddress) - vertLayout.addWidget(self.addressEdit) - vertLayout.addSpacing(8) - -- nameLabel = QtGui.QLabel(_('Display Name')) -+ nameLabel = QtWidgets.QLabel(_('Display Name')) - vertLayout.addWidget(nameLabel) -- self.nameEdit = QtGui.QLineEdit() -+ self.nameEdit = QtWidgets.QLineEdit() - self.nameEdit.textEdited.connect(self.contentsChanged) - vertLayout.addWidget(self.nameEdit) - if popupDialog: - self.setWindowFlags(QtCore.Qt.Popup) - else: - vertLayout.addSpacing(8) -- ctrlLayout = QtGui.QHBoxLayout() -+ ctrlLayout = QtWidgets.QHBoxLayout() - vertLayout.addLayout(ctrlLayout) - ctrlLayout.addStretch(0) -- okButton = QtGui.QPushButton(_('&OK')) -+ okButton = QtWidgets.QPushButton(_('&OK')) - ctrlLayout.addWidget(okButton) - okButton.clicked.connect(self.accept) -- cancelButton = QtGui.QPushButton(_('&Cancel')) -+ cancelButton = QtWidgets.QPushButton(_('&Cancel')) - ctrlLayout.addWidget(cancelButton) - cancelButton.clicked.connect(self.reject) - self.addressEdit.setFocus() -@@ -1635,10 +1635,10 @@ class ExtLinkDialog(QtGui.QDialog): - oldAddress = urltools.extractAddress(oldAddress) - if os.access(oldAddress, os.F_OK): - defaultPath = oldAddress -- address = QtGui.QFileDialog.getOpenFileName(self, -+ address = QtWidgets.QFileDialog.getOpenFileName(self, - _('TreeLine - External Link File'), - defaultPath, -- globalref.fileFilters['all']) -+ globalref.fileFilters['all'])[0] - if address: - if self.relativeButton.isChecked(): - address = urltools.toRelative(address, refPath) -@@ -1662,7 +1662,7 @@ class IntLinkEditor(ComboEditor): - super().__init__(parent) - self.intLinkDialog = None - self.addedIntLinkFlag = False -- openAction = QtGui.QAction(_('&Go to Target'), self) -+ openAction = QtWidgets.QAction(_('&Go to Target'), self) - openAction.triggered.connect(self.openLink) - self.lineEdit().extraMenuActions = [openAction] - -@@ -1675,7 +1675,7 @@ class IntLinkEditor(ComboEditor): - self.intLinkDialog.show() - pos = self.mapToGlobal(self.rect().bottomRight()) - pos.setX(pos.x() - self.intLinkDialog.width() + 1) -- screenBottom = (QtGui.QApplication.desktop().screenGeometry(self). -+ screenBottom = (QtWidgets.QApplication.desktop().screenGeometry(self). - bottom()) - if pos.y() + self.intLinkDialog.height() > screenBottom: - pos.setY(pos.y() - self.rect().height() - -@@ -1744,7 +1744,7 @@ class IntLinkEditor(ComboEditor): - _targetButtonLabel = {False: _('Enable click-on-&target'), - True: _('Disable click-on-&target')} - --class IntLinkDialog(QtGui.QDialog): -+class IntLinkDialog(QtWidgets.QDialog): - """A popup or normal dialog box for internal link editing. - """ - contentsChanged = QtCore.pyqtSignal() -@@ -1762,36 +1762,36 @@ class IntLinkDialog(QtGui.QDialog): - self.setWindowTitle(_('Internal Link')) - self.popupDialog = popupDialog - self.targetMode = False -- vertLayout = QtGui.QVBoxLayout(self) -+ vertLayout = QtWidgets.QVBoxLayout(self) - vertLayout.setSpacing(1) -- self.targetButton = QtGui.QPushButton(_targetButtonLabel[False]) -+ self.targetButton = QtWidgets.QPushButton(_targetButtonLabel[False]) - self.targetButton.setCheckable(True) - self.targetButton.clicked.connect(self.toggleTargetClick) - vertLayout.addWidget(self.targetButton) - vertLayout.addSpacing(8) -- addressLabel = QtGui.QLabel(_('Unique ID')) -+ addressLabel = QtWidgets.QLabel(_('Unique ID')) - vertLayout.addWidget(addressLabel) -- self.addressEdit = QtGui.QLineEdit() -+ self.addressEdit = QtWidgets.QLineEdit() - self.addressEdit.textEdited.connect(self.contentsChanged) - vertLayout.addWidget(self.addressEdit) - vertLayout.addSpacing(8) -- nameLabel = QtGui.QLabel(_('Display Name')) -+ nameLabel = QtWidgets.QLabel(_('Display Name')) - vertLayout.addWidget(nameLabel) -- self.nameEdit = QtGui.QLineEdit() -+ self.nameEdit = QtWidgets.QLineEdit() - self.nameEdit.textEdited.connect(self.contentsChanged) - vertLayout.addWidget(self.nameEdit) - if self.popupDialog: - self.setWindowFlags(QtCore.Qt.Popup) - else: - vertLayout.addSpacing(8) -- ctrlLayout = QtGui.QHBoxLayout() -+ ctrlLayout = QtWidgets.QHBoxLayout() - vertLayout.addLayout(ctrlLayout) - ctrlLayout.addStretch(0) -- okButton = QtGui.QPushButton(_('&OK')) -+ okButton = QtWidgets.QPushButton(_('&OK')) - ctrlLayout.addWidget(okButton) - okButton.setDefault(True) - okButton.clicked.connect(self.accept) -- cancelButton = QtGui.QPushButton(_('&Cancel')) -+ cancelButton = QtWidgets.QPushButton(_('&Cancel')) - ctrlLayout.addWidget(cancelButton) - cancelButton.clicked.connect(self.reject) - -@@ -1930,7 +1930,7 @@ class PictureLinkEditor(ComboEditor): - """ - super().__init__(parent) - self.dialog = None -- openAction = QtGui.QAction(_('&Open Picture'), self) -+ openAction = QtWidgets.QAction(_('&Open Picture'), self) - openAction.triggered.connect(self.openPicture) - self.lineEdit().extraMenuActions = [openAction] - -@@ -1943,7 +1943,7 @@ class PictureLinkEditor(ComboEditor): - self.dialog.show() - pos = self.mapToGlobal(self.rect().bottomRight()) - pos.setX(pos.x() - self.dialog.width() + 1) -- screenBottom = (QtGui.QApplication.desktop().screenGeometry(self). -+ screenBottom = (QtWidgets.QApplication.desktop().screenGeometry(self). - bottom()) - if pos.y() + self.dialog.height() > screenBottom: - pos.setY(pos.y() - self.rect().height() - self.dialog.height()) -@@ -1981,7 +1981,7 @@ class PictureLinkEditor(ComboEditor): - self.setEditText(urlText) - - --class PictureLinkDialog(QtGui.QDialog): -+class PictureLinkDialog(QtWidgets.QDialog): - """A popup or normal dialog box for picture link editing. - """ - thumbnailSize = QtCore.QSize(250, 100) -@@ -1998,40 +1998,40 @@ class PictureLinkDialog(QtGui.QDialog): - QtCore.Qt.WindowSystemMenuHint) - self.setWindowTitle(_('Picture Link')) - self.setMinimumWidth(self.thumbnailSize.width()) -- vertLayout = QtGui.QVBoxLayout(self) -+ vertLayout = QtWidgets.QVBoxLayout(self) - vertLayout.setSpacing(1) -- self.thumbnail = QtGui.QLabel() -+ self.thumbnail = QtWidgets.QLabel() - pixmap = QtGui.QPixmap(self.thumbnailSize) - pixmap.fill() - self.thumbnail.setPixmap(pixmap) - vertLayout.addWidget(self.thumbnail, 0, QtCore.Qt.AlignHCenter) - vertLayout.addSpacing(8) - -- self.browseButton = QtGui.QPushButton(_('&Browse for File')) -+ self.browseButton = QtWidgets.QPushButton(_('&Browse for File')) - self.browseButton.setAutoDefault(False) - self.browseButton.clicked.connect(self.fileBrowse) - vertLayout.addWidget(self.browseButton) - vertLayout.addSpacing(8) - -- self.pathTypeLabel = QtGui.QLabel(_('File Path Type')) -+ self.pathTypeLabel = QtWidgets.QLabel(_('File Path Type')) - vertLayout.addWidget(self.pathTypeLabel) -- pathTypeLayout = QtGui.QHBoxLayout() -+ pathTypeLayout = QtWidgets.QHBoxLayout() - vertLayout.addLayout(pathTypeLayout) - pathTypeLayout.setSpacing(8) -- pathTypeButtons = QtGui.QButtonGroup(self) -- self.absoluteButton = QtGui.QRadioButton(_('Absolute')) -+ pathTypeButtons = QtWidgets.QButtonGroup(self) -+ self.absoluteButton = QtWidgets.QRadioButton(_('Absolute')) - pathTypeButtons.addButton(self.absoluteButton) - pathTypeLayout.addWidget(self.absoluteButton) -- self.relativeButton = QtGui.QRadioButton(_('Relative')) -+ self.relativeButton = QtWidgets.QRadioButton(_('Relative')) - pathTypeButtons.addButton(self.relativeButton) - pathTypeLayout.addWidget(self.relativeButton) - self.absoluteButton.setChecked(True) - pathTypeButtons.buttonClicked.connect(self.updatePathType) - vertLayout.addSpacing(8) - -- addressLabel = QtGui.QLabel(_('Address')) -+ addressLabel = QtWidgets.QLabel(_('Address')) - vertLayout.addWidget(addressLabel) -- self.addressEdit = QtGui.QLineEdit() -+ self.addressEdit = QtWidgets.QLineEdit() - self.addressEdit.textEdited.connect(self.checkAddress) - vertLayout.addWidget(self.addressEdit) - vertLayout.addSpacing(8) -@@ -2040,13 +2040,13 @@ class PictureLinkDialog(QtGui.QDialog): - self.setWindowFlags(QtCore.Qt.Popup) - else: - vertLayout.addSpacing(8) -- ctrlLayout = QtGui.QHBoxLayout() -+ ctrlLayout = QtWidgets.QHBoxLayout() - vertLayout.addLayout(ctrlLayout) - ctrlLayout.addStretch(0) -- okButton = QtGui.QPushButton(_('&OK')) -+ okButton = QtWidgets.QPushButton(_('&OK')) - ctrlLayout.addWidget(okButton) - okButton.clicked.connect(self.accept) -- cancelButton = QtGui.QPushButton(_('&Cancel')) -+ cancelButton = QtWidgets.QPushButton(_('&Cancel')) - ctrlLayout.addWidget(cancelButton) - cancelButton.clicked.connect(self.reject) - self.addressEdit.setFocus() -@@ -2144,10 +2144,10 @@ class PictureLinkDialog(QtGui.QDialog): - oldAddress = urltools.extractAddress(oldAddress) - if os.access(oldAddress, os.F_OK): - defaultPath = oldAddress -- address = QtGui.QFileDialog.getOpenFileName(self, -+ address = QtWidgets.QFileDialog.getOpenFileName(self, - _('TreeLine - Picture File'), - defaultPath, -- globalref.fileFilters['all']) -+ globalref.fileFilters['all'])[0] - if address: - if self.relativeButton.isChecked(): - address = urltools.toRelative(address, refPath) -Index: TreeLine/source/dataeditview.py -=================================================================== ---- TreeLine.orig/source/dataeditview.py -+++ TreeLine/source/dataeditview.py -@@ -13,7 +13,7 @@ - #****************************************************************************** - - import os.path --from PyQt4 import QtCore, QtGui -+from PyQt5 import QtCore, QtGui, QtWidgets - import treenode - import undo - import urltools -@@ -23,7 +23,7 @@ import globalref - _minColumnWidth = 80 - defaultFont = None - --class DataEditCell(QtGui.QTableWidgetItem): -+class DataEditCell(QtWidgets.QTableWidgetItem): - """Class override for data edit view cells. - Used for the cells with editable content. - """ -@@ -68,7 +68,7 @@ class DataEditCell(QtGui.QTableWidgetIte - self.doc.setPlainText(self.text()) - - --class DataEditDelegate(QtGui.QStyledItemDelegate): -+class DataEditDelegate(QtWidgets.QStyledItemDelegate): - """Class override for display and editing of DataEditCells. - """ - def __init__(self, parent=None): -@@ -101,8 +101,8 @@ class DataEditDelegate(QtGui.QStyledItem - paintRect = QtCore.QRectF(0, 0, styleOption.rect.width(), - styleOption.rect.height()) - painter.setClipRect(paintRect) -- painter.fillRect(paintRect, QtGui.QApplication.palette().base()) -- painter.setPen(QtGui.QPen(QtGui.QApplication.palette().text(), 1)) -+ painter.fillRect(paintRect, QtWidgets.QApplication.palette().base()) -+ painter.setPen(QtGui.QPen(QtWidgets.QApplication.palette().text(), 1)) - painter.drawRect(paintRect.adjusted(0, 0, -1, -1)) - doc.drawContents(painter) - if cell.errorFlag: -@@ -111,7 +111,7 @@ class DataEditDelegate(QtGui.QStyledItem - path.lineTo(10, 0) - path.closeSubpath() - painter.fillPath(path, -- QtGui.QApplication.palette().highlight()) -+ QtWidgets.QApplication.palette().highlight()) - painter.restore() - else: - super().paint(painter, styleOption, modelIndex) -@@ -311,7 +311,7 @@ class DataEditDelegate(QtGui.QStyledItem - return super().eventFilter(editor, event) - - --class DataEditView(QtGui.QTableWidget): -+class DataEditView(QtWidgets.QTableWidget): - """Class override for the table-based data edit view. - - Sets view defaults and updates the content. -@@ -339,18 +339,18 @@ class DataEditView(QtGui.QTableWidget): - self.setAcceptDrops(True) - self.horizontalHeader().hide() - self.verticalHeader().hide() -- self.setVerticalScrollMode(QtGui.QAbstractItemView.ScrollPerPixel) -- self.setHorizontalScrollMode(QtGui.QAbstractItemView.ScrollPerPixel) -- self.setSelectionMode(QtGui.QAbstractItemView.SingleSelection) -+ self.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel) -+ self.setHorizontalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel) -+ self.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection) - self.setItemDelegate(DataEditDelegate(self)) -- self.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers) -+ self.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) - self.setShowGrid(False) - self.setTabKeyNavigation(False) - pal = self.palette() - pal.setBrush(QtGui.QPalette.Base, -- QtGui.QApplication.palette().window()) -+ QtWidgets.QApplication.palette().window()) - pal.setBrush(QtGui.QPalette.Text, -- QtGui.QApplication.palette().windowText()) -+ QtWidgets.QApplication.palette().windowText()) - self.setPalette(pal) - self.currentItemChanged.connect(self.moveEditor) - -@@ -463,7 +463,7 @@ class DataEditView(QtGui.QTableWidget): - text -- the initial text string for the cell - alignment -- the text alignment QT constant (None for default) - """ -- cell = QtGui.QTableWidgetItem(text) -+ cell = QtWidgets.QTableWidgetItem(text) - cell.setFlags(QtCore.Qt.NoItemFlags) - if alignment: - cell.setTextAlignment(alignment) -Index: TreeLine/source/exports.py -=================================================================== ---- TreeLine.orig/source/exports.py -+++ TreeLine/source/exports.py -@@ -17,7 +17,7 @@ import copy - import io - import zipfile - from xml.etree import ElementTree --from PyQt4 import QtCore, QtGui -+from PyQt5 import QtCore, QtGui, QtWidgets - import treenode - import treeformats - import treemodel -@@ -68,10 +68,10 @@ class ExportControl: - 'bookmarksHtml': self.exportBookmarksHtml, - 'bookmarksXbel': self.exportBookmarksXbel} - exportDialog = ExportDialog(len(self.selectedNodes), -- QtGui.QApplication.activeWindow()) -- if exportDialog.exec_() == QtGui.QDialog.Accepted: -+ QtWidgets.QApplication.activeWindow()) -+ if exportDialog.exec_() == QtWidgets.QDialog.Accepted: - result = exportMethods[ExportDialog.currentSubtype]() -- QtGui.QApplication.restoreOverrideCursor() -+ QtWidgets.QApplication.restoreOverrideCursor() - return result - return False - -@@ -87,11 +87,11 @@ class ExportControl: - if os.path.basename(self.defaultFilePath): - self.defaultFilePath = '{0}.{1}'.format(self.defaultFilePath, - defaultExt) -- filePath = QtGui.QFileDialog.getSaveFileName(QtGui.QApplication. -+ filePath = QtWidgets.QFileDialog.getSaveFileName(QtWidgets.QApplication. - activeWindow(), - dialogTitle, - self.defaultFilePath, -- filters) -+ filters)[0] - if filePath and not os.path.splitext(filePath)[1]: - filePath = '{0}.{1}'.format(filePath, defaultExt) - return filePath -@@ -108,7 +108,7 @@ class ExportControl: - filePath = self.getFileName(_('TreeLine - Export HTML'), 'html') - if not filePath: - return False -- QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) -+ QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) - if ExportDialog.exportWhat == ExportDialog.entireTree: - self.selectedNodes = [self.rootNode] - outputGroup = treeoutput.OutputGroup(self.selectedNodes, -@@ -162,7 +162,7 @@ class ExportControl: - filePath = self.getFileName(_('TreeLine - Export HTML'), 'html') - if not filePath: - return False -- QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) -+ QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) - if ExportDialog.exportWhat == ExportDialog.entireTree: - self.selectedNodes = [self.rootNode] - outputGroup = treeoutput.OutputGroup(self.selectedNodes, -@@ -239,13 +239,13 @@ class ExportControl: - filePath -- use if given, otherwise prompt user - """ - if not filePath: -- filePath = QtGui.QFileDialog.getExistingDirectory(QtGui. -+ filePath = QtWidgets.QFileDialog.getExistingDirectory(QtWidgets. - QApplication.activeWindow(), - _('TreeLine - Export HTML'), - self.defaultFilePath) - if not filePath: - return False -- QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) -+ QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) - oldDir = os.getcwd() - os.chdir(filePath) - indent = globalref.genOptions.getValue('IndentOffset') -@@ -291,13 +291,13 @@ class ExportControl: - filePath -- use if given, otherwise prompt user - """ - if not filePath: -- filePath = QtGui.QFileDialog.getExistingDirectory(QtGui. -+ filePath = QtWidgets.QFileDialog.getExistingDirectory(QtWidgets. - QApplication.activeWindow(), - _('TreeLine - Export HTML'), - self.defaultFilePath) - if not filePath: - return False -- QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) -+ QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) - oldDir = os.getcwd() - os.chdir(filePath) - if ExportDialog.exportWhat == ExportDialog.entireTree: -@@ -333,7 +333,7 @@ class ExportControl: - 'txt') - if not filePath: - return False -- QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) -+ QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) - if ExportDialog.exportWhat == ExportDialog.entireTree: - self.selectedNodes = [self.rootNode] - if ExportDialog.exportWhat == ExportDialog.selectNode: -@@ -363,7 +363,7 @@ class ExportControl: - 'txt') - if not filePath: - return False -- QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) -+ QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) - if ExportDialog.exportWhat == ExportDialog.entireTree: - self.selectedNodes = [self.rootNode] - lines = [] -@@ -395,7 +395,7 @@ class ExportControl: - 'txt') - if not filePath: - return False -- QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) -+ QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) - if ExportDialog.exportWhat == ExportDialog.selectNode: - nodeList = self.selectedNodes - else: -@@ -432,7 +432,7 @@ class ExportControl: - 'xml') - if not filePath: - return False -- QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) -+ QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) - if ExportDialog.exportWhat == ExportDialog.entireTree: - self.selectedNodes = [self.rootNode] - addBranches = ExportDialog.exportWhat != ExportDialog.selectNode -@@ -459,7 +459,7 @@ class ExportControl: - 'Subtree'), 'trl') - if not filePath: - return False -- QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) -+ QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) - if ExportDialog.exportWhat == ExportDialog.entireTree: - self.selectedNodes = [self.rootNode] - addBranches = ExportDialog.exportWhat != ExportDialog.selectNode -@@ -496,7 +496,7 @@ class ExportControl: - filePath = self.getFileName(_('TreeLine - Export ODF Text'), 'odt') - if not filePath: - return False -- QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) -+ QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) - if ExportDialog.exportWhat == ExportDialog.entireTree: - self.selectedNodes = [self.rootNode] - addBranches = ExportDialog.exportWhat != ExportDialog.selectNode -@@ -636,7 +636,7 @@ class ExportControl: - 'html') - if not filePath: - return False -- QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) -+ QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) - if ExportDialog.exportWhat == ExportDialog.entireTree: - self.selectedNodes = [self.rootNode] - addBranches = ExportDialog.exportWhat != ExportDialog.selectNode -@@ -667,7 +667,7 @@ class ExportControl: - 'xml') - if not filePath: - return False -- QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) -+ QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) - if ExportDialog.exportWhat == ExportDialog.entireTree: - self.selectedNodes = [self.rootNode] - addBranches = ExportDialog.exportWhat != ExportDialog.selectNode -@@ -729,7 +729,7 @@ def addOdfElement(name, parent=None, att - return elem - - --class ExportDialog(QtGui.QWizard): -+class ExportDialog(QtWidgets.QWizard): - """Dialog/wizard for setting file export type and options. - """ - typePage, subtypePage, optionPage = range(3) -@@ -788,14 +788,14 @@ class ExportDialog(QtGui.QWizard): - self.setWindowFlags(QtCore.Qt.Dialog | QtCore.Qt.WindowTitleHint | - QtCore.Qt.WindowSystemMenuHint) - self.setWindowTitle(_('File Export')) -- self.setWizardStyle(QtGui.QWizard.ClassicStyle) -+ self.setWizardStyle(QtWidgets.QWizard.ClassicStyle) - self.setPage(ExportDialog.typePage, ExportDialogTypePage()) - self.setPage(ExportDialog.subtypePage, ExportDialogSubtypePage()) - self.setPage(ExportDialog.optionPage, - ExportDialogOptionPage(selectionAvail)) - - --class ExportDialogTypePage(QtGui.QWizardPage): -+class ExportDialogTypePage(QtWidgets.QWizardPage): - """A wizard page for selecting the main export type. - """ - def __init__(self, parent=None): -@@ -806,13 +806,13 @@ class ExportDialogTypePage(QtGui.QWizard - """ - super().__init__(parent) - -- topLayout = QtGui.QVBoxLayout(self) -+ topLayout = QtWidgets.QVBoxLayout(self) - self.setLayout(topLayout) - self.setTitle(_('Choose export format type')) - -- typeButtons = QtGui.QButtonGroup(self) -+ typeButtons = QtWidgets.QButtonGroup(self) - for id, exportType in enumerate(ExportDialog.exportTypes): -- button = QtGui.QRadioButton(ExportDialog. -+ button = QtWidgets.QRadioButton(ExportDialog. - exportTypeDescript[exportType]) - typeButtons.addButton(button, id) - topLayout.addWidget(button) -@@ -839,7 +839,7 @@ class ExportDialogTypePage(QtGui.QWizard - return ExportDialog.optionPage - - --class ExportDialogSubtypePage(QtGui.QWizardPage): -+class ExportDialogSubtypePage(QtWidgets.QWizardPage): - """A wizard page for selecting the export subtype. - """ - def __init__(self, parent=None): -@@ -850,10 +850,10 @@ class ExportDialogSubtypePage(QtGui.QWiz - """ - super().__init__(parent) - -- topLayout = QtGui.QVBoxLayout(self) -+ topLayout = QtWidgets.QVBoxLayout(self) - self.setLayout(topLayout) - self.setTitle(_('Choose export format subtype')) -- self.subtypeButtons = QtGui.QButtonGroup(self) -+ self.subtypeButtons = QtWidgets.QButtonGroup(self) - self.subtypeButtons.buttonClicked[int].connect(self.setCurrentSubtype) - - def initializePage(self): -@@ -868,7 +868,7 @@ class ExportDialogSubtypePage(QtGui.QWiz - - for id, subtype in enumerate(ExportDialog. - exportSubtypes[ExportDialog.currentType]): -- button = QtGui.QRadioButton(ExportDialog.subtypeDescript[subtype]) -+ button = QtWidgets.QRadioButton(ExportDialog.subtypeDescript[subtype]) - self.subtypeButtons.addButton(button, id) - topLayout.addWidget(button) - if subtype == ExportDialog.currentSubtype: -@@ -884,7 +884,7 @@ class ExportDialogSubtypePage(QtGui.QWiz - ExportDialog.currentSubtype = availSubtypes[buttonId] - - --class ExportDialogOptionPage(QtGui.QWizardPage): -+class ExportDialogOptionPage(QtWidgets.QWizardPage): - """A wizard page for selecting other export options. - """ - def __init__(self, selectionAvail=True, parent=None): -@@ -897,65 +897,65 @@ class ExportDialogOptionPage(QtGui.QWiza - super().__init__(parent) - self.selectionAvail = selectionAvail - -- topLayout = QtGui.QVBoxLayout(self) -+ topLayout = QtWidgets.QVBoxLayout(self) - self.setLayout(topLayout) - self.setTitle(_('Choose export options')) - -- whatGroupBox = QtGui.QGroupBox(_('What to Export')) -+ whatGroupBox = QtWidgets.QGroupBox(_('What to Export')) - topLayout.addWidget(whatGroupBox) -- whatLayout = QtGui.QVBoxLayout(whatGroupBox) -- self.whatButtons = QtGui.QButtonGroup(self) -- treeButton = QtGui.QRadioButton(_('&Entire tree')) -+ whatLayout = QtWidgets.QVBoxLayout(whatGroupBox) -+ self.whatButtons = QtWidgets.QButtonGroup(self) -+ treeButton = QtWidgets.QRadioButton(_('&Entire tree')) - self.whatButtons.addButton(treeButton, ExportDialog.entireTree) - whatLayout.addWidget(treeButton) -- branchButton = QtGui.QRadioButton(_('Selected &branches')) -+ branchButton = QtWidgets.QRadioButton(_('Selected &branches')) - self.whatButtons.addButton(branchButton, ExportDialog.selectBranch) - whatLayout.addWidget(branchButton) -- nodeButton = QtGui.QRadioButton(_('Selected &nodes')) -+ nodeButton = QtWidgets.QRadioButton(_('Selected &nodes')) - self.whatButtons.addButton(nodeButton, ExportDialog.selectNode) - whatLayout.addWidget(nodeButton) - self.whatButtons.button(ExportDialog.exportWhat).setChecked(True) - self.whatButtons.buttonClicked[int].connect(self.setExportWhat) - -- optionBox = QtGui.QGroupBox(_('Other Options')) -+ optionBox = QtWidgets.QGroupBox(_('Other Options')) - topLayout.addWidget(optionBox) -- optionLayout = QtGui.QVBoxLayout(optionBox) -- self.rootButton = QtGui.QCheckBox(_('&Include root node')) -+ optionLayout = QtWidgets.QVBoxLayout(optionBox) -+ self.rootButton = QtWidgets.QCheckBox(_('&Include root node')) - optionLayout.addWidget(self.rootButton) - self.rootButton.setChecked(ExportDialog.includeRoot) - self.rootButton.toggled.connect(self.setIncludeRoot) - -- self.openOnlyButton = QtGui.QCheckBox(_('&Only open node children')) -+ self.openOnlyButton = QtWidgets.QCheckBox(_('&Only open node children')) - optionLayout.addWidget(self.openOnlyButton) - self.openOnlyButton.setChecked(ExportDialog.openOnly) - self.openOnlyButton.toggled.connect(self.setOpenOnly) - -- self.headerButton = QtGui.QCheckBox(_('Include &print header && ' -+ self.headerButton = QtWidgets.QCheckBox(_('Include &print header && ' - 'footer')) - optionLayout.addWidget(self.headerButton) - self.headerButton.setChecked(ExportDialog.addHeader) - self.headerButton.toggled.connect(self.setAddHeader) - -- columnLayout = QtGui.QHBoxLayout() -+ columnLayout = QtWidgets.QHBoxLayout() - optionLayout.addLayout(columnLayout) -- self.numColSpin = QtGui.QSpinBox() -+ self.numColSpin = QtWidgets.QSpinBox() - columnLayout.addWidget(self.numColSpin) - self.numColSpin.setRange(1, 9) - self.numColSpin.setMaximumWidth(40) - self.numColSpin.setValue(ExportDialog.numColumns) -- self.colLabel = QtGui.QLabel(_('&Columns')) -+ self.colLabel = QtWidgets.QLabel(_('&Columns')) - columnLayout.addWidget(self.colLabel) - self.colLabel.setBuddy(self.numColSpin) - self.numColSpin.valueChanged.connect(self.setNumColumns) - -- navLevelsLayout = QtGui.QHBoxLayout() -+ navLevelsLayout = QtWidgets.QHBoxLayout() - optionLayout.addLayout(navLevelsLayout) -- self.navLevelsSpin = QtGui.QSpinBox() -+ self.navLevelsSpin = QtWidgets.QSpinBox() - navLevelsLayout.addWidget(self.navLevelsSpin) - self.navLevelsSpin.setRange(1, 9) - self.navLevelsSpin.setMaximumWidth(40) - self.navLevelsSpin.setValue(ExportDialog.navPaneLevels) -- self.navLevelsLabel = QtGui.QLabel(_('Navigation pane &levels')) -+ self.navLevelsLabel = QtWidgets.QLabel(_('Navigation pane &levels')) - navLevelsLayout.addWidget(self.navLevelsLabel) - self.navLevelsLabel.setBuddy(self.navLevelsSpin) - self.navLevelsSpin.valueChanged.connect(self.setNavLevels) -Index: TreeLine/source/fieldformat.py -=================================================================== ---- TreeLine.orig/source/fieldformat.py -+++ TreeLine/source/fieldformat.py -@@ -15,7 +15,7 @@ - import re - import os.path - import xml.sax.saxutils --from PyQt4 import QtCore, QtGui -+from PyQt5 import QtCore - import globalref - import gennumber - import genboolean -Index: TreeLine/source/helpview.py -=================================================================== ---- TreeLine.orig/source/helpview.py -+++ TreeLine/source/helpview.py -@@ -13,20 +13,20 @@ - - import os.path - import sys --from PyQt4 import QtCore, QtGui -+from PyQt5 import QtCore, QtGui, QtWidgets - import dataeditors - - --class HelpView(QtGui.QMainWindow): -+class HelpView(QtWidgets.QMainWindow): - """Main window for viewing an html help file. - """ - def __init__(self, path, caption, icons, parent=None): - """Helpview initialize with text. - """ -- QtGui.QMainWindow.__init__(self, parent) -+ QtWidgets.QMainWindow.__init__(self, parent) - self.setAttribute(QtCore.Qt.WA_QuitOnClose, False) - self.setWindowFlags(QtCore.Qt.Window) -- self.setStatusBar(QtGui.QStatusBar()) -+ self.setStatusBar(QtWidgets.QStatusBar()) - self.textView = HelpViewer(self) - self.setCentralWidget(self.textView) - path = os.path.abspath(path) -@@ -37,10 +37,10 @@ class HelpView(QtGui.QMainWindow): - self.resize(520, 440) - self.setWindowTitle(caption) - tools = self.addToolBar(_('Tools')) -- self.menu = QtGui.QMenu(self.textView) -+ self.menu = QtWidgets.QMenu(self.textView) - self.textView.highlighted[str].connect(self.showLink) - -- backAct = QtGui.QAction(_('&Back'), self) -+ backAct = QtWidgets.QAction(_('&Back'), self) - backAct.setIcon(icons['helpback']) - tools.addAction(backAct) - self.menu.addAction(backAct) -@@ -48,7 +48,7 @@ class HelpView(QtGui.QMainWindow): - backAct.setEnabled(False) - self.textView.backwardAvailable.connect(backAct.setEnabled) - -- forwardAct = QtGui.QAction(_('&Forward'), self) -+ forwardAct = QtWidgets.QAction(_('&Forward'), self) - forwardAct.setIcon(icons['helpforward']) - tools.addAction(forwardAct) - self.menu.addAction(forwardAct) -@@ -56,7 +56,7 @@ class HelpView(QtGui.QMainWindow): - forwardAct.setEnabled(False) - self.textView.forwardAvailable.connect(forwardAct.setEnabled) - -- homeAct = QtGui.QAction(_('&Home'), self) -+ homeAct = QtWidgets.QAction(_('&Home'), self) - homeAct.setIcon(icons['helphome']) - tools.addAction(homeAct) - self.menu.addAction(homeAct) -@@ -64,21 +64,21 @@ class HelpView(QtGui.QMainWindow): - - tools.addSeparator() - tools.addSeparator() -- findLabel = QtGui.QLabel(_(' Find: '), self) -+ findLabel = QtWidgets.QLabel(_(' Find: '), self) - tools.addWidget(findLabel) -- self.findEdit = QtGui.QLineEdit(self) -+ self.findEdit = QtWidgets.QLineEdit(self) - tools.addWidget(self.findEdit) - self.findEdit.textEdited.connect(self.findTextChanged) - self.findEdit.returnPressed.connect(self.findNext) - -- self.findPreviousAct = QtGui.QAction(_('Find &Previous'), self) -+ self.findPreviousAct = QtWidgets.QAction(_('Find &Previous'), self) - self.findPreviousAct.setIcon(icons['helpprevious']) - tools.addAction(self.findPreviousAct) - self.menu.addAction(self.findPreviousAct) - self.findPreviousAct.triggered.connect(self.findPrevious) - self.findPreviousAct.setEnabled(False) - -- self.findNextAct = QtGui.QAction(_('Find &Next'), self) -+ self.findNextAct = QtWidgets.QAction(_('Find &Next'), self) - self.findNextAct.setIcon(icons['helpnext']) - tools.addAction(self.findNextAct) - self.menu.addAction(self.findNextAct) -@@ -114,11 +114,11 @@ class HelpView(QtGui.QMainWindow): - self.statusBar().showMessage(_('Text string not found')) - - --class HelpViewer(QtGui.QTextBrowser): -+class HelpViewer(QtWidgets.QTextBrowser): - """Shows an html help file. - """ - def __init__(self, parent=None): -- QtGui.QTextBrowser.__init__(self, parent) -+ QtWidgets.QTextBrowser.__init__(self, parent) - - def setSource(self, url): - """Called when user clicks on a URL. -@@ -127,7 +127,7 @@ class HelpViewer(QtGui.QTextBrowser): - if name.startswith('http'): - dataeditors.openExtUrl(name) - else: -- QtGui.QTextBrowser.setSource(self, QtCore.QUrl(name)) -+ QtWidgets.QTextBrowser.setSource(self, QtCore.QUrl(name)) - - def contextMenuEvent(self, event): - """Init popup menu on right click"". -Index: TreeLine/source/icondict.py -=================================================================== ---- TreeLine.orig/source/icondict.py -+++ TreeLine/source/icondict.py -@@ -12,7 +12,7 @@ - #****************************************************************************** - - import os.path --from PyQt4 import QtCore, QtGui -+from PyQt5 import QtCore, QtGui - - _iconExtension = ('.png', '.bmp', '.ico', '.gif') - defaultName = 'default' -Index: TreeLine/source/imports.py -=================================================================== ---- TreeLine.orig/source/imports.py -+++ TreeLine/source/imports.py -@@ -18,7 +18,7 @@ import collections - import zipfile - import html.parser - from xml.etree import ElementTree --from PyQt4 import QtCore, QtGui -+from PyQt5 import QtCore, QtGui, QtWidgets - import miscdialogs - import treenode - import treemodel -@@ -79,34 +79,34 @@ class ImportControl: - dialog = miscdialogs.RadioChoiceDialog(_('Import File'), - _('Choose Import Method'), - methods.items(), -- QtGui.QApplication. -+ QtWidgets.QApplication. - activeWindow()) - if addWarning: - fileName = os.path.basename(self.filePath) - dialog.addLabelBox(_('Invalid File'), - _('"{0}" is not a valid TreeLine file.\n\n' - 'Use an import filter?').format(fileName)) -- if dialog.exec_() != QtGui.QDialog.Accepted: -+ if dialog.exec_() != QtWidgets.QDialog.Accepted: - return None - method = dialog.selectedButton() - if not self.filePath: - filters = ';;'.join((globalref.fileFilters[fileFilters[method]], - globalref.fileFilters['all'])) - defaultFilePath = globalref.mainControl.defaultFilePath(True) -- self.filePath = QtGui.QFileDialog.getOpenFileName(QtGui. -+ self.filePath = QtWidgets.QFileDialog.getOpenFileName(QtWidgets. - QApplication.activeWindow(), - _('TreeLine - Import File'), -- defaultFilePath, filters) -+ defaultFilePath, filters)[0] - if not self.filePath: - return None - self.errorMessage = '' - try: -- QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) -+ QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) - model = getattr(self, method)() -- QtGui.QApplication.restoreOverrideCursor() -+ QtWidgets.QApplication.restoreOverrideCursor() - except IOError: -- QtGui.QApplication.restoreOverrideCursor() -- QtGui.QMessageBox.warning(QtGui.QApplication.activeWindow(), -+ QtWidgets.QApplication.restoreOverrideCursor() -+ QtWidgets.QMessageBox.warning(QtWidgets.QApplication.activeWindow(), - 'TreeLine', - _('Error - could not read file {0}'). - format(self.filePath)) -@@ -116,7 +116,7 @@ class ImportControl: - if self.errorMessage: - message = '{0}\n{1}'.format(message, self.errorMessage) - self.errorMessage = '' -- QtGui.QMessageBox.warning(QtGui.QApplication.activeWindow(), -+ QtWidgets.QMessageBox.warning(QtWidgets.QApplication.activeWindow(), - 'TreeLine', message) - return model - -Index: TreeLine/source/miscdialogs.py -=================================================================== ---- TreeLine.orig/source/miscdialogs.py -+++ TreeLine/source/miscdialogs.py -@@ -17,14 +17,14 @@ import sys - import os.path - import operator - import collections --from PyQt4 import QtCore, QtGui -+from PyQt5 import QtCore, QtGui, QtWidgets - import printdialogs - import undo - import options - import globalref - - --class RadioChoiceDialog(QtGui.QDialog): -+class RadioChoiceDialog(QtWidgets.QDialog): - """Dialog for choosing between a list of text items (radio buttons). - - Dialog title, group heading, button text and return text can be set. -@@ -42,27 +42,27 @@ class RadioChoiceDialog(QtGui.QDialog): - self.setWindowFlags(QtCore.Qt.Dialog | QtCore.Qt.WindowTitleHint | - QtCore.Qt.WindowSystemMenuHint) - self.setWindowTitle(title) -- topLayout = QtGui.QVBoxLayout(self) -+ topLayout = QtWidgets.QVBoxLayout(self) - self.setLayout(topLayout) - -- groupBox = QtGui.QGroupBox(heading) -+ groupBox = QtWidgets.QGroupBox(heading) - topLayout.addWidget(groupBox) -- groupLayout = QtGui.QVBoxLayout(groupBox) -- self.buttonGroup = QtGui.QButtonGroup(self) -+ groupLayout = QtWidgets.QVBoxLayout(groupBox) -+ self.buttonGroup = QtWidgets.QButtonGroup(self) - for text, value in choiceList: -- button = QtGui.QRadioButton(text) -+ button = QtWidgets.QRadioButton(text) - button.returnValue = value - groupLayout.addWidget(button) - self.buttonGroup.addButton(button) - self.buttonGroup.buttons()[0].setChecked(True) - -- ctrlLayout = QtGui.QHBoxLayout() -+ ctrlLayout = QtWidgets.QHBoxLayout() - topLayout.addLayout(ctrlLayout) - ctrlLayout.addStretch(0) -- okButton = QtGui.QPushButton(_('&OK')) -+ okButton = QtWidgets.QPushButton(_('&OK')) - ctrlLayout.addWidget(okButton) - okButton.clicked.connect(self.accept) -- cancelButton = QtGui.QPushButton(_('&Cancel')) -+ cancelButton = QtWidgets.QPushButton(_('&Cancel')) - ctrlLayout.addWidget(cancelButton) - cancelButton.clicked.connect(self.reject) - groupBox.setFocus() -@@ -74,10 +74,10 @@ class RadioChoiceDialog(QtGui.QDialog): - heading -- the groupbox text - text - the label text - """ -- labelBox = QtGui.QGroupBox(heading) -+ labelBox = QtWidgets.QGroupBox(heading) - self.layout().insertWidget(0, labelBox) -- labelLayout = QtGui.QVBoxLayout(labelBox) -- label = QtGui.QLabel(text) -+ labelLayout = QtWidgets.QVBoxLayout(labelBox) -+ label = QtWidgets.QLabel(text) - labelLayout.addWidget(label) - - def selectedButton(self): -@@ -86,7 +86,7 @@ class RadioChoiceDialog(QtGui.QDialog): - return self.buttonGroup.checkedButton().returnValue - - --class FieldSelectDialog(QtGui.QDialog): -+class FieldSelectDialog(QtWidgets.QDialog): - """Dialog for selecting a sequence from a list of field names. - """ - def __init__(self, title, heading, fieldList, parent=None): -@@ -103,32 +103,32 @@ class FieldSelectDialog(QtGui.QDialog): - QtCore.Qt.WindowSystemMenuHint) - self.setWindowTitle(title) - self.selectedFields = [] -- topLayout = QtGui.QVBoxLayout(self) -+ topLayout = QtWidgets.QVBoxLayout(self) - self.setLayout(topLayout) -- groupBox = QtGui.QGroupBox(heading) -+ groupBox = QtWidgets.QGroupBox(heading) - topLayout.addWidget(groupBox) -- groupLayout = QtGui.QVBoxLayout(groupBox) -+ groupLayout = QtWidgets.QVBoxLayout(groupBox) - -- self.listView = QtGui.QTreeWidget() -+ self.listView = QtWidgets.QTreeWidget() - groupLayout.addWidget(self.listView) - self.listView.setHeaderLabels(['#', _('Fields')]) - self.listView.setRootIsDecorated(False) - self.listView.setSortingEnabled(False) -- self.listView.setSelectionMode(QtGui.QAbstractItemView.MultiSelection) -+ self.listView.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection) - for field in fieldList: -- QtGui.QTreeWidgetItem(self.listView, ['', field]) -+ QtWidgets.QTreeWidgetItem(self.listView, ['', field]) - self.listView.resizeColumnToContents(0) - self.listView.resizeColumnToContents(1) - self.listView.itemSelectionChanged.connect(self.updateSelectedFields) - -- ctrlLayout = QtGui.QHBoxLayout() -+ ctrlLayout = QtWidgets.QHBoxLayout() - topLayout.addLayout(ctrlLayout) - ctrlLayout.addStretch(0) -- self.okButton = QtGui.QPushButton(_('&OK')) -+ self.okButton = QtWidgets.QPushButton(_('&OK')) - ctrlLayout.addWidget(self.okButton) - self.okButton.clicked.connect(self.accept) - self.okButton.setEnabled(False) -- cancelButton = QtGui.QPushButton(_('&Cancel')) -+ cancelButton = QtWidgets.QPushButton(_('&Cancel')) - ctrlLayout.addWidget(cancelButton) - cancelButton.clicked.connect(self.reject) - self.listView.setFocus() -@@ -139,13 +139,13 @@ class FieldSelectDialog(QtGui.QDialog): - itemList = [self.listView.topLevelItem(i) for i in - range(self.listView.topLevelItemCount())] - for item in itemList: -- if self.listView.isItemSelected(item): -+ if item.isSelected(): - if item.text(1) not in self.selectedFields: - self.selectedFields.append(item.text(1)) - elif item.text(1) in self.selectedFields: - self.selectedFields.remove(item.text(1)) - for item in itemList: -- if self.listView.isItemSelected(item): -+ if item.isSelected(): - item.setText(0, str(self.selectedFields.index(item.text(1)) - + 1)) - else: -@@ -153,7 +153,7 @@ class FieldSelectDialog(QtGui.QDialog): - self.okButton.setEnabled(len(self.selectedFields)) - - --class FilePropertiesDialog(QtGui.QDialog): -+class FilePropertiesDialog(QtWidgets.QDialog): - """Dialog for setting file parameters like compression and encryption. - """ - def __init__(self, localControl, parent=None): -@@ -168,42 +168,42 @@ class FilePropertiesDialog(QtGui.QDialog - self.setWindowFlags(QtCore.Qt.Dialog | QtCore.Qt.WindowTitleHint | - QtCore.Qt.WindowSystemMenuHint) - self.setWindowTitle(_('File Properties')) -- topLayout = QtGui.QVBoxLayout(self) -+ topLayout = QtWidgets.QVBoxLayout(self) - self.setLayout(topLayout) - -- groupBox = QtGui.QGroupBox(_('File Storage')) -+ groupBox = QtWidgets.QGroupBox(_('File Storage')) - topLayout.addWidget(groupBox) -- groupLayout = QtGui.QVBoxLayout(groupBox) -- self.compressCheck = QtGui.QCheckBox(_('&Use file compression')) -+ groupLayout = QtWidgets.QVBoxLayout(groupBox) -+ self.compressCheck = QtWidgets.QCheckBox(_('&Use file compression')) - self.compressCheck.setChecked(localControl.compressed) - groupLayout.addWidget(self.compressCheck) -- self.encryptCheck = QtGui.QCheckBox(_('Use file &encryption')) -+ self.encryptCheck = QtWidgets.QCheckBox(_('Use file &encryption')) - self.encryptCheck.setChecked(localControl.encrypted) - groupLayout.addWidget(self.encryptCheck) - -- groupBox = QtGui.QGroupBox(_('Spell Check')) -+ groupBox = QtWidgets.QGroupBox(_('Spell Check')) - topLayout.addWidget(groupBox) -- groupLayout = QtGui.QHBoxLayout(groupBox) -- label = QtGui.QLabel(_('Language code or\ndictionary (optional)')) -+ groupLayout = QtWidgets.QHBoxLayout(groupBox) -+ label = QtWidgets.QLabel(_('Language code or\ndictionary (optional)')) - groupLayout.addWidget(label) -- self.spellCheckEdit = QtGui.QLineEdit() -+ self.spellCheckEdit = QtWidgets.QLineEdit() - self.spellCheckEdit.setText(self.localControl.spellCheckLang) - groupLayout.addWidget(self.spellCheckEdit) - -- groupBox = QtGui.QGroupBox(_('Math Fields')) -+ groupBox = QtWidgets.QGroupBox(_('Math Fields')) - topLayout.addWidget(groupBox) -- groupLayout = QtGui.QVBoxLayout(groupBox) -- self.zeroBlanks = QtGui.QCheckBox(_('&Treat blank fields as zeros')) -+ groupLayout = QtWidgets.QVBoxLayout(groupBox) -+ self.zeroBlanks = QtWidgets.QCheckBox(_('&Treat blank fields as zeros')) - self.zeroBlanks.setChecked(localControl.model.mathZeroBlanks) - groupLayout.addWidget(self.zeroBlanks) - -- ctrlLayout = QtGui.QHBoxLayout() -+ ctrlLayout = QtWidgets.QHBoxLayout() - topLayout.addLayout(ctrlLayout) - ctrlLayout.addStretch(0) -- okButton = QtGui.QPushButton(_('&OK')) -+ okButton = QtWidgets.QPushButton(_('&OK')) - ctrlLayout.addWidget(okButton) - okButton.clicked.connect(self.accept) -- cancelButton = QtGui.QPushButton(_('&Cancel')) -+ cancelButton = QtWidgets.QPushButton(_('&Cancel')) - ctrlLayout.addWidget(cancelButton) - cancelButton.clicked.connect(self.reject) - -@@ -230,7 +230,7 @@ class FilePropertiesDialog(QtGui.QDialog - super().reject() - - --class PasswordDialog(QtGui.QDialog): -+class PasswordDialog(QtWidgets.QDialog): - """Dialog for password entry and optional re-entry. - """ - remember = True -@@ -246,7 +246,7 @@ class PasswordDialog(QtGui.QDialog): - QtCore.Qt.WindowSystemMenuHint) - self.setWindowTitle(_('Encrypted File Password')) - self.password = '' -- topLayout = QtGui.QVBoxLayout(self) -+ topLayout = QtWidgets.QVBoxLayout(self) - self.setLayout(topLayout) - if fileLabel: - prompt = _('Type Password for "{0}":').format(fileLabel) -@@ -259,19 +259,19 @@ class PasswordDialog(QtGui.QDialog): - topLayout)) - self.editors[0].returnPressed.connect(self.editors[1].setFocus) - self.editors[-1].returnPressed.connect(self.accept) -- self.rememberCheck = QtGui.QCheckBox(_('Remember password during this ' -+ self.rememberCheck = QtWidgets.QCheckBox(_('Remember password during this ' - 'session')) - self.rememberCheck.setChecked(PasswordDialog.remember) - topLayout.addWidget(self.rememberCheck) - -- ctrlLayout = QtGui.QHBoxLayout() -+ ctrlLayout = QtWidgets.QHBoxLayout() - topLayout.addLayout(ctrlLayout) - ctrlLayout.addStretch(0) -- okButton = QtGui.QPushButton(_('&OK')) -+ okButton = QtWidgets.QPushButton(_('&OK')) - okButton.setAutoDefault(False) - ctrlLayout.addWidget(okButton) - okButton.clicked.connect(self.accept) -- cancelButton = QtGui.QPushButton(_('&Cancel')) -+ cancelButton = QtWidgets.QPushButton(_('&Cancel')) - cancelButton.setAutoDefault(False) - ctrlLayout.addWidget(cancelButton) - cancelButton.clicked.connect(self.reject) -@@ -283,10 +283,10 @@ class PasswordDialog(QtGui.QDialog): - labelText -- the text for the label - layout -- the layout to append it - """ -- label = QtGui.QLabel(labelText) -+ label = QtWidgets.QLabel(labelText) - layout.addWidget(label) -- editor = QtGui.QLineEdit() -- editor.setEchoMode(QtGui.QLineEdit.Password) -+ editor = QtWidgets.QLineEdit() -+ editor.setEchoMode(QtWidgets.QLineEdit.Password) - layout.addWidget(editor) - return editor - -@@ -296,10 +296,10 @@ class PasswordDialog(QtGui.QDialog): - self.password = self.editors[0].text() - PasswordDialog.remember = self.rememberCheck.isChecked() - if not self.password: -- QtGui.QMessageBox.warning(self, 'TreeLine', -+ QtWidgets.QMessageBox.warning(self, 'TreeLine', - _('Zero-length passwords are not permitted')) - elif len(self.editors) > 1 and self.editors[1].text() != self.password: -- QtGui.QMessageBox.warning(self, 'TreeLine', -+ QtWidgets.QMessageBox.warning(self, 'TreeLine', - _('Re-typed password did not match')) - else: - super().accept() -@@ -350,7 +350,7 @@ class TemplateFileItem: - return hash((self.langCode, self.displayName)) - - --class TemplateFileDialog(QtGui.QDialog): -+class TemplateFileDialog(QtWidgets.QDialog): - """Dialog for listing available template files. - """ - def __init__(self, title, heading, searchPaths, addDefault=True, -@@ -375,22 +375,22 @@ class TemplateFileDialog(QtGui.QDialog): - item.displayName = _('Default - Single Line Text') - self.templateItems.append(item) - -- topLayout = QtGui.QVBoxLayout(self) -+ topLayout = QtWidgets.QVBoxLayout(self) - self.setLayout(topLayout) -- groupBox = QtGui.QGroupBox(heading) -+ groupBox = QtWidgets.QGroupBox(heading) - topLayout.addWidget(groupBox) -- boxLayout = QtGui.QVBoxLayout(groupBox) -- self.listBox = QtGui.QListWidget() -+ boxLayout = QtWidgets.QVBoxLayout(groupBox) -+ self.listBox = QtWidgets.QListWidget() - boxLayout.addWidget(self.listBox) - self.listBox.itemDoubleClicked.connect(self.accept) - -- ctrlLayout = QtGui.QHBoxLayout() -+ ctrlLayout = QtWidgets.QHBoxLayout() - topLayout.addLayout(ctrlLayout) - ctrlLayout.addStretch(0) -- self.okButton = QtGui.QPushButton(_('&OK')) -+ self.okButton = QtWidgets.QPushButton(_('&OK')) - ctrlLayout.addWidget(self.okButton) - self.okButton.clicked.connect(self.accept) -- cancelButton = QtGui.QPushButton(_('&Cancel')) -+ cancelButton = QtWidgets.QPushButton(_('&Cancel')) - ctrlLayout.addWidget(cancelButton) - cancelButton.clicked.connect(self.reject) - -@@ -442,7 +442,7 @@ class TemplateFileDialog(QtGui.QDialog): - return item.name - - --class FindFilterDialog(QtGui.QDialog): -+class FindFilterDialog(QtWidgets.QDialog): - """Dialog for searching for text within tree titles and data. - """ - dialogShown = QtCore.pyqtSignal(bool) -@@ -461,71 +461,71 @@ class FindFilterDialog(QtGui.QDialog): - self.setAttribute(QtCore.Qt.WA_QuitOnClose, False) - self.setWindowFlags(QtCore.Qt.Window | QtCore.Qt.WindowStaysOnTopHint) - -- topLayout = QtGui.QVBoxLayout(self) -+ topLayout = QtWidgets.QVBoxLayout(self) - self.setLayout(topLayout) - -- textBox = QtGui.QGroupBox(_('&Search Text')) -+ textBox = QtWidgets.QGroupBox(_('&Search Text')) - topLayout.addWidget(textBox) -- textLayout = QtGui.QVBoxLayout(textBox) -- self.textEntry = QtGui.QLineEdit() -+ textLayout = QtWidgets.QVBoxLayout(textBox) -+ self.textEntry = QtWidgets.QLineEdit() - textLayout.addWidget(self.textEntry) - self.textEntry.textEdited.connect(self.updateAvail) - -- horizLayout = QtGui.QHBoxLayout() -+ horizLayout = QtWidgets.QHBoxLayout() - topLayout.addLayout(horizLayout) - -- whatBox = QtGui.QGroupBox(_('What to Search')) -+ whatBox = QtWidgets.QGroupBox(_('What to Search')) - horizLayout.addWidget(whatBox) -- whatLayout = QtGui.QVBoxLayout(whatBox) -- self.whatButtons = QtGui.QButtonGroup(self) -- button = QtGui.QRadioButton(_('Full &data')) -+ whatLayout = QtWidgets.QVBoxLayout(whatBox) -+ self.whatButtons = QtWidgets.QButtonGroup(self) -+ button = QtWidgets.QRadioButton(_('Full &data')) - self.whatButtons.addButton(button, FindFilterDialog.fullData) - whatLayout.addWidget(button) -- button = QtGui.QRadioButton(_('&Titles only')) -+ button = QtWidgets.QRadioButton(_('&Titles only')) - self.whatButtons.addButton(button, FindFilterDialog.titlesOnly) - whatLayout.addWidget(button) - self.whatButtons.button(FindFilterDialog.fullData).setChecked(True) - -- howBox = QtGui.QGroupBox(_('How to Search')) -+ howBox = QtWidgets.QGroupBox(_('How to Search')) - horizLayout.addWidget(howBox) -- howLayout = QtGui.QVBoxLayout(howBox) -- self.howButtons = QtGui.QButtonGroup(self) -- button = QtGui.QRadioButton(_('&Key words')) -+ howLayout = QtWidgets.QVBoxLayout(howBox) -+ self.howButtons = QtWidgets.QButtonGroup(self) -+ button = QtWidgets.QRadioButton(_('&Key words')) - self.howButtons.addButton(button, FindFilterDialog.keyWords) - howLayout.addWidget(button) -- button = QtGui.QRadioButton(_('Key full &words')) -+ button = QtWidgets.QRadioButton(_('Key full &words')) - self.howButtons.addButton(button, FindFilterDialog.fullWords) - howLayout.addWidget(button) -- button = QtGui.QRadioButton(_('F&ull phrase')) -+ button = QtWidgets.QRadioButton(_('F&ull phrase')) - self.howButtons.addButton(button, FindFilterDialog.fullPhrase) - howLayout.addWidget(button) -- button = QtGui.QRadioButton(_('&Regular expression')) -+ button = QtWidgets.QRadioButton(_('&Regular expression')) - self.howButtons.addButton(button, FindFilterDialog.regExp) - howLayout.addWidget(button) - self.howButtons.button(FindFilterDialog.keyWords).setChecked(True) - -- ctrlLayout = QtGui.QHBoxLayout() -+ ctrlLayout = QtWidgets.QHBoxLayout() - topLayout.addLayout(ctrlLayout) - if self.dialogType == FindFilterDialog.findDialog: - self.setWindowTitle(_('Find')) -- self.previousButton = QtGui.QPushButton(_('Find &Previous')) -+ self.previousButton = QtWidgets.QPushButton(_('Find &Previous')) - ctrlLayout.addWidget(self.previousButton) - self.previousButton.clicked.connect(self.findPrevious) -- self.nextButton = QtGui.QPushButton(_('Find &Next')) -+ self.nextButton = QtWidgets.QPushButton(_('Find &Next')) - self.nextButton.setDefault(True) - ctrlLayout.addWidget(self.nextButton) - self.nextButton.clicked.connect(self.findNext) -- self.resultLabel = QtGui.QLabel() -+ self.resultLabel = QtWidgets.QLabel() - topLayout.addWidget(self.resultLabel) - else: - self.setWindowTitle(_('Filter')) -- self.filterButton = QtGui.QPushButton(_('&Filter')) -+ self.filterButton = QtWidgets.QPushButton(_('&Filter')) - ctrlLayout.addWidget(self.filterButton) - self.filterButton.clicked.connect(self.startFilter) -- self.endFilterButton = QtGui.QPushButton(_('&End Filter')) -+ self.endFilterButton = QtWidgets.QPushButton(_('&End Filter')) - ctrlLayout.addWidget(self.endFilterButton) - self.endFilterButton.clicked.connect(self.endFilter) -- closeButton = QtGui.QPushButton(_('&Close')) -+ closeButton = QtWidgets.QPushButton(_('&Close')) - ctrlLayout.addWidget(closeButton) - closeButton.clicked.connect(self.close) - self.updateAvail('') -@@ -572,7 +572,7 @@ class FindFilterDialog(QtGui.QDialog): - try: - regExp = re.compile(text) - except re.error: -- QtGui.QMessageBox.warning(self, 'TreeLine', -+ QtWidgets.QMessageBox.warning(self, 'TreeLine', - _('Error - invalid regular expression')) - return - result = control.findNodesByRegExp([regExp], titlesOnly, forward) -@@ -609,7 +609,7 @@ class FindFilterDialog(QtGui.QDialog): - try: - re.compile(self.textEntry.text()) - except re.error: -- QtGui.QMessageBox.warning(self, 'TreeLine', -+ QtWidgets.QMessageBox.warning(self, 'TreeLine', - _('Error - invalid regular expression')) - return - window = globalref.mainControl.activeControl.activeWindow -@@ -638,7 +638,7 @@ class FindFilterDialog(QtGui.QDialog): - self.dialogShown.emit(False) - - --class FindReplaceDialog(QtGui.QDialog): -+class FindReplaceDialog(QtWidgets.QDialog): - """Dialog for finding and replacing text in the node data. - """ - dialogShown = QtCore.pyqtSignal(bool) -@@ -655,73 +655,73 @@ class FindReplaceDialog(QtGui.QDialog): - self.setWindowTitle(_('Find and Replace')) - - self.matchedNode = None -- topLayout = QtGui.QGridLayout(self) -+ topLayout = QtWidgets.QGridLayout(self) - self.setLayout(topLayout) - -- textBox = QtGui.QGroupBox(_('&Search Text')) -+ textBox = QtWidgets.QGroupBox(_('&Search Text')) - topLayout.addWidget(textBox, 0, 0) -- textLayout = QtGui.QVBoxLayout(textBox) -- self.textEntry = QtGui.QLineEdit() -+ textLayout = QtWidgets.QVBoxLayout(textBox) -+ self.textEntry = QtWidgets.QLineEdit() - textLayout.addWidget(self.textEntry) - self.textEntry.textEdited.connect(self.updateAvail) - self.textEntry.textEdited.connect(self.clearMatch) - -- replaceBox = QtGui.QGroupBox(_('Replacement &Text')) -+ replaceBox = QtWidgets.QGroupBox(_('Replacement &Text')) - topLayout.addWidget(replaceBox, 0, 1) -- replaceLayout = QtGui.QVBoxLayout(replaceBox) -- self.replaceEntry = QtGui.QLineEdit() -+ replaceLayout = QtWidgets.QVBoxLayout(replaceBox) -+ self.replaceEntry = QtWidgets.QLineEdit() - replaceLayout.addWidget(self.replaceEntry) - -- howBox = QtGui.QGroupBox(_('How to Search')) -+ howBox = QtWidgets.QGroupBox(_('How to Search')) - topLayout.addWidget(howBox, 1, 0, 2, 1) -- howLayout = QtGui.QVBoxLayout(howBox) -- self.howButtons = QtGui.QButtonGroup(self) -- button = QtGui.QRadioButton(_('Any &match')) -+ howLayout = QtWidgets.QVBoxLayout(howBox) -+ self.howButtons = QtWidgets.QButtonGroup(self) -+ button = QtWidgets.QRadioButton(_('Any &match')) - self.howButtons.addButton(button, FindReplaceDialog.anyMatch) - howLayout.addWidget(button) -- button = QtGui.QRadioButton(_('Full &words')) -+ button = QtWidgets.QRadioButton(_('Full &words')) - self.howButtons.addButton(button, FindReplaceDialog.fullWord) - howLayout.addWidget(button) -- button = QtGui.QRadioButton(_('Re&gular expression')) -+ button = QtWidgets.QRadioButton(_('Re&gular expression')) - self.howButtons.addButton(button, FindReplaceDialog.regExp) - howLayout.addWidget(button) - self.howButtons.button(FindReplaceDialog.anyMatch).setChecked(True) - self.howButtons.buttonClicked.connect(self.clearMatch) - -- typeBox = QtGui.QGroupBox(_('&Node Type')) -+ typeBox = QtWidgets.QGroupBox(_('&Node Type')) - topLayout.addWidget(typeBox, 1, 1) -- typeLayout = QtGui.QVBoxLayout(typeBox) -- self.typeCombo = QtGui.QComboBox() -+ typeLayout = QtWidgets.QVBoxLayout(typeBox) -+ self.typeCombo = QtWidgets.QComboBox() - typeLayout.addWidget(self.typeCombo) - self.typeCombo.currentIndexChanged.connect(self.loadFieldNames) - -- fieldBox = QtGui.QGroupBox(_('N&ode Fields')) -+ fieldBox = QtWidgets.QGroupBox(_('N&ode Fields')) - topLayout.addWidget(fieldBox, 2, 1) -- fieldLayout = QtGui.QVBoxLayout(fieldBox) -- self.fieldCombo = QtGui.QComboBox() -+ fieldLayout = QtWidgets.QVBoxLayout(fieldBox) -+ self.fieldCombo = QtWidgets.QComboBox() - fieldLayout.addWidget(self.fieldCombo) - self.fieldCombo.currentIndexChanged.connect(self.clearMatch) - -- ctrlLayout = QtGui.QHBoxLayout() -+ ctrlLayout = QtWidgets.QHBoxLayout() - topLayout.addLayout(ctrlLayout, 3, 0, 1, 2) -- self.previousButton = QtGui.QPushButton(_('Find &Previous')) -+ self.previousButton = QtWidgets.QPushButton(_('Find &Previous')) - ctrlLayout.addWidget(self.previousButton) - self.previousButton.clicked.connect(self.findPrevious) -- self.nextButton = QtGui.QPushButton(_('&Find Next')) -+ self.nextButton = QtWidgets.QPushButton(_('&Find Next')) - self.nextButton.setDefault(True) - ctrlLayout.addWidget(self.nextButton) - self.nextButton.clicked.connect(self.findNext) -- self.replaceButton = QtGui.QPushButton(_('&Replace')) -+ self.replaceButton = QtWidgets.QPushButton(_('&Replace')) - ctrlLayout.addWidget(self.replaceButton) - self.replaceButton.clicked.connect(self.replace) -- self.replaceAllButton = QtGui.QPushButton(_('Replace &All')) -+ self.replaceAllButton = QtWidgets.QPushButton(_('Replace &All')) - ctrlLayout.addWidget(self.replaceAllButton) - self.replaceAllButton.clicked.connect(self.replaceAll) -- closeButton = QtGui.QPushButton(_('&Close')) -+ closeButton = QtWidgets.QPushButton(_('&Close')) - ctrlLayout.addWidget(closeButton) - closeButton.clicked.connect(self.close) - -- self.resultLabel = QtGui.QLabel() -+ self.resultLabel = QtWidgets.QLabel() - topLayout.addWidget(self.resultLabel, 4, 0, 1, 2) - self.loadTypeNames() - self.updateAvail() -@@ -814,7 +814,7 @@ class FindReplaceDialog(QtGui.QDialog): - try: - searchText, regExpObj, typeName, fieldName = self.findParameters() - except re.error: -- QtGui.QMessageBox.warning(self, 'TreeLine', -+ QtWidgets.QMessageBox.warning(self, 'TreeLine', - _('Error - invalid regular expression')) - self.updateAvail() - return -@@ -848,7 +848,7 @@ class FindReplaceDialog(QtGui.QDialog): - fieldName, replaceText): - self.find() - else: -- QtGui.QMessageBox.warning(self, 'TreeLine', -+ QtWidgets.QMessageBox.warning(self, 'TreeLine', - _('Error - replacement failed')) - self.matchedNode = None - self.updateAvail() -@@ -874,7 +874,7 @@ class FindReplaceDialog(QtGui.QDialog): - self.dialogShown.emit(False) - - --class SortDialog(QtGui.QDialog): -+class SortDialog(QtWidgets.QDialog): - """Dialog for defining sort operations. - """ - dialogShown = QtCore.pyqtSignal(bool) -@@ -892,64 +892,64 @@ class SortDialog(QtGui.QDialog): - self.setWindowFlags(QtCore.Qt.Window | QtCore.Qt.WindowStaysOnTopHint) - self.setWindowTitle(_('Sort Nodes')) - -- topLayout = QtGui.QVBoxLayout(self) -+ topLayout = QtWidgets.QVBoxLayout(self) - self.setLayout(topLayout) -- horizLayout = QtGui.QHBoxLayout() -+ horizLayout = QtWidgets.QHBoxLayout() - topLayout.addLayout(horizLayout) -- whatBox = QtGui.QGroupBox(_('What to Sort')) -+ whatBox = QtWidgets.QGroupBox(_('What to Sort')) - horizLayout.addWidget(whatBox) -- whatLayout = QtGui.QVBoxLayout(whatBox) -- self.whatButtons = QtGui.QButtonGroup(self) -- button = QtGui.QRadioButton(_('&Entire tree')) -+ whatLayout = QtWidgets.QVBoxLayout(whatBox) -+ self.whatButtons = QtWidgets.QButtonGroup(self) -+ button = QtWidgets.QRadioButton(_('&Entire tree')) - self.whatButtons.addButton(button, SortDialog.fullTree) - whatLayout.addWidget(button) -- button = QtGui.QRadioButton(_('Selected &branches')) -+ button = QtWidgets.QRadioButton(_('Selected &branches')) - self.whatButtons.addButton(button, SortDialog.selectBranch) - whatLayout.addWidget(button) -- button = QtGui.QRadioButton(_('Selection\'s childre&n')) -+ button = QtWidgets.QRadioButton(_('Selection\'s childre&n')) - self.whatButtons.addButton(button, SortDialog.selectChildren) - whatLayout.addWidget(button) -- button = QtGui.QRadioButton(_('Selection\'s &siblings')) -+ button = QtWidgets.QRadioButton(_('Selection\'s &siblings')) - self.whatButtons.addButton(button, SortDialog.selectSiblings) - whatLayout.addWidget(button) - self.whatButtons.button(SortDialog.fullTree).setChecked(True) - -- vertLayout = QtGui.QVBoxLayout() -+ vertLayout = QtWidgets.QVBoxLayout() - horizLayout.addLayout(vertLayout) -- methodBox = QtGui.QGroupBox(_('Sort Method')) -+ methodBox = QtWidgets.QGroupBox(_('Sort Method')) - vertLayout.addWidget(methodBox) -- methodLayout = QtGui.QVBoxLayout(methodBox) -- self.methodButtons = QtGui.QButtonGroup(self) -- button = QtGui.QRadioButton(_('&Predefined Key Fields')) -+ methodLayout = QtWidgets.QVBoxLayout(methodBox) -+ self.methodButtons = QtWidgets.QButtonGroup(self) -+ button = QtWidgets.QRadioButton(_('&Predefined Key Fields')) - self.methodButtons.addButton(button, SortDialog.fieldSort) - methodLayout.addWidget(button) -- button = QtGui.QRadioButton(_('Node &Titles')) -+ button = QtWidgets.QRadioButton(_('Node &Titles')) - self.methodButtons.addButton(button, SortDialog.titleSort) - methodLayout.addWidget(button) - self.methodButtons.button(SortDialog.fieldSort).setChecked(True) - -- directionBox = QtGui.QGroupBox(_('Sort Direction')) -+ directionBox = QtWidgets.QGroupBox(_('Sort Direction')) - vertLayout.addWidget(directionBox) -- directionLayout = QtGui.QVBoxLayout(directionBox) -- self.directionButtons = QtGui.QButtonGroup(self) -- button = QtGui.QRadioButton(_('&Forward')) -+ directionLayout = QtWidgets.QVBoxLayout(directionBox) -+ self.directionButtons = QtWidgets.QButtonGroup(self) -+ button = QtWidgets.QRadioButton(_('&Forward')) - self.directionButtons.addButton(button, SortDialog.forward) - directionLayout.addWidget(button) -- button = QtGui.QRadioButton(_('&Reverse')) -+ button = QtWidgets.QRadioButton(_('&Reverse')) - self.directionButtons.addButton(button, SortDialog.reverse) - directionLayout.addWidget(button) - self.directionButtons.button(SortDialog.forward).setChecked(True) - -- ctrlLayout = QtGui.QHBoxLayout() -+ ctrlLayout = QtWidgets.QHBoxLayout() - topLayout.addLayout(ctrlLayout) - ctrlLayout.addStretch() -- okButton = QtGui.QPushButton(_('&OK')) -+ okButton = QtWidgets.QPushButton(_('&OK')) - ctrlLayout.addWidget(okButton) - okButton.clicked.connect(self.sortAndClose) -- applyButton = QtGui.QPushButton(_('&Apply')) -+ applyButton = QtWidgets.QPushButton(_('&Apply')) - ctrlLayout.addWidget(applyButton) - applyButton.clicked.connect(self.sortNodes) -- closeButton = QtGui.QPushButton(_('&Close')) -+ closeButton = QtWidgets.QPushButton(_('&Close')) - ctrlLayout.addWidget(closeButton) - closeButton.clicked.connect(self.close) - self.updateCommandsAvail() -@@ -975,7 +975,7 @@ class SortDialog(QtGui.QDialog): - def sortNodes(self): - """Perform the sorting operation. - """ -- QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) -+ QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) - control = globalref.mainControl.activeControl - selNodes = control.currentSelectionModel().selectedNodes() - if self.whatButtons.checkedId() == SortDialog.fullTree: -@@ -1002,7 +1002,7 @@ class SortDialog(QtGui.QDialog): - for node in selNodes: - node.sortChildrenByTitle(False, forward) - control.updateAll() -- QtGui.QApplication.restoreOverrideCursor() -+ QtWidgets.QApplication.restoreOverrideCursor() - - def sortAndClose(self): - """Perform the sorting operation and close the dialog. -@@ -1019,7 +1019,7 @@ class SortDialog(QtGui.QDialog): - self.dialogShown.emit(False) - - --class NumberingDialog(QtGui.QDialog): -+class NumberingDialog(QtWidgets.QDialog): - """Dialog for updating node nuumbering fields. - """ - dialogShown = QtCore.pyqtSignal(bool) -@@ -1036,58 +1036,58 @@ class NumberingDialog(QtGui.QDialog): - self.setWindowFlags(QtCore.Qt.Window | QtCore.Qt.WindowStaysOnTopHint) - self.setWindowTitle(_('Update Node Numbering')) - -- topLayout = QtGui.QVBoxLayout(self) -+ topLayout = QtWidgets.QVBoxLayout(self) - self.setLayout(topLayout) -- whatBox = QtGui.QGroupBox(_('What to Update')) -+ whatBox = QtWidgets.QGroupBox(_('What to Update')) - topLayout.addWidget(whatBox) -- whatLayout = QtGui.QVBoxLayout(whatBox) -- self.whatButtons = QtGui.QButtonGroup(self) -- button = QtGui.QRadioButton(_('&Entire tree')) -+ whatLayout = QtWidgets.QVBoxLayout(whatBox) -+ self.whatButtons = QtWidgets.QButtonGroup(self) -+ button = QtWidgets.QRadioButton(_('&Entire tree')) - self.whatButtons.addButton(button, NumberingDialog.fullTree) - whatLayout.addWidget(button) -- button = QtGui.QRadioButton(_('Selected &branches')) -+ button = QtWidgets.QRadioButton(_('Selected &branches')) - self.whatButtons.addButton(button, NumberingDialog.selectBranch) - whatLayout.addWidget(button) -- button = QtGui.QRadioButton(_('&Selection\'s children')) -+ button = QtWidgets.QRadioButton(_('&Selection\'s children')) - self.whatButtons.addButton(button, NumberingDialog.selectChildren) - whatLayout.addWidget(button) - self.whatButtons.button(NumberingDialog.fullTree).setChecked(True) - -- rootBox = QtGui.QGroupBox(_('Root Node')) -+ rootBox = QtWidgets.QGroupBox(_('Root Node')) - topLayout.addWidget(rootBox) -- rootLayout = QtGui.QVBoxLayout(rootBox) -- self.rootCheck = QtGui.QCheckBox(_('Include top-level nodes')) -+ rootLayout = QtWidgets.QVBoxLayout(rootBox) -+ self.rootCheck = QtWidgets.QCheckBox(_('Include top-level nodes')) - rootLayout.addWidget(self.rootCheck) - self.rootCheck.setChecked(True) - -- noFieldBox = QtGui.QGroupBox(_('Handling Nodes without Numbering ' -+ noFieldBox = QtWidgets.QGroupBox(_('Handling Nodes without Numbering ' - 'Fields')) - topLayout.addWidget(noFieldBox) -- noFieldLayout = QtGui.QVBoxLayout(noFieldBox) -- self.noFieldButtons = QtGui.QButtonGroup(self) -- button = QtGui.QRadioButton(_('&Ignore and skip')) -+ noFieldLayout = QtWidgets.QVBoxLayout(noFieldBox) -+ self.noFieldButtons = QtWidgets.QButtonGroup(self) -+ button = QtWidgets.QRadioButton(_('&Ignore and skip')) - self.noFieldButtons.addButton(button, NumberingDialog.ignoreNoField) - noFieldLayout.addWidget(button) -- button = QtGui.QRadioButton(_('&Restart numbers for next siblings')) -+ button = QtWidgets.QRadioButton(_('&Restart numbers for next siblings')) - self.noFieldButtons.addButton(button, - NumberingDialog.restartAfterNoField) - noFieldLayout.addWidget(button) -- button = QtGui.QRadioButton(_('Reserve &numbers')) -+ button = QtWidgets.QRadioButton(_('Reserve &numbers')) - self.noFieldButtons.addButton(button, NumberingDialog.reserveNoField) - noFieldLayout.addWidget(button) - self.noFieldButtons.button(NumberingDialog. - ignoreNoField).setChecked(True) - -- ctrlLayout = QtGui.QHBoxLayout() -+ ctrlLayout = QtWidgets.QHBoxLayout() - topLayout.addLayout(ctrlLayout) - ctrlLayout.addStretch() -- okButton = QtGui.QPushButton(_('&OK')) -+ okButton = QtWidgets.QPushButton(_('&OK')) - ctrlLayout.addWidget(okButton) - okButton.clicked.connect(self.numberAndClose) -- applyButton = QtGui.QPushButton(_('&Apply')) -+ applyButton = QtWidgets.QPushButton(_('&Apply')) - ctrlLayout.addWidget(applyButton) - applyButton.clicked.connect(self.updateNumbering) -- closeButton = QtGui.QPushButton(_('&Close')) -+ closeButton = QtWidgets.QPushButton(_('&Close')) - ctrlLayout.addWidget(closeButton) - closeButton.clicked.connect(self.close) - self.updateCommandsAvail() -@@ -1114,14 +1114,14 @@ class NumberingDialog(QtGui.QDialog): - fieldDict = (globalref.mainControl.activeControl.model.formats. - numberingFieldDict()) - if not fieldDict: -- QtGui.QMessageBox.warning(self, _('TreeLine Numbering'), -+ QtWidgets.QMessageBox.warning(self, _('TreeLine Numbering'), - _('No numbering fields were found in data types')) - return fieldDict - - def updateNumbering(self): - """Perform the numbering update operation. - """ -- QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) -+ QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) - fieldDict = self.checkForNumberingFields() - if fieldDict: - control = globalref.mainControl.activeControl -@@ -1145,7 +1145,7 @@ class NumberingDialog(QtGui.QDialog): - if not restartSetting: - startNum[0] += 1 - control.updateAll() -- QtGui.QApplication.restoreOverrideCursor() -+ QtWidgets.QApplication.restoreOverrideCursor() - - def numberAndClose(self): - """Perform the numbering update operation and close the dialog. -@@ -1171,7 +1171,7 @@ menuNames = collections.OrderedDict([(N_ - (N_('Window Menu'), _('Window')), - (N_('Help Menu'), _('Help'))]) - --class CustomShortcutsDialog(QtGui.QDialog): -+class CustomShortcutsDialog(QtWidgets.QDialog): - """Dialog for customizing keyboard commands. - """ - def __init__(self, allActions, parent=None): -@@ -1185,12 +1185,12 @@ class CustomShortcutsDialog(QtGui.QDialo - self.setWindowFlags(QtCore.Qt.Dialog | QtCore.Qt.WindowTitleHint | - QtCore.Qt.WindowSystemMenuHint) - self.setWindowTitle(_('Keyboard Shortcuts')) -- topLayout = QtGui.QVBoxLayout(self) -+ topLayout = QtWidgets.QVBoxLayout(self) - self.setLayout(topLayout) -- scrollArea = QtGui.QScrollArea() -+ scrollArea = QtWidgets.QScrollArea() - topLayout.addWidget(scrollArea) -- viewport = QtGui.QWidget() -- viewLayout = QtGui.QGridLayout(viewport) -+ viewport = QtWidgets.QWidget() -+ viewLayout = QtWidgets.QGridLayout(viewport) - scrollArea.setWidget(viewport) - scrollArea.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - scrollArea.setWidgetResizable(True) -@@ -1204,22 +1204,22 @@ class CustomShortcutsDialog(QtGui.QDialo - pass - else: - text = '{0} > {1}'.format(category, action.toolTip()) -- label = QtGui.QLabel(text) -+ label = QtWidgets.QLabel(text) - viewLayout.addWidget(label, i, 0) - editor = KeyLineEdit(keyOption, action, self) - viewLayout.addWidget(editor, i, 1) - self.editors.append(editor) - -- ctrlLayout = QtGui.QHBoxLayout() -+ ctrlLayout = QtWidgets.QHBoxLayout() - topLayout.addLayout(ctrlLayout) -- restoreButton = QtGui.QPushButton(_('&Restore Defaults')) -+ restoreButton = QtWidgets.QPushButton(_('&Restore Defaults')) - ctrlLayout.addWidget(restoreButton) - restoreButton.clicked.connect(self.restoreDefaults) - ctrlLayout.addStretch(0) -- self.okButton = QtGui.QPushButton(_('&OK')) -+ self.okButton = QtWidgets.QPushButton(_('&OK')) - ctrlLayout.addWidget(self.okButton) - self.okButton.clicked.connect(self.accept) -- cancelButton = QtGui.QPushButton(_('&Cancel')) -+ cancelButton = QtWidgets.QPushButton(_('&Cancel')) - ctrlLayout.addWidget(cancelButton) - cancelButton.clicked.connect(self.reject) - self.editors[0].setFocus() -@@ -1243,7 +1243,7 @@ class CustomShortcutsDialog(QtGui.QDialo - super().accept() - - --class KeyLineEdit(QtGui.QLineEdit): -+class KeyLineEdit(QtWidgets.QLineEdit): - """Line editor for keyboad sequence entry. - """ - usedKeySet = set() -@@ -1354,7 +1354,7 @@ class KeyLineEdit(QtGui.QLineEdit): - self.modified = True - else: - text = _('Key {0} is already used').format(keyText) -- QtGui.QMessageBox.warning(self.parent(), 'TreeLine', text) -+ QtWidgets.QMessageBox.warning(self.parent(), 'TreeLine', text) - event.accept() - - def contextMenuEvent(self, event): -@@ -1363,7 +1363,7 @@ class KeyLineEdit(QtGui.QLineEdit): - Arguments: - event -- the menu event - """ -- menu = QtGui.QMenu(self) -+ menu = QtWidgets.QMenu(self) - menu.addAction(_('Clear &Key'), self.clearKey) - menu.exec_(event.globalPos()) - -@@ -1409,7 +1409,7 @@ class KeyLineEdit(QtGui.QLineEdit): - super().focusInEvent(event) - - --class CustomToolbarDialog(QtGui.QDialog): -+class CustomToolbarDialog(QtWidgets.QDialog): - """Dialog for customizing toolbar buttons. - """ - separatorString = _('--Separator--') -@@ -1433,90 +1433,90 @@ class CustomToolbarDialog(QtGui.QDialog) - self.availableCommands = [] - self.toolbarLists = [] - -- topLayout = QtGui.QVBoxLayout(self) -+ topLayout = QtWidgets.QVBoxLayout(self) - self.setLayout(topLayout) -- gridLayout = QtGui.QGridLayout() -+ gridLayout = QtWidgets.QGridLayout() - topLayout.addLayout(gridLayout) - -- sizeBox = QtGui.QGroupBox(_('Toolbar &Size')) -+ sizeBox = QtWidgets.QGroupBox(_('Toolbar &Size')) - gridLayout.addWidget(sizeBox, 0, 0, 1, 2) -- sizeLayout = QtGui.QVBoxLayout(sizeBox) -- self.sizeCombo = QtGui.QComboBox() -+ sizeLayout = QtWidgets.QVBoxLayout(sizeBox) -+ self.sizeCombo = QtWidgets.QComboBox() - sizeLayout.addWidget(self.sizeCombo) - self.sizeCombo.addItems([_('Small Icons'), _('Large Icons')]) - self.sizeCombo.currentIndexChanged.connect(self.setModified) - -- numberBox = QtGui.QGroupBox(_('Toolbar Quantity')) -+ numberBox = QtWidgets.QGroupBox(_('Toolbar Quantity')) - gridLayout.addWidget(numberBox, 0, 2) -- numberLayout = QtGui.QHBoxLayout(numberBox) -- self.quantitySpin = QtGui.QSpinBox() -+ numberLayout = QtWidgets.QHBoxLayout(numberBox) -+ self.quantitySpin = QtWidgets.QSpinBox() - numberLayout.addWidget(self.quantitySpin) - self.quantitySpin.setRange(0, 20) -- numberlabel = QtGui.QLabel(_('&Toolbars')) -+ numberlabel = QtWidgets.QLabel(_('&Toolbars')) - numberLayout.addWidget(numberlabel) - numberlabel.setBuddy(self.quantitySpin) - self.quantitySpin.valueChanged.connect(self.changeQuantity) - -- availableBox = QtGui.QGroupBox(_('A&vailable Commands')) -+ availableBox = QtWidgets.QGroupBox(_('A&vailable Commands')) - gridLayout.addWidget(availableBox, 1, 0) -- availableLayout = QtGui.QVBoxLayout(availableBox) -- menuCombo = QtGui.QComboBox() -+ availableLayout = QtWidgets.QVBoxLayout(availableBox) -+ menuCombo = QtWidgets.QComboBox() - availableLayout.addWidget(menuCombo) - menuCombo.addItems([_(name) for name in menuNames.keys()]) - menuCombo.currentIndexChanged.connect(self.updateAvailableCommands) - -- self.availableListWidget = QtGui.QListWidget() -+ self.availableListWidget = QtWidgets.QListWidget() - availableLayout.addWidget(self.availableListWidget) - -- buttonLayout = QtGui.QVBoxLayout() -+ buttonLayout = QtWidgets.QVBoxLayout() - gridLayout.addLayout(buttonLayout, 1, 1) -- self.addButton = QtGui.QPushButton('>>') -+ self.addButton = QtWidgets.QPushButton('>>') - buttonLayout.addWidget(self.addButton) - self.addButton.setMaximumWidth(self.addButton.sizeHint().height()) - self.addButton.clicked.connect(self.addTool) - -- self.removeButton = QtGui.QPushButton('<<') -+ self.removeButton = QtWidgets.QPushButton('<<') - buttonLayout.addWidget(self.removeButton) - self.removeButton.setMaximumWidth(self.removeButton.sizeHint(). - height()) - self.removeButton.clicked.connect(self.removeTool) - -- toolbarBox = QtGui.QGroupBox(_('Tool&bar Commands')) -+ toolbarBox = QtWidgets.QGroupBox(_('Tool&bar Commands')) - gridLayout.addWidget(toolbarBox, 1, 2) -- toolbarLayout = QtGui.QVBoxLayout(toolbarBox) -- self.toolbarCombo = QtGui.QComboBox() -+ toolbarLayout = QtWidgets.QVBoxLayout(toolbarBox) -+ self.toolbarCombo = QtWidgets.QComboBox() - toolbarLayout.addWidget(self.toolbarCombo) - self.toolbarCombo.currentIndexChanged.connect(self. - updateToolbarCommands) - -- self.toolbarListWidget = QtGui.QListWidget() -+ self.toolbarListWidget = QtWidgets.QListWidget() - toolbarLayout.addWidget(self.toolbarListWidget) - self.toolbarListWidget.currentRowChanged.connect(self. - setButtonsAvailable) - -- moveLayout = QtGui.QHBoxLayout() -+ moveLayout = QtWidgets.QHBoxLayout() - toolbarLayout.addLayout(moveLayout) -- self.moveUpButton = QtGui.QPushButton(_('Move &Up')) -+ self.moveUpButton = QtWidgets.QPushButton(_('Move &Up')) - moveLayout.addWidget(self.moveUpButton) - self.moveUpButton.clicked.connect(self.moveUp) -- self.moveDownButton = QtGui.QPushButton(_('Move &Down')) -+ self.moveDownButton = QtWidgets.QPushButton(_('Move &Down')) - moveLayout.addWidget(self.moveDownButton) - self.moveDownButton.clicked.connect(self.moveDown) - -- ctrlLayout = QtGui.QHBoxLayout() -+ ctrlLayout = QtWidgets.QHBoxLayout() - topLayout.addLayout(ctrlLayout) -- restoreButton = QtGui.QPushButton(_('&Restore Defaults')) -+ restoreButton = QtWidgets.QPushButton(_('&Restore Defaults')) - ctrlLayout.addWidget(restoreButton) - restoreButton.clicked.connect(self.restoreDefaults) - ctrlLayout.addStretch() -- self.okButton = QtGui.QPushButton(_('&OK')) -+ self.okButton = QtWidgets.QPushButton(_('&OK')) - ctrlLayout.addWidget(self.okButton) - self.okButton.clicked.connect(self.accept) -- self.applyButton = QtGui.QPushButton(_('&Apply')) -+ self.applyButton = QtWidgets.QPushButton(_('&Apply')) - ctrlLayout.addWidget(self.applyButton) - self.applyButton.clicked.connect(self.applyChanges) - self.applyButton.setEnabled(False) -- cancelButton = QtGui.QPushButton(_('&Cancel')) -+ cancelButton = QtWidgets.QPushButton(_('&Cancel')) - ctrlLayout.addWidget(cancelButton) - cancelButton.clicked.connect(self.reject) - -@@ -1595,9 +1595,9 @@ class CustomToolbarDialog(QtGui.QDialog) - icon = action.icon() - if not icon.isNull(): - self.availableCommands.append(option.name) -- QtGui.QListWidgetItem(icon, action.toolTip(), -+ QtWidgets.QListWidgetItem(icon, action.toolTip(), - self.availableListWidget) -- QtGui.QListWidgetItem(CustomToolbarDialog.separatorString, -+ QtWidgets.QListWidgetItem(CustomToolbarDialog.separatorString, - self.availableListWidget) - self.availableListWidget.setCurrentRow(0) - -@@ -1613,10 +1613,10 @@ class CustomToolbarDialog(QtGui.QDialog) - for command in self.toolbarLists[toolbarNum]: - if command: - action = self.allActions[command] -- QtGui.QListWidgetItem(action.icon(), action.toolTip(), -+ QtWidgets.QListWidgetItem(action.icon(), action.toolTip(), - self.toolbarListWidget) - else: # separator -- QtGui.QListWidgetItem(CustomToolbarDialog.separatorString, -+ QtWidgets.QListWidgetItem(CustomToolbarDialog.separatorString, - self.toolbarListWidget) - if self.toolbarLists[toolbarNum]: - self.toolbarListWidget.setCurrentRow(0) -@@ -1642,10 +1642,10 @@ class CustomToolbarDialog(QtGui.QDialog) - command = self.availableCommands[self.availableListWidget. - currentRow()] - action = self.allActions[command] -- item = QtGui.QListWidgetItem(action.icon(), action.toolTip()) -+ item = QtWidgets.QListWidgetItem(action.icon(), action.toolTip()) - except IndexError: - command = '' -- item = QtGui.QListWidgetItem(CustomToolbarDialog.separatorString) -+ item = QtWidgets.QListWidgetItem(CustomToolbarDialog.separatorString) - if self.toolbarLists[toolbarNum]: - pos = self.toolbarListWidget.currentRow() + 1 - else: -@@ -1758,7 +1758,7 @@ class CustomFontData: - self.mainFont.toString()) - - --class CustomFontDialog(QtGui.QDialog): -+class CustomFontDialog(QtWidgets.QDialog): - """Dialog for selecting custom fonts. - - Uses the print setup dialog's font page for the details. -@@ -1775,9 +1775,9 @@ class CustomFontDialog(QtGui.QDialog): - QtCore.Qt.WindowSystemMenuHint) - self.setWindowTitle(_('Customize Toolbars')) - -- topLayout = QtGui.QVBoxLayout(self) -+ topLayout = QtWidgets.QVBoxLayout(self) - self.setLayout(topLayout) -- tabs = QtGui.QTabWidget() -+ tabs = QtWidgets.QTabWidget() - topLayout.addWidget(tabs) - - self.pages = [] -@@ -1793,16 +1793,16 @@ class CustomFontDialog(QtGui.QDialog): - self.pages.append(editorFontPage) - tabs.addTab(editorFontPage, _('Editor View Font')) - -- ctrlLayout = QtGui.QHBoxLayout() -+ ctrlLayout = QtWidgets.QHBoxLayout() - topLayout.addLayout(ctrlLayout) - ctrlLayout.addStretch() -- self.okButton = QtGui.QPushButton(_('&OK')) -+ self.okButton = QtWidgets.QPushButton(_('&OK')) - ctrlLayout.addWidget(self.okButton) - self.okButton.clicked.connect(self.accept) -- self.applyButton = QtGui.QPushButton(_('&Apply')) -+ self.applyButton = QtWidgets.QPushButton(_('&Apply')) - ctrlLayout.addWidget(self.applyButton) - self.applyButton.clicked.connect(self.applyChanges) -- cancelButton = QtGui.QPushButton(_('&Cancel')) -+ cancelButton = QtWidgets.QPushButton(_('&Cancel')) - ctrlLayout.addWidget(cancelButton) - cancelButton.clicked.connect(self.reject) - -@@ -1825,7 +1825,7 @@ class CustomFontDialog(QtGui.QDialog): - super().accept() - - --class PluginListDialog(QtGui.QDialog): -+class PluginListDialog(QtWidgets.QDialog): - """Dialog for listing loaded plugin modules. - """ - def __init__(self, pluginList, parent=None): -@@ -1840,18 +1840,18 @@ class PluginListDialog(QtGui.QDialog): - QtCore.Qt.WindowSystemMenuHint) - self.setWindowTitle(_('TreeLine Plugins')) - -- topLayout = QtGui.QVBoxLayout(self) -+ topLayout = QtWidgets.QVBoxLayout(self) - self.setLayout(topLayout) -- label = QtGui.QLabel(_('Plugin Modules Loaded')) -+ label = QtWidgets.QLabel(_('Plugin Modules Loaded')) - topLayout.addWidget(label) -- listBox = QtGui.QListWidget() -- listBox.setSelectionMode(QtGui.QAbstractItemView.NoSelection) -+ listBox = QtWidgets.QListWidget() -+ listBox.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection) - listBox.addItems(pluginList) - topLayout.addWidget(listBox) - -- ctrlLayout = QtGui.QHBoxLayout() -+ ctrlLayout = QtWidgets.QHBoxLayout() - topLayout.addLayout(ctrlLayout) - ctrlLayout.addStretch() -- self.okButton = QtGui.QPushButton(_('&OK')) -+ self.okButton = QtWidgets.QPushButton(_('&OK')) - ctrlLayout.addWidget(self.okButton) - self.okButton.clicked.connect(self.accept) -Index: TreeLine/source/nodeformat.py -=================================================================== ---- TreeLine.orig/source/nodeformat.py -+++ TreeLine/source/nodeformat.py -@@ -22,7 +22,7 @@ import operator - import xml.sax.saxutils - if not sys.platform.startswith('win'): - import pwd --from PyQt4 import QtCore, QtGui -+from PyQt5 import QtCore - import fieldformat - import conditional - import imports -Index: TreeLine/source/options.py -=================================================================== ---- TreeLine.orig/source/options.py -+++ TreeLine/source/options.py -@@ -14,7 +14,7 @@ - from collections import OrderedDict - import sys - import os.path --from PyQt4 import QtCore, QtGui -+from PyQt5 import QtCore, QtGui, QtWidgets - import miscdialogs - - -@@ -91,9 +91,9 @@ class StringOptionItem: - groupBox = self.getGroupBox(rowLayout, currentGroupBox) - gridLayout = groupBox.layout() - row = gridLayout.rowCount() -- label = QtGui.QLabel(self.description, groupBox) -+ label = QtWidgets.QLabel(self.description, groupBox) - gridLayout.addWidget(label, row, 0) -- self.dialogControl = QtGui.QLineEdit(self.value, groupBox) -+ self.dialogControl = QtWidgets.QLineEdit(self.value, groupBox) - gridLayout.addWidget(self.dialogControl, row, 1) - return groupBox - -@@ -114,10 +114,10 @@ class StringOptionItem: - """ - if currentGroupBox and currentGroupBox.title() == self.category: - return currentGroupBox -- newGroupBox = QtGui.QGroupBox(self.category, -+ newGroupBox = QtWidgets.QGroupBox(self.category, - rowLayout.parentWidget()) - rowLayout.addWidget(newGroupBox) -- QtGui.QGridLayout(newGroupBox) -+ QtWidgets.QGridLayout(newGroupBox) - return newGroupBox - - -@@ -183,9 +183,9 @@ class IntOptionItem(StringOptionItem): - groupBox = self.getGroupBox(rowLayout, currentGroupBox) - gridLayout = groupBox.layout() - row = gridLayout.rowCount() -- label = QtGui.QLabel(self.description, groupBox) -+ label = QtWidgets.QLabel(self.description, groupBox) - gridLayout.addWidget(label, row, 0) -- self.dialogControl = QtGui.QSpinBox(groupBox) -+ self.dialogControl = QtWidgets.QSpinBox(groupBox) - self.dialogControl.setValue(self.value) - if self.minimum != None: - self.dialogControl.setMinimum(self.minimum) -@@ -264,9 +264,9 @@ class FloatOptionItem(StringOptionItem): - groupBox = self.getGroupBox(rowLayout, currentGroupBox) - gridLayout = groupBox.layout() - row = gridLayout.rowCount() -- label = QtGui.QLabel(self.description, groupBox) -+ label = QtWidgets.QLabel(self.description, groupBox) - gridLayout.addWidget(label, row, 0) -- self.dialogControl = QtGui.QDoubleSpinBox(groupBox) -+ self.dialogControl = QtWidgets.QDoubleSpinBox(groupBox) - self.dialogControl.setValue(self.value) - if self.minimum != None: - self.dialogControl.setMinimum(self.minimum) -@@ -345,7 +345,7 @@ class BoolOptionItem(StringOptionItem): - groupBox = self.getGroupBox(rowLayout, currentGroupBox) - gridLayout = groupBox.layout() - row = gridLayout.rowCount() -- self.dialogControl = QtGui.QCheckBox(self.description, groupBox) -+ self.dialogControl = QtWidgets.QCheckBox(self.description, groupBox) - self.dialogControl.setChecked(self.value) - gridLayout.addWidget(self.dialogControl, row, 0, 1, 2) - return groupBox -@@ -411,9 +411,9 @@ class ListOptionItem(StringOptionItem): - groupBox = self.getGroupBox(rowLayout, currentGroupBox) - gridLayout = groupBox.layout() - row = gridLayout.rowCount() -- label = QtGui.QLabel(self.description, groupBox) -+ label = QtWidgets.QLabel(self.description, groupBox) - gridLayout.addWidget(label, row, 0) -- self.dialogControl = QtGui.QComboBox(groupBox) -+ self.dialogControl = QtWidgets.QComboBox(groupBox) - self.dialogControl.addItems(self.choices) - self.dialogControl.setCurrentIndex(self.choices.index(self.value)) - gridLayout.addWidget(self.dialogControl, row, 1) -@@ -475,14 +475,14 @@ class ChoiceOptionItem(StringOptionItem) - rowLayout -- the vertical box layout holding the group boxes - currentGroupBox -- the currently used group box - """ -- groupBox = QtGui.QGroupBox(self.category, rowLayout.parentWidget()) -+ groupBox = QtWidgets.QGroupBox(self.category, rowLayout.parentWidget()) - rowLayout.addWidget(groupBox) -- QtGui.QGridLayout(groupBox) -+ QtWidgets.QGridLayout(groupBox) - gridLayout = groupBox.layout() -- self.dialogControl = QtGui.QButtonGroup(groupBox) -+ self.dialogControl = QtWidgets.QButtonGroup(groupBox) - row = 0 - for choice in self.choices: -- button = QtGui.QRadioButton(choice, groupBox) -+ button = QtWidgets.QRadioButton(choice, groupBox) - self.dialogControl.addButton(button) - gridLayout.addWidget(button, row, 0, 1, 2) - row += 1 -@@ -607,7 +607,7 @@ class Options(OrderedDict): - _('Choose configuration file location'), - [(_('User\'s home directory (recommended)'), 0), - (_('Program directory (for portable use)'), 1)]) -- if dialog.exec_() != QtGui.QDialog.Accepted: -+ if dialog.exec_() != QtWidgets.QDialog.Accepted: - sys.exit(0) - if dialog.selectedButton() == 1: - Options.basePath = modConfigPath -@@ -720,7 +720,7 @@ class Options(OrderedDict): - self.modified = False - - --class OptionDialog(QtGui.QDialog): -+class OptionDialog(QtWidgets.QDialog): - """Provides a dialog with controls for all options in an Options instance. - """ - def __init__(self, options, parent=None): -@@ -728,26 +728,26 @@ class OptionDialog(QtGui.QDialog): - self.options = options - self.setWindowFlags(QtCore.Qt.Dialog | QtCore.Qt.WindowTitleHint | - QtCore.Qt.WindowSystemMenuHint) -- topLayout = QtGui.QVBoxLayout(self) -+ topLayout = QtWidgets.QVBoxLayout(self) - self.setLayout(topLayout) -- columnLayout = QtGui.QHBoxLayout() -+ columnLayout = QtWidgets.QHBoxLayout() - topLayout.addLayout(columnLayout) -- rowLayout = QtGui.QVBoxLayout() -+ rowLayout = QtWidgets.QVBoxLayout() - columnLayout.addLayout(rowLayout) - groupBox = None - for option in self.options.values(): - if option.columnNum > columnLayout.count() - 1: -- rowLayout = QtGui.QVBoxLayout() -+ rowLayout = QtWidgets.QVBoxLayout() - columnLayout.addLayout(rowLayout) - groupBox = option.addDialogControl(rowLayout, groupBox) - -- ctrlLayout = QtGui.QHBoxLayout() -+ ctrlLayout = QtWidgets.QHBoxLayout() - topLayout.addLayout(ctrlLayout) - ctrlLayout.addStretch(0) -- okButton = QtGui.QPushButton(_('&OK')) -+ okButton = QtWidgets.QPushButton(_('&OK')) - ctrlLayout.addWidget(okButton) - okButton.clicked.connect(self.accept) -- cancelButton = QtGui.QPushButton(_('&Cancel')) -+ cancelButton = QtWidgets.QPushButton(_('&Cancel')) - ctrlLayout.addWidget(cancelButton) - cancelButton.clicked.connect(self.reject) - -Index: TreeLine/source/outputview.py -=================================================================== ---- TreeLine.orig/source/outputview.py -+++ TreeLine/source/outputview.py -@@ -13,7 +13,7 @@ - #****************************************************************************** - - import os.path --from PyQt4 import QtCore, QtGui -+from PyQt5 import QtCore, QtGui, QtWidgets - import treenode - import treeoutput - import urltools -@@ -21,7 +21,7 @@ import dataeditors - import globalref - - --class OutputView(QtGui.QTextBrowser): -+class OutputView(QtWidgets.QTextBrowser): - """Class override for the data output view. - - Sets view defaults and updates the content. -@@ -120,7 +120,7 @@ class OutputView(QtGui.QTextBrowser): - selections = [] - for word in wordList: - while self.find(word): -- extraSel = QtGui.QTextEdit.ExtraSelection() -+ extraSel = QtWidgets.QTextEdit.ExtraSelection() - extraSel.cursor = self.textCursor() - extraSel.format.setBackground(backColor) - extraSel.format.setForeground(foreColor) -Index: TreeLine/source/plugininterface.py -=================================================================== ---- TreeLine.orig/source/plugininterface.py -+++ TreeLine/source/plugininterface.py -@@ -57,7 +57,7 @@ import os.path - import io - import tempfile - from xml.etree import ElementTree --from PyQt4 import QtCore, QtGui -+from PyQt5 import QtCore, QtGui, QtWidgets - import treeoutput - import nodeformat - import fieldformat -@@ -697,7 +697,7 @@ class PluginInterface(): - - Returns None if something else has the focus. - """ -- widget = QtGui.QApplication.focusWidget() -+ widget = QtWidgets.QApplication.focusWidget() - if (hasattr(widget, 'paste') and - isinstance(widget.parent().parent(), dataeditview.DataEditView)): - return widget -Index: TreeLine/source/printdata.py -=================================================================== ---- TreeLine.orig/source/printdata.py -+++ TreeLine/source/printdata.py -@@ -13,7 +13,7 @@ - #****************************************************************************** - - import os.path --from PyQt4 import QtCore, QtGui -+from PyQt5 import QtCore, QtGui, QtPrintSupport, QtWidgets - import treeoutput - import printdialogs - import globalref -@@ -37,7 +37,7 @@ class PrintData: - self.printWhat = entireTree - self.includeRoot = True - self.openOnly = False -- self.printer = QtGui.QPrinter(QtGui.QPrinter.HighResolution) -+ self.printer = QtPrintSupport.QPrinter(QtPrintSupport.QPrinter.HighResolution) - self.setDefaults() - self.adjustSpacing() - -@@ -47,9 +47,9 @@ class PrintData: - self.drawLines = True - self.widowControl = True - self.indentFactor = 2.0 -- self.printer.setPaperSize(QtGui.QPrinter.Letter) -- self.printer.setOrientation(QtGui.QPrinter.Portrait) -- self.printer.setPageMargins(0.5, 0.5, 0.5, 0.5, QtGui.QPrinter.Inch) -+ self.printer.setPaperSize(QtPrintSupport.QPrinter.Letter) -+ self.printer.setOrientation(QtPrintSupport.QPrinter.Portrait) -+ self.printer.setPageMargins(0.5, 0.5, 0.5, 0.5, QtPrintSupport.QPrinter.Inch) - self.headerMargin = 0.2 - self.footerMargin = 0.2 - self.numColumns = 1 -@@ -86,16 +86,16 @@ class PrintData: - attrs['printwidowcontrol'] = 'n' - if self.indentFactor != 2.0: - attrs['printindentfactor'] = repr(self.indentFactor) -- if self.printer.paperSize() == QtGui.QPrinter.Custom: -+ if self.printer.paperSize() == QtPrintSupport.QPrinter.Custom: - paperWidth, paperHeight = self.roundedPaperSize() - attrs['printpaperwidth'] = repr(paperWidth) - attrs['printpaperheight'] = repr(paperHeight) -- elif self.printer.paperSize() != QtGui.QPrinter.Letter: -+ elif self.printer.paperSize() != QtPrintSupport.QPrinter.Letter: - paperSizeDict = dict((num, attrib) for attrib, num in -- vars(QtGui.QPrinter).items() -- if isinstance(num, QtGui.QPrinter.PageSize)) -+ vars(QtPrintSupport.QPrinter).items() -+ if isinstance(num, QtPrintSupport.QPrinter.PageSize)) - attrs['printpapersize'] = paperSizeDict[self.printer.paperSize()] -- if self.printer.orientation() != QtGui.QPrinter.Portrait: -+ if self.printer.orientation() != QtPrintSupport.QPrinter.Portrait: - attrs['printportrait'] = 'n' - if self.roundedMargins() != [0.5] * 4: - attrs['printmargins'] = ' '.join([repr(margin) for margin in -@@ -130,19 +130,19 @@ class PrintData: - if 'printindentfactor' in attrs: - self.indentFactor = float(attrs['printindentfactor']) - if 'printpapersize' in attrs: -- self.printer.setPaperSize(getattr(QtGui.QPrinter, -+ self.printer.setPaperSize(getattr(QtPrintSupport.QPrinter, - attrs['printpapersize'])) - if 'printpaperwidth' in attrs and 'printpaperheight' in attrs: - width = float(attrs['printpaperwidth']) - height = float(attrs['printpaperheight']) - self.printer.setPaperSize(QtCore.QSizeF(width, height), -- QtGui.QPrinter.Inch) -+ QtPrintSupport.QPrinter.Inch) - if attrs.get('printportrait', '').startswith('n'): -- self.printer.setOrientation(QtGui.QPrinter.Landscape) -+ self.printer.setOrientation(QtPrintSupport.QPrinter.Landscape) - if 'printmargins' in attrs: - margins = [float(margin) for margin in - attrs['printmargins'].split()] -- margins.append(QtGui.QPrinter.Inch) -+ margins.append(QtPrintSupport.QPrinter.Inch) - self.printer.setPageMargins(*margins) - if 'printheadermargin' in attrs: - self.headerMargin = float(attrs['printheadermargin']) -@@ -165,14 +165,14 @@ class PrintData: - Rounds to nearest .01" to avoid Qt unit conversion artifacts. - """ - return [round(margin, 2) for margin in -- self.printer.getPageMargins(QtGui.QPrinter.Inch)] -+ self.printer.getPageMargins(QtPrintSupport.QPrinter.Inch)] - - def roundedPaperSize(self): - """Return a tuple of rounded paper width and height. - - Rounds to nearest .01" to avoid Qt unit conversion artifacts. - """ -- size = self.printer.paperSize(QtGui.QPrinter.Inch) -+ size = self.printer.paperSize(QtPrintSupport.QPrinter.Inch) - return (round(size.width(), 2), round(size.height(), 2)) - - def setupData(self): -@@ -275,18 +275,18 @@ class PrintData: - """ - pageNum = 1 - maxPageNum = self.outputGroup[-1].pageNum -- if self.printer.printRange() != QtGui.QPrinter.AllPages: -+ if self.printer.printRange() != QtPrintSupport.QPrinter.AllPages: - pageNum = self.printer.fromPage() - maxPageNum = self.printer.toPage() - painter = QtGui.QPainter() - if not painter.begin(self.printer): -- QtGui.QMessageBox.warning(QtGui.QApplication.activeWindow(), -+ QtWidgets.QMessageBox.warning(QtWidgets.QApplication.activeWindow(), - 'TreeLine', _('Error initializing printer')) -- QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) -+ QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) - while True: - self.paintPage(pageNum, painter) - if pageNum == maxPageNum: -- QtGui.QApplication.restoreOverrideCursor() -+ QtWidgets.QApplication.restoreOverrideCursor() - return - pageNum += 1 - self.printer.newPage() -@@ -305,7 +305,7 @@ class PrintData: - layout.setPaintDevice(self.printer) - headerDoc.setTextWidth(self.printer.pageRect().width()) - painter.save() -- topMargin = self.printer.getPageMargins(QtGui.QPrinter.Inch)[1] -+ topMargin = self.printer.getPageMargins(QtPrintSupport.QPrinter.Inch)[1] - headerDelta = ((self.headerMargin - topMargin) * - self.printer.logicalDpiX()) - painter.translate(0, int(headerDelta)) -@@ -328,7 +328,7 @@ class PrintData: - layout.setPaintDevice(self.printer) - footerDoc.setTextWidth(self.printer.pageRect().width()) - painter.save() -- bottomMargin = self.printer.getPageMargins(QtGui.QPrinter.Inch)[3] -+ bottomMargin = self.printer.getPageMargins(QtPrintSupport.QPrinter.Inch)[3] - footerDelta = ((bottomMargin - self.footerMargin) * - self.printer.logicalDpiX()) - painter.translate(0, self.printer.pageRect().height() + -@@ -452,7 +452,7 @@ class PrintData: - """Show a dialog to set margins, page size and other printing options. - """ - setupDialog = printdialogs.PrintSetupDialog(self, True, -- QtGui.QApplication. -+ QtWidgets.QApplication. - activeWindow()) - setupDialog.exec_() - -@@ -461,7 +461,7 @@ class PrintData: - """ - self.setupData() - previewDialog = printdialogs.PrintPreviewDialog(self, -- QtGui.QApplication. -+ QtWidgets.QApplication. - activeWindow()) - previewDialog.previewWidget.paintRequested.connect(self.paintData) - if globalref.genOptions.getValue('SaveWindowGeom'): -@@ -472,9 +472,9 @@ class PrintData: - """Show dialog and print tree output based on current options. - """ - self.setupData() -- printDialog = QtGui.QPrintDialog(self.printer, -- QtGui.QApplication.activeWindow()) -- if printDialog.exec_() == QtGui.QDialog.Accepted: -+ printDialog = QtPrintSupport.QPrintDialog(self.printer, -+ QtWidgets.QApplication.activeWindow()) -+ if printDialog.exec_() == QtWidgets.QDialog.Accepted: - self.paintData(self.printer) - - def filePrintPdf(self): -@@ -486,19 +486,19 @@ class PrintData: - defaultFilePath = os.path.splitext(defaultFilePath)[0] - if os.path.basename(defaultFilePath): - defaultFilePath = '{0}.{1}'.format(defaultFilePath, 'pdf') -- filePath = QtGui.QFileDialog.getSaveFileName(QtGui.QApplication. -+ filePath = QtWidgets.QFileDialog.getSaveFileName(QtWidgets.QApplication. - activeWindow(), - _('TreeLine - Export PDF'), -- defaultFilePath, filters) -+ defaultFilePath, filters)[0] - if not filePath: - return - if not os.path.splitext(filePath)[1]: - filePath = '{0}.{1}'.format(filePath, 'pdf') -- self.printer.setOutputFormat(QtGui.QPrinter.PdfFormat) -+ self.printer.setOutputFormat(QtPrintSupport.QPrinter.PdfFormat) - self.printer.setOutputFileName(filePath) - self.adjustSpacing() - self.setupData() - self.paintData(self.printer) -- self.printer.setOutputFormat(QtGui.QPrinter.NativeFormat) -+ self.printer.setOutputFormat(QtPrintSupport.QPrinter.NativeFormat) - self.printer.setOutputFileName('') - self.adjustSpacing() -Index: TreeLine/source/printdialogs.py -=================================================================== ---- TreeLine.orig/source/printdialogs.py -+++ TreeLine/source/printdialogs.py -@@ -14,7 +14,7 @@ - - import re - import collections --from PyQt4 import QtCore, QtGui -+from PyQt5 import QtCore, QtGui, QtPrintSupport, QtWidgets - import printdata - import configdialog - import treeformats -@@ -22,7 +22,7 @@ import undo - import globalref - - --class PrintPreviewDialog(QtGui.QDialog): -+class PrintPreviewDialog(QtWidgets.QDialog): - """Dialog for print previews. - - Similar to QPrintPreviewDialog but calls a custom page setup dialog. -@@ -39,24 +39,24 @@ class PrintPreviewDialog(QtGui.QDialog): - QtCore.Qt.WindowSystemMenuHint) - self.setWindowTitle(_('Print Preview')) - self.printData = printData -- topLayout = QtGui.QVBoxLayout(self) -+ topLayout = QtWidgets.QVBoxLayout(self) - self.setLayout(topLayout) - -- toolBar = QtGui.QToolBar(self) -+ toolBar = QtWidgets.QToolBar(self) - topLayout.addWidget(toolBar) - -- self.previewWidget = QtGui.QPrintPreviewWidget(printData.printer, self) -+ self.previewWidget = QtPrintSupport.QPrintPreviewWidget(printData.printer, self) - topLayout.addWidget(self.previewWidget) - self.previewWidget.previewChanged.connect(self.updateControls) - -- self.zoomWidthAct = QtGui.QAction(_('Fit Width'), self, checkable=True) -+ self.zoomWidthAct = QtWidgets.QAction(_('Fit Width'), self, checkable=True) - icon = globalref.toolIcons.getIcon('printpreviewzoomwidth') - if icon: - self.zoomWidthAct.setIcon(icon) - self.zoomWidthAct.triggered.connect(self.zoomWidth) - toolBar.addAction(self.zoomWidthAct) - -- self.zoomAllAct = QtGui.QAction(_('Fit Page'), self, checkable=True) -+ self.zoomAllAct = QtWidgets.QAction(_('Fit Page'), self, checkable=True) - icon = globalref.toolIcons.getIcon('printpreviewzoomall') - if icon: - self.zoomAllAct.setIcon(icon) -@@ -64,23 +64,23 @@ class PrintPreviewDialog(QtGui.QDialog): - toolBar.addAction(self.zoomAllAct) - toolBar.addSeparator() - -- self.zoomCombo = QtGui.QComboBox(self) -+ self.zoomCombo = QtWidgets.QComboBox(self) - self.zoomCombo.setEditable(True) -- self.zoomCombo.setInsertPolicy(QtGui.QComboBox.NoInsert) -+ self.zoomCombo.setInsertPolicy(QtWidgets.QComboBox.NoInsert) - self.zoomCombo.addItems([' 12%', ' 25%', ' 50%', ' 75%', ' 100%', - ' 125%', ' 150%', ' 200%', ' 400%', ' 800%']) - self.zoomCombo.currentIndexChanged[str].connect(self.zoomToValue) - self.zoomCombo.lineEdit().returnPressed.connect(self.zoomToValue) - toolBar.addWidget(self.zoomCombo) - -- zoomInAct = QtGui.QAction(_('Zoom In'), self) -+ zoomInAct = QtWidgets.QAction(_('Zoom In'), self) - icon = globalref.toolIcons.getIcon('printpreviewzoomin') - if icon: - zoomInAct.setIcon(icon) - zoomInAct.triggered.connect(self.zoomIn) - toolBar.addAction(zoomInAct) - -- zoomOutAct = QtGui.QAction(_('Zoom Out'), self) -+ zoomOutAct = QtWidgets.QAction(_('Zoom Out'), self) - icon = globalref.toolIcons.getIcon('printpreviewzoomout') - if icon: - zoomOutAct.setIcon(icon) -@@ -88,14 +88,14 @@ class PrintPreviewDialog(QtGui.QDialog): - toolBar.addAction(zoomOutAct) - toolBar.addSeparator() - -- self.previousAct = QtGui.QAction(_('Previous Page'), self) -+ self.previousAct = QtWidgets.QAction(_('Previous Page'), self) - icon = globalref.toolIcons.getIcon('printpreviewprevious') - if icon: - self.previousAct.setIcon(icon) - self.previousAct.triggered.connect(self.previousPage) - toolBar.addAction(self.previousAct) - -- self.pageNumEdit = QtGui.QLineEdit(self) -+ self.pageNumEdit = QtWidgets.QLineEdit(self) - self.pageNumEdit.setAlignment(QtCore.Qt.AlignRight | - QtCore.Qt.AlignVCenter) - width = QtGui.QFontMetrics(self.pageNumEdit.font()).width('0000') -@@ -103,10 +103,10 @@ class PrintPreviewDialog(QtGui.QDialog): - self.pageNumEdit.returnPressed.connect(self.setPageNum) - toolBar.addWidget(self.pageNumEdit) - -- self.maxPageLabel = QtGui.QLabel(' / 000 ', self) -+ self.maxPageLabel = QtWidgets.QLabel(' / 000 ', self) - toolBar.addWidget(self.maxPageLabel) - -- self.nextAct = QtGui.QAction(_('Next Page'), self) -+ self.nextAct = QtWidgets.QAction(_('Next Page'), self) - icon = globalref.toolIcons.getIcon('printpreviewnext') - if icon: - self.nextAct.setIcon(icon) -@@ -114,7 +114,7 @@ class PrintPreviewDialog(QtGui.QDialog): - toolBar.addAction(self.nextAct) - toolBar.addSeparator() - -- self.onePageAct = QtGui.QAction(_('Single Page'), self, checkable=True) -+ self.onePageAct = QtWidgets.QAction(_('Single Page'), self, checkable=True) - icon = globalref.toolIcons.getIcon('printpreviewsingle') - if icon: - self.onePageAct.setIcon(icon) -@@ -122,7 +122,7 @@ class PrintPreviewDialog(QtGui.QDialog): - setSinglePageViewMode) - toolBar.addAction(self.onePageAct) - -- self.twoPageAct = QtGui.QAction(_('Facing Pages'), self, -+ self.twoPageAct = QtWidgets.QAction(_('Facing Pages'), self, - checkable=True) - icon = globalref.toolIcons.getIcon('printpreviewdouble') - if icon: -@@ -132,14 +132,14 @@ class PrintPreviewDialog(QtGui.QDialog): - toolBar.addAction(self.twoPageAct) - toolBar.addSeparator() - -- pageSetupAct = QtGui.QAction(_('Print Setup'), self) -+ pageSetupAct = QtWidgets.QAction(_('Print Setup'), self) - icon = globalref.toolIcons.getIcon('fileprintsetup') - if icon: - pageSetupAct.setIcon(icon) - pageSetupAct.triggered.connect(self.printSetup) - toolBar.addAction(pageSetupAct) - -- filePrintAct = QtGui.QAction(_('Print'), self) -+ filePrintAct = QtWidgets.QAction(_('Print'), self) - icon = globalref.toolIcons.getIcon('fileprint') - if icon: - filePrintAct.setIcon(icon) -@@ -150,9 +150,9 @@ class PrintPreviewDialog(QtGui.QDialog): - """Update control availability and status based on a change signal. - """ - self.zoomWidthAct.setChecked(self.previewWidget.zoomMode() == -- QtGui.QPrintPreviewWidget.FitToWidth) -+ QtPrintSupport.QPrintPreviewWidget.FitToWidth) - self.zoomAllAct.setChecked(self.previewWidget.zoomMode() == -- QtGui.QPrintPreviewWidget.FitInView) -+ QtPrintSupport.QPrintPreviewWidget.FitInView) - zoom = self.previewWidget.zoomFactor() * 100 - self.zoomCombo.setEditText('{0:4.0f}%'.format(zoom)) - self.previousAct.setEnabled(self.previewWidget.currentPage() > 1) -@@ -162,9 +162,9 @@ class PrintPreviewDialog(QtGui.QDialog): - self.maxPageLabel.setText(' / {0} '.format(self.previewWidget. - pageCount())) - self.onePageAct.setChecked(self.previewWidget.viewMode() == -- QtGui.QPrintPreviewWidget.SinglePageView) -+ QtPrintSupport.QPrintPreviewWidget.SinglePageView) - self.twoPageAct.setChecked(self.previewWidget.viewMode() == -- QtGui.QPrintPreviewWidget.FacingPagesView) -+ QtPrintSupport.QPrintPreviewWidget.FacingPagesView) - - def zoomWidth(self, checked=True): - """Set the fit to width zoom mode if checked. -@@ -173,10 +173,10 @@ class PrintPreviewDialog(QtGui.QDialog): - checked -- set this mode if True - """ - if checked: -- self.previewWidget.setZoomMode(QtGui.QPrintPreviewWidget. -+ self.previewWidget.setZoomMode(QtPrintSupport.QPrintPreviewWidget. - FitToWidth) - else: -- self.previewWidget.setZoomMode(QtGui.QPrintPreviewWidget. -+ self.previewWidget.setZoomMode(QtPrintSupport.QPrintPreviewWidget. - CustomZoom) - self.updateControls() - -@@ -187,9 +187,9 @@ class PrintPreviewDialog(QtGui.QDialog): - checked -- set this mode if True - """ - if checked: -- self.previewWidget.setZoomMode(QtGui.QPrintPreviewWidget.FitInView) -+ self.previewWidget.setZoomMode(QtPrintSupport.QPrintPreviewWidget.FitInView) - else: -- self.previewWidget.setZoomMode(QtGui.QPrintPreviewWidget. -+ self.previewWidget.setZoomMode(QtPrintSupport.QPrintPreviewWidget. - CustomZoom) - self.updateControls() - -@@ -245,7 +245,7 @@ class PrintPreviewDialog(QtGui.QDialog): - """Show a dialog to set margins, page size and other printing options. - """ - setupDialog = PrintSetupDialog(self.printData, False, self) -- if setupDialog.exec_() == QtGui.QDialog.Accepted: -+ if setupDialog.exec_() == QtWidgets.QDialog.Accepted: - self.printData.setupData() - self.previewWidget.updatePreview() - -@@ -290,7 +290,7 @@ class PrintPreviewDialog(QtGui.QDialog): - self.saveDialogGeom() - - --class PrintSetupDialog(QtGui.QDialog): -+class PrintSetupDialog(QtWidgets.QDialog): - """Base dialog for setting the print configuration. - - Pushes most options to the PrintData class. -@@ -309,10 +309,10 @@ class PrintSetupDialog(QtGui.QDialog): - self.setWindowTitle(_('Printing Setup')) - self.printData = printData - -- topLayout = QtGui.QVBoxLayout(self) -+ topLayout = QtWidgets.QVBoxLayout(self) - self.setLayout(topLayout) - -- tabs = QtGui.QTabWidget() -+ tabs = QtWidgets.QTabWidget() - topLayout.addWidget(tabs) - generalPage = GeneralPage(self.printData) - tabs.addTab(generalPage, _('&General Options')) -@@ -324,20 +324,20 @@ class PrintSetupDialog(QtGui.QDialog): - tabs.addTab(headerPage, _('&Header/Footer')) - self.tabPages = [generalPage, pageSetupPage, fontPage, headerPage] - -- ctrlLayout = QtGui.QHBoxLayout() -+ ctrlLayout = QtWidgets.QHBoxLayout() - topLayout.addLayout(ctrlLayout) - ctrlLayout.addStretch() - if showExtraButtons: -- previewButton = QtGui.QPushButton(_('Print Pre&view...')) -+ previewButton = QtWidgets.QPushButton(_('Print Pre&view...')) - ctrlLayout.addWidget(previewButton) - previewButton.clicked.connect(self.preview) -- printButton = QtGui.QPushButton(_('&Print...')) -+ printButton = QtWidgets.QPushButton(_('&Print...')) - ctrlLayout.addWidget(printButton) - printButton.clicked.connect(self.quickPrint) -- okButton = QtGui.QPushButton(_('&OK')) -+ okButton = QtWidgets.QPushButton(_('&OK')) - ctrlLayout.addWidget(okButton) - okButton.clicked.connect(self.accept) -- cancelButton = QtGui.QPushButton(_('&Cancel')) -+ cancelButton = QtWidgets.QPushButton(_('&Cancel')) - ctrlLayout.addWidget(cancelButton) - cancelButton.clicked.connect(self.reject) - -@@ -368,7 +368,7 @@ class PrintSetupDialog(QtGui.QDialog): - super().accept() - - --class GeneralPage(QtGui.QWidget): -+class GeneralPage(QtWidgets.QWidget): - """Dialog page for misc. print options. - """ - def __init__(self, printData, parent=None): -@@ -380,51 +380,51 @@ class GeneralPage(QtGui.QWidget): - """ - super().__init__(parent) - self.printData = printData -- topLayout = QtGui.QGridLayout(self) -+ topLayout = QtWidgets.QGridLayout(self) - self.setLayout(topLayout) - -- whatGroupBox = QtGui.QGroupBox(_('What to print')) -+ whatGroupBox = QtWidgets.QGroupBox(_('What to print')) - topLayout.addWidget(whatGroupBox, 0, 0) -- whatLayout = QtGui.QVBoxLayout(whatGroupBox) -- self.whatButtons = QtGui.QButtonGroup(self) -- treeButton = QtGui.QRadioButton(_('&Entire tree')) -+ whatLayout = QtWidgets.QVBoxLayout(whatGroupBox) -+ self.whatButtons = QtWidgets.QButtonGroup(self) -+ treeButton = QtWidgets.QRadioButton(_('&Entire tree')) - self.whatButtons.addButton(treeButton, printdata.entireTree) - whatLayout.addWidget(treeButton) -- branchButton = QtGui.QRadioButton(_('Selected &branches')) -+ branchButton = QtWidgets.QRadioButton(_('Selected &branches')) - self.whatButtons.addButton(branchButton, printdata.selectBranch) - whatLayout.addWidget(branchButton) -- nodeButton = QtGui.QRadioButton(_('Selected &nodes')) -+ nodeButton = QtWidgets.QRadioButton(_('Selected &nodes')) - self.whatButtons.addButton(nodeButton, printdata.selectNode) - whatLayout.addWidget(nodeButton) - self.whatButtons.button(self.printData.printWhat).setChecked(True) - self.whatButtons.buttonClicked.connect(self.updateCmdAvail) - -- includeBox = QtGui.QGroupBox(_('Included Nodes')) -+ includeBox = QtWidgets.QGroupBox(_('Included Nodes')) - topLayout.addWidget(includeBox, 1, 0) -- includeLayout = QtGui.QVBoxLayout(includeBox) -- self.rootButton = QtGui.QCheckBox(_('&Include root node')) -+ includeLayout = QtWidgets.QVBoxLayout(includeBox) -+ self.rootButton = QtWidgets.QCheckBox(_('&Include root node')) - includeLayout.addWidget(self.rootButton) - self.rootButton.setChecked(self.printData.includeRoot) -- self.openOnlyButton = QtGui.QCheckBox(_('Onl&y open node children')) -+ self.openOnlyButton = QtWidgets.QCheckBox(_('Onl&y open node children')) - includeLayout.addWidget(self.openOnlyButton) - self.openOnlyButton.setChecked(self.printData.openOnly) - -- featureBox = QtGui.QGroupBox(_('Features')) -+ featureBox = QtWidgets.QGroupBox(_('Features')) - topLayout.addWidget(featureBox, 0, 1) -- featureLayout = QtGui.QVBoxLayout(featureBox) -- self.linesButton = QtGui.QCheckBox(_('&Draw lines to children')) -+ featureLayout = QtWidgets.QVBoxLayout(featureBox) -+ self.linesButton = QtWidgets.QCheckBox(_('&Draw lines to children')) - featureLayout.addWidget(self.linesButton) - self.linesButton.setChecked(self.printData.drawLines) -- self.widowButton = QtGui.QCheckBox(_('&Keep first child with parent')) -+ self.widowButton = QtWidgets.QCheckBox(_('&Keep first child with parent')) - featureLayout.addWidget(self.widowButton) - self.widowButton.setChecked(self.printData.widowControl) - -- indentBox = QtGui.QGroupBox(_('Indent')) -+ indentBox = QtWidgets.QGroupBox(_('Indent')) - topLayout.addWidget(indentBox, 1, 1) -- indentLayout = QtGui.QHBoxLayout(indentBox) -- indentLabel = QtGui.QLabel(_('Indent Offse&t\n(line height units)')) -+ indentLayout = QtWidgets.QHBoxLayout(indentBox) -+ indentLabel = QtWidgets.QLabel(_('Indent Offse&t\n(line height units)')) - indentLayout.addWidget(indentLabel) -- self.indentSpin = QtGui.QDoubleSpinBox() -+ self.indentSpin = QtWidgets.QDoubleSpinBox() - indentLayout.addWidget(self.indentSpin) - indentLabel.setBuddy(self.indentSpin) - self.indentSpin.setMinimum(0.5) -@@ -481,7 +481,7 @@ _units = collections.OrderedDict([('in', - _unitValues = {'in': 1.0, 'cm': 2.54, 'mm': 25.4} - _unitDecimals = {'in': 2, 'cm': 1, 'mm': 0} - --class PageSetupPage(QtGui.QWidget): -+class PageSetupPage(QtWidgets.QWidget): - """Dialog page for page setup options. - """ - def __init__(self, printData, parent=None): -@@ -493,15 +493,15 @@ class PageSetupPage(QtGui.QWidget): - """ - super().__init__(parent) - self.printData = printData -- topLayout = QtGui.QHBoxLayout(self) -+ topLayout = QtWidgets.QHBoxLayout(self) - self.setLayout(topLayout) -- leftLayout = QtGui.QVBoxLayout() -+ leftLayout = QtWidgets.QVBoxLayout() - topLayout.addLayout(leftLayout) - -- unitsBox = QtGui.QGroupBox(_('&Units')) -+ unitsBox = QtWidgets.QGroupBox(_('&Units')) - leftLayout.addWidget(unitsBox) -- unitsLayout = QtGui.QVBoxLayout(unitsBox) -- unitsCombo = QtGui.QComboBox() -+ unitsLayout = QtWidgets.QVBoxLayout(unitsBox) -+ unitsCombo = QtWidgets.QComboBox() - unitsLayout.addWidget(unitsCombo) - unitsCombo.addItems(list(_units.values())) - self.currentUnit = globalref.miscOptions.getValue('PrintUnits') -@@ -510,18 +510,18 @@ class PageSetupPage(QtGui.QWidget): - unitsCombo.setCurrentIndex(list(_units.keys()).index(self.currentUnit)) - unitsCombo.currentIndexChanged.connect(self.changeUnits) - -- paperSizeBox = QtGui.QGroupBox(_('Paper &Size')) -+ paperSizeBox = QtWidgets.QGroupBox(_('Paper &Size')) - leftLayout.addWidget(paperSizeBox) -- paperSizeLayout = QtGui.QGridLayout(paperSizeBox) -+ paperSizeLayout = QtWidgets.QGridLayout(paperSizeBox) - spacing = paperSizeLayout.spacing() - paperSizeLayout.setVerticalSpacing(0) - paperSizeLayout.setRowMinimumHeight(1, spacing) -- paperSizeCombo = QtGui.QComboBox() -+ paperSizeCombo = QtWidgets.QComboBox() - paperSizeLayout.addWidget(paperSizeCombo, 0, 0, 1, 2) - paperSizeCombo.addItems(list(_paperSizes.values())) - paperSizeDict = dict((num, attrib) for attrib, num in -- vars(QtGui.QPrinter).items() -- if isinstance(num, QtGui.QPrinter.PageSize)) -+ vars(QtPrintSupport.QPrinter).items() -+ if isinstance(num, QtPrintSupport.QPrinter.PageSize)) - self.currentPaperSize = paperSizeDict[self.printData.printer. - paperSize()] - if self.currentPaperSize not in _paperSizes: -@@ -529,14 +529,14 @@ class PageSetupPage(QtGui.QWidget): - paperSizeCombo.setCurrentIndex(list(_paperSizes.keys()). - index(self.currentPaperSize)) - paperSizeCombo.currentIndexChanged.connect(self.changePaper) -- widthLabel = QtGui.QLabel(_('&Width:')) -+ widthLabel = QtWidgets.QLabel(_('&Width:')) - paperSizeLayout.addWidget(widthLabel, 2, 0) - self.paperWidthSpin = UnitSpinBox(self.currentUnit) - paperSizeLayout.addWidget(self.paperWidthSpin, 3, 0) - widthLabel.setBuddy(self.paperWidthSpin) - paperWidth, paperHeight = self.printData.roundedPaperSize() - self.paperWidthSpin.setInchValue(paperWidth) -- heightlabel = QtGui.QLabel(_('Height:')) -+ heightlabel = QtWidgets.QLabel(_('Height:')) - paperSizeLayout.addWidget(heightlabel, 2, 1) - self.paperHeightSpin = UnitSpinBox(self.currentUnit) - paperSizeLayout.addWidget(self.paperHeightSpin, 3, 1) -@@ -546,47 +546,47 @@ class PageSetupPage(QtGui.QWidget): - self.paperWidthSpin.setEnabled(False) - self.paperHeightSpin.setEnabled(False) - -- orientbox = QtGui.QGroupBox(_('Orientation')) -+ orientbox = QtWidgets.QGroupBox(_('Orientation')) - leftLayout.addWidget(orientbox) -- orientLayout = QtGui.QVBoxLayout(orientbox) -- portraitButton = QtGui.QRadioButton(_('Portra&it')) -+ orientLayout = QtWidgets.QVBoxLayout(orientbox) -+ portraitButton = QtWidgets.QRadioButton(_('Portra&it')) - orientLayout.addWidget(portraitButton) -- landscapeButton = QtGui.QRadioButton(_('Lan&dscape')) -+ landscapeButton = QtWidgets.QRadioButton(_('Lan&dscape')) - orientLayout.addWidget(landscapeButton) - self.portraitOrient = (self.printData.printer.orientation() == -- QtGui.QPrinter.Portrait) -+ QtPrintSupport.QPrinter.Portrait) - if self.portraitOrient: - portraitButton.setChecked(True) - else: - landscapeButton.setChecked(True) - portraitButton.toggled.connect(self.changeOrient) - -- rightLayout = QtGui.QVBoxLayout() -+ rightLayout = QtWidgets.QVBoxLayout() - topLayout.addLayout(rightLayout) - -- marginsBox = QtGui.QGroupBox(_('Margins')) -+ marginsBox = QtWidgets.QGroupBox(_('Margins')) - rightLayout.addWidget(marginsBox) -- marginsLayout = QtGui.QGridLayout(marginsBox) -+ marginsLayout = QtWidgets.QGridLayout(marginsBox) - spacing = marginsLayout.spacing() - marginsLayout.setVerticalSpacing(0) - marginsLayout.setRowMinimumHeight(2, spacing) - marginsLayout.setRowMinimumHeight(5, spacing) -- leftLabel = QtGui.QLabel(_('&Left:')) -+ leftLabel = QtWidgets.QLabel(_('&Left:')) - marginsLayout.addWidget(leftLabel, 3, 0) - leftMarginSpin = UnitSpinBox(self.currentUnit) - marginsLayout.addWidget(leftMarginSpin, 4, 0) - leftLabel.setBuddy(leftMarginSpin) -- topLabel = QtGui.QLabel(_('&Top:')) -+ topLabel = QtWidgets.QLabel(_('&Top:')) - marginsLayout.addWidget(topLabel, 0, 1) - topMarginSpin = UnitSpinBox(self.currentUnit) - marginsLayout.addWidget(topMarginSpin, 1, 1) - topLabel.setBuddy(topMarginSpin) -- rightLabel = QtGui.QLabel(_('&Right:')) -+ rightLabel = QtWidgets.QLabel(_('&Right:')) - marginsLayout.addWidget(rightLabel, 3, 2) - rightMarginSpin = UnitSpinBox(self.currentUnit) - marginsLayout.addWidget(rightMarginSpin, 4, 2) - rightLabel.setBuddy(rightMarginSpin) -- bottomLabel = QtGui.QLabel(_('&Bottom:')) -+ bottomLabel = QtWidgets.QLabel(_('&Bottom:')) - marginsLayout.addWidget(bottomLabel, 6, 1) - bottomMarginSpin = UnitSpinBox(self.currentUnit) - marginsLayout.addWidget(bottomMarginSpin, 7, 1) -@@ -596,31 +596,31 @@ class PageSetupPage(QtGui.QWidget): - for control, value in zip(self.marginControls, - self.printData.roundedMargins()): - control.setInchValue(value) -- headerLabel = QtGui.QLabel(_('He&ader:')) -+ headerLabel = QtWidgets.QLabel(_('He&ader:')) - marginsLayout.addWidget(headerLabel, 0, 2) - self.headerMarginSpin = UnitSpinBox(self.currentUnit) - marginsLayout.addWidget(self.headerMarginSpin, 1, 2) - headerLabel.setBuddy(self.headerMarginSpin) - self.headerMarginSpin.setInchValue(self.printData.headerMargin) -- footerLabel = QtGui.QLabel(_('Foot&er:')) -+ footerLabel = QtWidgets.QLabel(_('Foot&er:')) - marginsLayout.addWidget(footerLabel, 6, 2) - self.footerMarginSpin = UnitSpinBox(self.currentUnit) - marginsLayout.addWidget(self.footerMarginSpin, 7, 2) - footerLabel.setBuddy(self.footerMarginSpin) - self.footerMarginSpin.setInchValue(self.printData.footerMargin) - -- columnsBox = QtGui.QGroupBox(_('Columns')) -+ columnsBox = QtWidgets.QGroupBox(_('Columns')) - rightLayout.addWidget(columnsBox) -- columnLayout = QtGui.QGridLayout(columnsBox) -- numLabel = QtGui.QLabel(_('&Number of columns')) -+ columnLayout = QtWidgets.QGridLayout(columnsBox) -+ numLabel = QtWidgets.QLabel(_('&Number of columns')) - columnLayout.addWidget(numLabel, 0, 0) -- self.columnSpin = QtGui.QSpinBox() -+ self.columnSpin = QtWidgets.QSpinBox() - columnLayout.addWidget(self.columnSpin, 0, 1) - numLabel.setBuddy(self.columnSpin) - self.columnSpin.setMinimum(1) - self.columnSpin.setMaximum(9) - self.columnSpin.setValue(self.printData.numColumns) -- spaceLabel = QtGui.QLabel(_('Space between colu&mns')) -+ spaceLabel = QtWidgets.QLabel(_('Space between colu&mns')) - columnLayout.addWidget(spaceLabel, 1, 0) - self.columnSpaceSpin = UnitSpinBox(self.currentUnit) - columnLayout.addWidget(self.columnSpaceSpin, 1, 1) -@@ -651,12 +651,12 @@ class PageSetupPage(QtGui.QWidget): - """ - self.currentPaperSize = list(_paperSizes.keys())[paperNum] - if self.currentPaperSize != 'Custom': -- tempPrinter = QtGui.QPrinter() -- tempPrinter.setPaperSize(getattr(QtGui.QPrinter, -+ tempPrinter = QtPrintSupport.QPrinter() -+ tempPrinter.setPaperSize(getattr(QtPrintSupport.QPrinter, - self.currentPaperSize)) - if not self.portraitOrient: -- tempPrinter.setOrientation(QtGui.QPrinter.Landscape) -- paperSize = tempPrinter.paperSize(QtGui.QPrinter.Inch) -+ tempPrinter.setOrientation(QtPrintSupport.QPrinter.Landscape) -+ paperSize = tempPrinter.paperSize(QtPrintSupport.QPrinter.Inch) - self.paperWidthSpin.setInchValue(round(paperSize.width(), 2)) - self.paperHeightSpin.setInchValue(round(paperSize.height(), 2)) - self.paperWidthSpin.setEnabled(self.currentPaperSize == 'Custom') -@@ -686,7 +686,7 @@ class PageSetupPage(QtGui.QWidget): - globalref.miscOptions.writeFile() - changed = False - if self.currentPaperSize != 'Custom': -- size = getattr(QtGui.QPrinter, self.currentPaperSize) -+ size = getattr(QtPrintSupport.QPrinter, self.currentPaperSize) - if size != self.printData.printer.paperSize(): - self.printData.printer.setPaperSize(size) - changed = True -@@ -695,16 +695,16 @@ class PageSetupPage(QtGui.QWidget): - self.paperHeightSpin.inchValue) - if size != self.printData.roundedPaperSize(): - self.printData.printer.setPaperSize(QtCore.QSizeF(*size), -- QtGui.QPrinter.Inch) -+ QtPrintSupport.QPrinter.Inch) - changed = True -- orient = (QtGui.QPrinter.Portrait if self.portraitOrient else -- QtGui.QPrinter.Landscape) -+ orient = (QtPrintSupport.QPrinter.Portrait if self.portraitOrient else -+ QtPrintSupport.QPrinter.Landscape) - if orient != self.printData.printer.orientation(): - self.printData.printer.setOrientation(orient) - changed = True - margins = [control.inchValue for control in self.marginControls] - if margins != self.printData.roundedMargins(): -- margins.append(QtGui.QPrinter.Inch) -+ margins.append(QtPrintSupport.QPrinter.Inch) - self.printData.printer.setPageMargins(*margins) - changed = True - if self.printData.headerMargin != self.headerMarginSpin.inchValue: -@@ -722,7 +722,7 @@ class PageSetupPage(QtGui.QWidget): - return changed - - --class UnitSpinBox(QtGui.QDoubleSpinBox): -+class UnitSpinBox(QtWidgets.QDoubleSpinBox): - """Spin box with unit suffix that can convert the units of its contents. - - Stores the value at full precision to avoid round-trip rounding errors. -@@ -785,10 +785,10 @@ class UnitSpinBox(QtGui.QDoubleSpinBox): - self.inchValue = round(self.value() / _unitValues[self.unit], 2) - - --class SmallListWidget(QtGui.QListWidget): -+class SmallListWidget(QtWidgets.QListWidget): - """ListWidget with a smaller size hint""" - def __init__(self, parent=None): -- QtGui.QListWidget.__init__(self, parent) -+ QtWidgets.QListWidget.__init__(self, parent) - - def sizeHint(self): - """Return smaller width""" -@@ -796,7 +796,7 @@ class SmallListWidget(QtGui.QListWidget) - return QtCore.QSize(100, itemHeight * 3) - - --class FontPage(QtGui.QWidget): -+class FontPage(QtWidgets.QWidget): - """Font selection print option dialog page. - """ - def __init__(self, printData, useSysDfltLabel=False, parent=None): -@@ -811,28 +811,28 @@ class FontPage(QtGui.QWidget): - self.printData = printData - self.currentFont = self.printData.mainFont - -- topLayout = QtGui.QVBoxLayout(self) -+ topLayout = QtWidgets.QVBoxLayout(self) - self.setLayout(topLayout) -- defaultBox = QtGui.QGroupBox(_('Default Font')) -+ defaultBox = QtWidgets.QGroupBox(_('Default Font')) - topLayout.addWidget(defaultBox) -- defaultLayout = QtGui.QVBoxLayout(defaultBox) -+ defaultLayout = QtWidgets.QVBoxLayout(defaultBox) - defaultText = (_('&Use system default font') if useSysDfltLabel else - _('&Use TreeLine output view font')) -- self.defaultCheck = QtGui.QCheckBox(defaultText) -+ self.defaultCheck = QtWidgets.QCheckBox(defaultText) - defaultLayout.addWidget(self.defaultCheck) - self.defaultCheck.setChecked(self.printData.useDefaultFont) - self.defaultCheck.clicked.connect(self.setFontSelectAvail) - -- self.fontBox = QtGui.QGroupBox(_('Select Font')) -+ self.fontBox = QtWidgets.QGroupBox(_('Select Font')) - topLayout.addWidget(self.fontBox) -- fontLayout = QtGui.QGridLayout(self.fontBox) -+ fontLayout = QtWidgets.QGridLayout(self.fontBox) - spacing = fontLayout.spacing() - fontLayout.setSpacing(0) - -- label = QtGui.QLabel(_('&Font')) -+ label = QtWidgets.QLabel(_('&Font')) - fontLayout.addWidget(label, 0, 0) - label.setIndent(2) -- self.familyEdit = QtGui.QLineEdit() -+ self.familyEdit = QtWidgets.QLineEdit() - fontLayout.addWidget(self.familyEdit, 1, 0) - self.familyEdit.setReadOnly(True) - self.familyList = SmallListWidget() -@@ -845,10 +845,10 @@ class FontPage(QtGui.QWidget): - self.familyList.addItems(families) - self.familyList.currentItemChanged.connect(self.updateFamily) - -- label = QtGui.QLabel(_('Font st&yle')) -+ label = QtWidgets.QLabel(_('Font st&yle')) - fontLayout.addWidget(label, 0, 2) - label.setIndent(2) -- self.styleEdit = QtGui.QLineEdit() -+ self.styleEdit = QtWidgets.QLineEdit() - fontLayout.addWidget(self.styleEdit, 1, 2) - self.styleEdit.setReadOnly(True) - self.styleList = SmallListWidget() -@@ -858,10 +858,10 @@ class FontPage(QtGui.QWidget): - fontLayout.setColumnMinimumWidth(3, spacing) - self.styleList.currentItemChanged.connect(self.updateStyle) - -- label = QtGui.QLabel(_('Si&ze')) -+ label = QtWidgets.QLabel(_('Si&ze')) - fontLayout.addWidget(label, 0, 4) - label.setIndent(2) -- self.sizeEdit = QtGui.QLineEdit() -+ self.sizeEdit = QtWidgets.QLineEdit() - fontLayout.addWidget(self.sizeEdit, 1, 4) - self.sizeEdit.setFocusPolicy(QtCore.Qt.ClickFocus) - validator = QtGui.QIntValidator(1, 512, self) -@@ -875,10 +875,10 @@ class FontPage(QtGui.QWidget): - fontLayout.setColumnStretch(2, 25) - fontLayout.setColumnStretch(4, 10) - -- sampleBox = QtGui.QGroupBox(_('Sample')) -+ sampleBox = QtWidgets.QGroupBox(_('Sample')) - topLayout.addWidget(sampleBox) -- sampleLayout = QtGui.QVBoxLayout(sampleBox) -- self.sampleEdit = QtGui.QLineEdit() -+ sampleLayout = QtWidgets.QVBoxLayout(sampleBox) -+ self.sampleEdit = QtWidgets.QLineEdit() - sampleLayout.addWidget(self.sampleEdit) - self.sampleEdit.setAlignment(QtCore.Qt.AlignCenter) - self.sampleEdit.setText(_('AaBbCcDdEeFfGg...TtUuVvWvXxYyZz')) -@@ -913,7 +913,7 @@ class FontPage(QtGui.QWidget): - if matches: - self.familyList.setCurrentItem(matches[0]) - self.familyList.scrollToItem(matches[0], -- QtGui.QAbstractItemView.PositionAtTop) -+ QtWidgets.QAbstractItemView.PositionAtTop) - style = QtGui.QFontDatabase().styleString(fontInfo) - matches = self.styleList.findItems(style, QtCore.Qt.MatchExactly) - if matches: -@@ -1029,7 +1029,7 @@ class FontPage(QtGui.QWidget): - _headerNames = (_('&Header Left'), _('Header C&enter'), _('Header &Right')) - _footerNames = (_('Footer &Left'), _('Footer Ce&nter'), _('Footer Righ&t')) - --class HeaderPage(QtGui.QWidget): -+class HeaderPage(QtWidgets.QWidget): - """Header/footer print option dialog page. - """ - def __init__(self, printData, parent=None): -@@ -1043,31 +1043,31 @@ class HeaderPage(QtGui.QWidget): - self.printData = printData - self.focusedEditor = None - -- topLayout = QtGui.QGridLayout(self) -- fieldBox = QtGui.QGroupBox(_('Fiel&ds')) -+ topLayout = QtWidgets.QGridLayout(self) -+ fieldBox = QtWidgets.QGroupBox(_('Fiel&ds')) - topLayout.addWidget(fieldBox, 0, 0, 3, 1) -- fieldLayout = QtGui.QVBoxLayout(fieldBox) -+ fieldLayout = QtWidgets.QVBoxLayout(fieldBox) - self.fieldListWidget = FieldListWidget() - fieldLayout.addWidget(self.fieldListWidget) -- fieldFormatButton = QtGui.QPushButton(_('Field For&mat')) -+ fieldFormatButton = QtWidgets.QPushButton(_('Field For&mat')) - fieldLayout.addWidget(fieldFormatButton) - fieldFormatButton.clicked.connect(self.showFieldFormatDialog) - -- self.addFieldButton = QtGui.QPushButton('>>') -+ self.addFieldButton = QtWidgets.QPushButton('>>') - topLayout.addWidget(self.addFieldButton, 0, 1) - self.addFieldButton.setMaximumWidth(self.addFieldButton.sizeHint(). - height()) - self.addFieldButton.clicked.connect(self.addField) - -- self.delFieldButton = QtGui.QPushButton('<<') -+ self.delFieldButton = QtWidgets.QPushButton('<<') - topLayout.addWidget(self.delFieldButton, 1, 1) - self.delFieldButton.setMaximumWidth(self.delFieldButton.sizeHint(). - height()) - self.delFieldButton.clicked.connect(self.delField) - -- headerFooterBox = QtGui.QGroupBox(_('Header and Footer')) -+ headerFooterBox = QtWidgets.QGroupBox(_('Header and Footer')) - topLayout.addWidget(headerFooterBox, 0, 2, 2, 1) -- headerFooterLayout = QtGui.QGridLayout(headerFooterBox) -+ headerFooterLayout = QtWidgets.QGridLayout(headerFooterBox) - spacing = headerFooterLayout.spacing() - headerFooterLayout.setVerticalSpacing(0) - headerFooterLayout.setRowMinimumHeight(2, spacing) -@@ -1089,7 +1089,7 @@ class HeaderPage(QtGui.QWidget): - """ - lineEdits = [] - for num, name in enumerate(names): -- label = QtGui.QLabel(name) -+ label = QtWidgets.QLabel(name) - layout.addWidget(label, startRow, num) - lineEdit = configdialog.TitleEdit() - layout.addWidget(lineEdit, startRow + 1, num) -@@ -1213,7 +1213,7 @@ class HeaderPage(QtGui.QWidget): - return changed - - --class FieldListWidget(QtGui.QListWidget): -+class FieldListWidget(QtWidgets.QListWidget): - """List widget for fields with smaller width size hint. - """ - def __init__(self, parent=None): -@@ -1230,7 +1230,7 @@ class FieldListWidget(QtGui.QListWidget) - return QtCore.QSize(120, 100) - - --class HeaderFieldFormatDialog(QtGui.QDialog): -+class HeaderFieldFormatDialog(QtWidgets.QDialog): - """Dialog to modify file info field formats used in headers and footers. - """ - def __init__(self, field, localControl, parent=None): -@@ -1247,42 +1247,42 @@ class HeaderFieldFormatDialog(QtGui.QDia - self.setWindowFlags(QtCore.Qt.Dialog | QtCore.Qt.WindowTitleHint | - QtCore.Qt.WindowSystemMenuHint) - self.setWindowTitle(_('Field Format for "{0}"').format(field.name)) -- topLayout = QtGui.QVBoxLayout(self) -+ topLayout = QtWidgets.QVBoxLayout(self) - self.setLayout(topLayout) - -- self.formatBox = QtGui.QGroupBox(_('Output &Format')) -+ self.formatBox = QtWidgets.QGroupBox(_('Output &Format')) - topLayout.addWidget(self.formatBox) -- formatLayout = QtGui.QHBoxLayout(self.formatBox) -- self.formatEdit = QtGui.QLineEdit() -+ formatLayout = QtWidgets.QHBoxLayout(self.formatBox) -+ self.formatEdit = QtWidgets.QLineEdit() - formatLayout.addWidget(self.formatEdit) -- self.helpButton = QtGui.QPushButton(_('Format &Help')) -+ self.helpButton = QtWidgets.QPushButton(_('Format &Help')) - formatLayout.addWidget(self.helpButton) - self.helpButton.clicked.connect(self.formatHelp) - -- extraBox = QtGui.QGroupBox(_('Extra Text')) -+ extraBox = QtWidgets.QGroupBox(_('Extra Text')) - topLayout.addWidget(extraBox) -- extraLayout = QtGui.QVBoxLayout(extraBox) -+ extraLayout = QtWidgets.QVBoxLayout(extraBox) - spacing = extraLayout.spacing() - extraLayout.setSpacing(0) -- prefixLabel = QtGui.QLabel(_('&Prefix')) -+ prefixLabel = QtWidgets.QLabel(_('&Prefix')) - extraLayout.addWidget(prefixLabel) -- self.prefixEdit = QtGui.QLineEdit() -+ self.prefixEdit = QtWidgets.QLineEdit() - extraLayout.addWidget(self.prefixEdit) - prefixLabel.setBuddy(self.prefixEdit) - extraLayout.addSpacing(spacing) -- suffixLabel = QtGui.QLabel(_('&Suffix')) -+ suffixLabel = QtWidgets.QLabel(_('&Suffix')) - extraLayout.addWidget(suffixLabel) -- self.suffixEdit = QtGui.QLineEdit() -+ self.suffixEdit = QtWidgets.QLineEdit() - extraLayout.addWidget(self.suffixEdit) - suffixLabel.setBuddy(self.suffixEdit) - -- ctrlLayout = QtGui.QHBoxLayout() -+ ctrlLayout = QtWidgets.QHBoxLayout() - topLayout.addLayout(ctrlLayout) - ctrlLayout.addStretch() -- okButton = QtGui.QPushButton(_('&OK')) -+ okButton = QtWidgets.QPushButton(_('&OK')) - ctrlLayout.addWidget(okButton) - okButton.clicked.connect(self.accept) -- cancelButton = QtGui.QPushButton(_('&Cancel')) -+ cancelButton = QtWidgets.QPushButton(_('&Cancel')) - ctrlLayout.addWidget(cancelButton) - cancelButton.clicked.connect(self.reject) - -@@ -1295,7 +1295,7 @@ class HeaderFieldFormatDialog(QtGui.QDia - def formatHelp(self): - """Provide a format help menu based on a button signal. - """ -- menu = QtGui.QMenu(self) -+ menu = QtWidgets.QMenu(self) - self.formatHelpDict = {} - for descript, key in self.field.getFormatHelpMenuList(): - if descript: -Index: TreeLine/source/recentfiles.py -=================================================================== ---- TreeLine.orig/source/recentfiles.py -+++ TreeLine/source/recentfiles.py -@@ -14,7 +14,7 @@ - - import os.path - import time --from PyQt4 import QtCore, QtGui -+from PyQt5 import QtCore, QtGui, QtWidgets - import globalref - import options - -@@ -39,7 +39,7 @@ class RecentFileItem: - self.scrollPos = '' - self.selectNodes = '' - self.openNodes = '' -- self.action = QtGui.QAction(globalref.mainControl) -+ self.action = QtWidgets.QAction(globalref.mainControl) - self.action.triggered.connect(self.openFile) - if path: - self.setPath(path) -@@ -180,7 +180,7 @@ class RecentFileItem: - topNode = localControl.model.nodeIdDict.get(self.scrollPos, None) - if topNode: - treeView.scrollTo(topNode.index(), -- QtGui.QAbstractItemView.PositionAtTop) -+ QtWidgets.QAbstractItemView.PositionAtTop) - if self.selectNodes: - nodes = [localControl.model.nodeIdDict.get(nodeId, None) for - nodeId in self.selectNodes.split(',')] -Index: TreeLine/source/spellcheck.py -=================================================================== ---- TreeLine.orig/source/spellcheck.py -+++ TreeLine/source/spellcheck.py -@@ -16,7 +16,7 @@ import re - import sys - import subprocess - import collections --from PyQt4 import QtCore, QtGui -+from PyQt5 import QtCore, QtGui, QtWidgets - import undo - import globalref - -@@ -179,7 +179,7 @@ class SpellCheckOperation: - self.currentField = '' - self.lineNum = 0 - self.textLine = '' -- parentWidget = QtGui.QApplication.activeWindow() -+ parentWidget = QtWidgets.QApplication.activeWindow() - path = globalref.miscOptions.getValue('SpellCheckPath') - while True: - try: -@@ -195,19 +195,19 @@ class SpellCheckOperation: - prompt = (_('Could not find either aspell.exe, ' - 'ispell.exe or hunspell.exe\n' - 'Browse for location?')) -- ans = QtGui.QMessageBox.warning(parentWidget, -+ ans = QtWidgets.QMessageBox.warning(parentWidget, - _('Spell Check Error'), - prompt, -- QtGui.QMessageBox.Yes | -- QtGui.QMessageBox.Cancel, -- QtGui.QMessageBox.Yes) -- if ans == QtGui.QMessageBox.Cancel: -+ QtWidgets.QMessageBox.Yes | -+ QtWidgets.QMessageBox.Cancel, -+ QtWidgets.QMessageBox.Yes) -+ if ans == QtWidgets.QMessageBox.Cancel: - raise - title = _('Locate aspell.exe, ipsell.exe or ' - 'hunspell.exe') -- path = QtGui.QFileDialog.getOpenFileName(parentWidget, -+ path = QtWidgets.QFileDialog.getOpenFileName(parentWidget, - title, '', -- _('Program (*.exe)')) -+ _('Program (*.exe)'))[0] - if path: - path = path[:-4] - if ' ' in path: -@@ -218,14 +218,14 @@ class SpellCheckOperation: - else: - prompt = (_('TreeLine Spell Check Error\nMake sure ' - 'aspell, ispell or hunspell is installed')) -- QtGui.QMessageBox.warning(parentWidget, 'TreeLine', -+ QtWidgets.QMessageBox.warning(parentWidget, 'TreeLine', - prompt) - raise - - def spellCheck(self): - """Spell check starting with the selected branches. - """ -- parentWidget = QtGui.QApplication.activeWindow() -+ parentWidget = QtWidgets.QApplication.activeWindow() - spellCheckDialog = SpellCheckDialog(self.spellCheckInterface, - parentWidget) - spellCheckDialog.misspellFound.connect(self.updateSelection) -@@ -237,12 +237,12 @@ class SpellCheckOperation: - if result and origBranches[0].parent: - prompt = (_('Finished checking the branch\n' - 'Continue from the root branch?')) -- ans = QtGui.QMessageBox.information(parentWidget, -+ ans = QtWidgets.QMessageBox.information(parentWidget, - _('TreeLine Spell Check'), - prompt, -- QtGui.QMessageBox.Yes | -- QtGui.QMessageBox.No) -- if ans == QtGui.QMessageBox.Yes: -+ QtWidgets.QMessageBox.Yes | -+ QtWidgets.QMessageBox.No) -+ if ans == QtWidgets.QMessageBox.Yes: - generator = self.textLineGenerator([self.controlRef.model. - root]) - result = spellCheckDialog.startSpellCheck(generator) -@@ -251,7 +251,7 @@ class SpellCheckOperation: - else: - result = False - if result: -- QtGui.QMessageBox.information(parentWidget, -+ QtWidgets.QMessageBox.information(parentWidget, - _('TreeLine Spell Check'), - _('Finished spell checking')) - -@@ -290,7 +290,7 @@ class SpellCheckOperation: - yield self.textLine - - --class SpellCheckDialog(QtGui.QDialog): -+class SpellCheckDialog(QtWidgets.QDialog): - """Dialog to perform and control the spell check operation. - """ - misspellFound = QtCore.pyqtSignal() -@@ -314,55 +314,55 @@ class SpellCheckDialog(QtGui.QDialog): - self.word = '' - self.postion = 0 - -- topLayout = QtGui.QHBoxLayout(self) -- leftLayout = QtGui.QVBoxLayout() -+ topLayout = QtWidgets.QHBoxLayout(self) -+ leftLayout = QtWidgets.QVBoxLayout() - topLayout.addLayout(leftLayout) -- wordBox = QtGui.QGroupBox(_('Not in Dictionary')) -+ wordBox = QtWidgets.QGroupBox(_('Not in Dictionary')) - leftLayout.addWidget(wordBox) -- wordLayout = QtGui.QVBoxLayout(wordBox) -- label = QtGui.QLabel(_('Word:')) -+ wordLayout = QtWidgets.QVBoxLayout(wordBox) -+ label = QtWidgets.QLabel(_('Word:')) - wordLayout.addWidget(label) -- self.wordEdit = QtGui.QLineEdit() -+ self.wordEdit = QtWidgets.QLineEdit() - wordLayout.addWidget(self.wordEdit) - self.wordEdit.textChanged.connect(self.updateFromWord) - wordLayout.addSpacing(5) -- label = QtGui.QLabel(_('Context:')) -+ label = QtWidgets.QLabel(_('Context:')) - wordLayout.addWidget(label) - self.contextEdit = SpellContextEdit() - wordLayout.addWidget(self.contextEdit) - self.contextEdit.textChanged.connect(self.updateFromContext) - -- suggestBox = QtGui.QGroupBox(_('Suggestions')) -+ suggestBox = QtWidgets.QGroupBox(_('Suggestions')) - leftLayout.addWidget(suggestBox) -- suggestLayout = QtGui.QVBoxLayout(suggestBox) -- self.suggestList = QtGui.QListWidget() -+ suggestLayout = QtWidgets.QVBoxLayout(suggestBox) -+ self.suggestList = QtWidgets.QListWidget() - suggestLayout.addWidget(self.suggestList) - self.suggestList.itemDoubleClicked.connect(self.replace) - -- rightLayout = QtGui.QVBoxLayout() -+ rightLayout = QtWidgets.QVBoxLayout() - topLayout.addLayout(rightLayout) -- ignoreButton = QtGui.QPushButton(_('Ignor&e')) -+ ignoreButton = QtWidgets.QPushButton(_('Ignor&e')) - rightLayout.addWidget(ignoreButton) - ignoreButton.clicked.connect(self.ignore) -- ignoreAllButton = QtGui.QPushButton(_('&Ignore All')) -+ ignoreAllButton = QtWidgets.QPushButton(_('&Ignore All')) - rightLayout.addWidget(ignoreAllButton) - ignoreAllButton.clicked.connect(self.ignoreAll) - rightLayout.addStretch() -- addButton = QtGui.QPushButton(_('&Add')) -+ addButton = QtWidgets.QPushButton(_('&Add')) - rightLayout.addWidget(addButton) - addButton.clicked.connect(self.add) -- addLowerButton = QtGui.QPushButton(_('Add &Lowercase')) -+ addLowerButton = QtWidgets.QPushButton(_('Add &Lowercase')) - rightLayout.addWidget(addLowerButton) - addLowerButton.clicked.connect(self.addLower) - rightLayout.addStretch() -- replaceButton = QtGui.QPushButton(_('&Replace')) -+ replaceButton = QtWidgets.QPushButton(_('&Replace')) - rightLayout.addWidget(replaceButton) - replaceButton.clicked.connect(self.replace) -- self.replaceAllButton = QtGui.QPushButton(_('Re&place All')) -+ self.replaceAllButton = QtWidgets.QPushButton(_('Re&place All')) - rightLayout.addWidget(self.replaceAllButton) - self.replaceAllButton.clicked.connect(self.replaceAll) - rightLayout.addStretch() -- cancelButton = QtGui.QPushButton(_('&Cancel')) -+ cancelButton = QtWidgets.QPushButton(_('&Cancel')) - rightLayout.addWidget(cancelButton) - cancelButton.clicked.connect(self.reject) - self.widgetDisableList = [ignoreButton, ignoreAllButton, addButton, -@@ -384,7 +384,7 @@ class SpellCheckDialog(QtGui.QDialog): - except StopIteration: - return True - if self.spellCheck(): -- if self.exec_() == QtGui.QDialog.Rejected: -+ if self.exec_() == QtWidgets.QDialog.Rejected: - return False - return True - -@@ -525,7 +525,7 @@ class SpellCheckDialog(QtGui.QDialog): - self.suggestList.clearSelection() - - --class SpellContextEdit(QtGui.QTextEdit): -+class SpellContextEdit(QtWidgets.QTextEdit): - """Editor for spell check word context. - - Sets the size hint to 3 lines and simplifies selction. -@@ -543,7 +543,7 @@ class SpellContextEdit(QtGui.QTextEdit): - """Set prefered size of 3 lines long. - """ - fontHeight = QtGui.QFontMetrics(self.currentFont()).lineSpacing() -- return QtCore.QSize(QtGui.QTextEdit.sizeHint(self).width(), -+ return QtCore.QSize(QtWidgets.QTextEdit.sizeHint(self).width(), - fontHeight * 3) - - def setSelection(self, fromPos, toPos): -Index: TreeLine/source/titlelistview.py -=================================================================== ---- TreeLine.orig/source/titlelistview.py -+++ TreeLine/source/titlelistview.py -@@ -12,13 +12,13 @@ - # but WITTHOUT ANY WARRANTY. See the included LICENSE file for details. - #****************************************************************************** - --from PyQt4 import QtCore, QtGui -+from PyQt5 import QtCore, QtGui, QtWidgets - import treenode - import undo - import globalref - - --class TitleListView(QtGui.QTextEdit): -+class TitleListView(QtWidgets.QTextEdit): - """Class override for the title list view. - - Sets view defaults and updates the content. -@@ -39,7 +39,7 @@ class TitleListView(QtGui.QTextEdit): - self.isChildView = isChildView - self.hideChildView = not globalref.genOptions.getValue('ShowChildPane') - self.setAcceptRichText(False) -- self.setLineWrapMode(QtGui.QTextEdit.NoWrap) -+ self.setLineWrapMode(QtWidgets.QTextEdit.NoWrap) - self.setTabChangesFocus(True) - self.setUndoRedoEnabled(False) - self.textChanged.connect(self.readChange) -@@ -117,7 +117,7 @@ class TitleListView(QtGui.QTextEdit): - selections = [] - for word in wordList: - while self.find(word): -- extraSel = QtGui.QTextEdit.ExtraSelection() -+ extraSel = QtWidgets.QTextEdit.ExtraSelection() - extraSel.cursor = self.textCursor() - extraSel.format.setBackground(backColor) - extraSel.format.setForeground(foreColor) -Index: TreeLine/source/treeline.py -=================================================================== ---- TreeLine.orig/source/treeline.py -+++ TreeLine/source/treeline.py -@@ -28,7 +28,7 @@ import os.path - import argparse - import locale - import builtins --from PyQt4 import QtCore, QtGui -+from PyQt5 import QtCore, QtGui, QtWidgets - - - def loadTranslator(fileName, app): -@@ -122,7 +122,7 @@ def setupTranslator(app, lang=''): - def main(): - """Main event loop function for TreeLine - """ -- app = QtGui.QApplication(sys.argv) -+ app = QtWidgets.QApplication(sys.argv) - parser = argparse.ArgumentParser() - parser.add_argument('--lang', help='language code for GUI translation') - parser.add_argument('fileList', nargs='*', metavar='filename', -Index: TreeLine/source/treelocalcontrol.py -=================================================================== ---- TreeLine.orig/source/treelocalcontrol.py -+++ TreeLine/source/treelocalcontrol.py -@@ -18,7 +18,7 @@ import sys - import gzip - import zlib - from xml.etree import ElementTree --from PyQt4 import QtCore, QtGui -+from PyQt5 import QtCore, QtGui, QtWidgets - import treemaincontrol - import treemodel - import treewindow -@@ -79,7 +79,7 @@ class TreeLocalControl(QtCore.QObject): - msg += _('The following IDs were re-assigned:\n\t') - msg += '\n\t'.join(opener.duplicateIdList) - msg += _('\nInternal link targets could be affected.') -- QtGui.QMessageBox.warning(None, 'TreeLine', msg) -+ QtWidgets.QMessageBox.warning(None, 'TreeLine', msg) - else: - self.model = treemodel.TreeModel(True) - self.model.allModified.connect(self.updateAll) -@@ -93,7 +93,7 @@ class TreeLocalControl(QtCore.QObject): - self.windowList = [] - self.activeWindow = None - self.findReplaceNodeRef = (None, 0) -- QtGui.QApplication.clipboard().dataChanged.connect(self. -+ QtWidgets.QApplication.clipboard().dataChanged.connect(self. - updatePasteAvail) - self.updatePasteAvail() - self.model.undoList = undo.UndoRedoList(self.allActions['EditUndo'], -@@ -138,7 +138,7 @@ class TreeLocalControl(QtCore.QObject): - Arguments: - setModified -- if True, set the modified flag for this file - """ -- QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) -+ QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) - typeChanges = self.model.root.setDescendantConditionalTypes() - self.updateAllMathFields() - for window in self.windowList: -@@ -149,7 +149,7 @@ class TreeLocalControl(QtCore.QObject): - window.treeFilterView.updateContents() - if setModified: - self.setModified() -- QtGui.QApplication.restoreOverrideCursor() -+ QtWidgets.QApplication.restoreOverrideCursor() - - def updateRightViews(self, setModified=False): - """Update the right-hand view in all windows. -@@ -168,7 +168,7 @@ class TreeLocalControl(QtCore.QObject): - Arguments: - setModified -- if True, set the modified flag for this file - """ -- QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) -+ QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) - self.model.root.setDescendantConditionalTypes() - self.updateAllMathFields() - if (globalref.mainControl.findConditionDialog and -@@ -188,7 +188,7 @@ class TreeLocalControl(QtCore.QObject): - self.updateCommandsAvail() - if setModified: - self.setModified() -- QtGui.QApplication.restoreOverrideCursor() -+ QtWidgets.QApplication.restoreOverrideCursor() - - def updateCommandsAvail(self): - """Set commands available based on node selections. -@@ -244,11 +244,11 @@ class TreeLocalControl(QtCore.QObject): - def updatePasteAvail(self): - """Set paste available based on a signal. - """ -- mime = QtGui.QApplication.clipboard().mimeData() -+ mime = QtWidgets.QApplication.clipboard().mimeData() - self.allActions['EditPaste'].setEnabled(len(mime.data('text/xml') or - mime.data('text/plain')) - > 0) -- focusWidget = QtGui.QApplication.focusWidget() -+ focusWidget = QtWidgets.QApplication.focusWidget() - if hasattr(focusWidget, 'pastePlain'): - focusWidget.updateActions() - -@@ -319,7 +319,7 @@ class TreeLocalControl(QtCore.QObject): - pos = len(self.filePath) - truncLength - abbrevPath = '...' + self.filePath[pos:] - for window in self.windowList: -- action = QtGui.QAction('&{0:d} {1}'.format(startNum, abbrevPath), -+ action = QtWidgets.QAction('&{0:d} {1}'.format(startNum, abbrevPath), - self, statusTip=self.filePath, - checkable=True) - action.triggered.connect(window.activateAndRaise) -@@ -357,7 +357,7 @@ class TreeLocalControl(QtCore.QObject): - try: - os.remove(filePath) - except OSError: -- QtGui.QMessageBox.warning(self.activeWindow, 'TreeLine', -+ QtWidgets.QMessageBox.warning(self.activeWindow, 'TreeLine', - _('Error - could not delete backup file {}'). - format(filePath)) - -@@ -389,15 +389,15 @@ class TreeLocalControl(QtCore.QObject): - return True - promptText = (_('Save changes to {}?').format(self.filePath) if - self.filePath else _('Save changes?')) -- ans = QtGui.QMessageBox.information(self.activeWindow, 'TreeLine', -+ ans = QtWidgets.QMessageBox.information(self.activeWindow, 'TreeLine', - promptText, -- QtGui.QMessageBox.Save | -- QtGui.QMessageBox.Discard | -- QtGui.QMessageBox.Cancel, -- QtGui.QMessageBox.Save) -- if ans == QtGui.QMessageBox.Save: -+ QtWidgets.QMessageBox.Save | -+ QtWidgets.QMessageBox.Discard | -+ QtWidgets.QMessageBox.Cancel, -+ QtWidgets.QMessageBox.Save) -+ if ans == QtWidgets.QMessageBox.Save: - self.fileSave() -- elif ans == QtGui.QMessageBox.Cancel: -+ elif ans == QtWidgets.QMessageBox.Cancel: - return False - else: # discard - self.setModified(False) -@@ -427,90 +427,90 @@ class TreeLocalControl(QtCore.QObject): - """ - localActions = {} - -- fileSaveAct = QtGui.QAction(_('&Save'), self, toolTip=_('Save File'), -+ fileSaveAct = QtWidgets.QAction(_('&Save'), self, toolTip=_('Save File'), - statusTip=_('Save the current file')) - fileSaveAct.setEnabled(False) - fileSaveAct.triggered.connect(self.fileSave) - localActions['FileSave'] = fileSaveAct - -- fileSaveAsAct = QtGui.QAction(_('Save &As...'), self, -+ fileSaveAsAct = QtWidgets.QAction(_('Save &As...'), self, - statusTip=_('Save the file with a new name')) - fileSaveAsAct.triggered.connect(self.fileSaveAs) - localActions['FileSaveAs'] = fileSaveAsAct - -- fileExportAct = QtGui.QAction(_('&Export...'), self, -+ fileExportAct = QtWidgets.QAction(_('&Export...'), self, - statusTip=_('Export the file in various other formats')) - fileExportAct.triggered.connect(self.fileExport) - localActions['FileExport'] = fileExportAct - -- filePropertiesAct = QtGui.QAction(_('Prop&erties...'), self, -+ filePropertiesAct = QtWidgets.QAction(_('Prop&erties...'), self, - statusTip=_('Set file parameters like compression and encryption')) - filePropertiesAct.triggered.connect(self.fileProperties) - localActions['FileProperties'] = filePropertiesAct - -- filePrintSetupAct = QtGui.QAction(_('P&rint Setup...'), self, -+ filePrintSetupAct = QtWidgets.QAction(_('P&rint Setup...'), self, - statusTip=_('Set margins, page size and other printing options')) - filePrintSetupAct.triggered.connect(self.printData.printSetup) - localActions['FilePrintSetup'] = filePrintSetupAct - -- filePrintPreviewAct = QtGui.QAction(_('Print Pre&view...'), self, -+ filePrintPreviewAct = QtWidgets.QAction(_('Print Pre&view...'), self, - statusTip=_('Show a preview of printing results')) - filePrintPreviewAct.triggered.connect(self.printData.printPreview) - localActions['FilePrintPreview'] = filePrintPreviewAct - -- filePrintAct = QtGui.QAction(_('&Print...'), self, -+ filePrintAct = QtWidgets.QAction(_('&Print...'), self, - statusTip=_('Print tree output based on current options')) - filePrintAct.triggered.connect(self.printData.filePrint) - localActions['FilePrint'] = filePrintAct - -- filePrintPdfAct = QtGui.QAction(_('Print &to PDF...'), self, -+ filePrintPdfAct = QtWidgets.QAction(_('Print &to PDF...'), self, - statusTip=_('Export to PDF with current printing options')) - filePrintPdfAct.triggered.connect(self.printData.filePrintPdf) - localActions['FilePrintPdf'] = filePrintPdfAct - -- editUndoAct = QtGui.QAction(_('&Undo'), self, -+ editUndoAct = QtWidgets.QAction(_('&Undo'), self, - statusTip=_('Undo the previous action')) - editUndoAct.triggered.connect(self.editUndo) - localActions['EditUndo'] = editUndoAct - -- editRedoAct = QtGui.QAction(_('&Redo'), self, -+ editRedoAct = QtWidgets.QAction(_('&Redo'), self, - statusTip=_('Redo the previous undo')) - editRedoAct.triggered.connect(self.editRedo) - localActions['EditRedo'] = editRedoAct - -- editCutAct = QtGui.QAction(_('Cu&t'), self, -+ editCutAct = QtWidgets.QAction(_('Cu&t'), self, - statusTip=_('Cut the branch or text to the clipboard')) - editCutAct.triggered.connect(self.editCut) - localActions['EditCut'] = editCutAct - -- editCopyAct = QtGui.QAction(_('&Copy'), self, -+ editCopyAct = QtWidgets.QAction(_('&Copy'), self, - statusTip=_('Copy the branch or text to the clipboard')) - editCopyAct.triggered.connect(self.editCopy) - localActions['EditCopy'] = editCopyAct - -- editPasteAct = QtGui.QAction(_('&Paste'), self, -+ editPasteAct = QtWidgets.QAction(_('&Paste'), self, - statusTip=_('Paste nodes or text from the clipboard')) - editPasteAct.triggered.connect(self.editPaste) - localActions['EditPaste'] = editPasteAct - -- editPastePlainAct = QtGui.QAction(_('P&aste Plain Text'), self, -+ editPastePlainAct = QtWidgets.QAction(_('P&aste Plain Text'), self, - statusTip=_('Paste non-formatted text from the clipboard')) - editPastePlainAct.setEnabled(False) - localActions['EditPastePlain'] = editPastePlainAct - -- editBoldAct = QtGui.QAction(_('&Bold Font'), self, -+ editBoldAct = QtWidgets.QAction(_('&Bold Font'), self, - statusTip=_('Set the current or selected font to bold'), - checkable=True) - editBoldAct.setEnabled(False) - localActions['EditBoldFont'] = editBoldAct - -- editItalicAct = QtGui.QAction(_('&Italic Font'), self, -+ editItalicAct = QtWidgets.QAction(_('&Italic Font'), self, - statusTip=_('Set the current or selected font to italic'), - checkable=True) - editItalicAct.setEnabled(False) - localActions['EditItalicFont'] = editItalicAct - -- editUnderlineAct = QtGui.QAction(_('U&nderline Font'), self, -+ editUnderlineAct = QtWidgets.QAction(_('U&nderline Font'), self, - statusTip=_('Set the current or selected font to underline'), - checkable=True) - editUnderlineAct.setEnabled(False) -@@ -520,90 +520,90 @@ class TreeLocalControl(QtCore.QObject): - key = globalref.keyboardOptions.getValue('EditFontSize') - if not key.isEmpty(): - title = '{0} ({1})'.format(title, key.toString()) -- self.fontSizeSubMenu = QtGui.QMenu(title, -+ self.fontSizeSubMenu = QtWidgets.QMenu(title, - statusTip=_('Set size of the current or selected text')) -- sizeActions = QtGui.QActionGroup(self) -+ sizeActions = QtWidgets.QActionGroup(self) - for size in (_('Small'), _('Default'), _('Large'), _('Larger'), - _('Largest')): -- action = QtGui.QAction(size, sizeActions) -+ action = QtWidgets.QAction(size, sizeActions) - action.setCheckable(True) - self.fontSizeSubMenu.addActions(sizeActions.actions()) - self.fontSizeSubMenu.setEnabled(False) -- fontSizeContextMenuAct = QtGui.QAction(_('Set Font Size'), -+ fontSizeContextMenuAct = QtWidgets.QAction(_('Set Font Size'), - self.fontSizeSubMenu) - localActions['EditFontSize'] = fontSizeContextMenuAct - -- editColorAct = QtGui.QAction(_('Font C&olor...'), self, -+ editColorAct = QtWidgets.QAction(_('Font C&olor...'), self, - statusTip=_('Set the color of the current or selected text')) - editColorAct.setEnabled(False) - localActions['EditFontColor'] = editColorAct - -- editExtLinkAct = QtGui.QAction(_('&External Link...'), self, -+ editExtLinkAct = QtWidgets.QAction(_('&External Link...'), self, - statusTip=_('Add or modify an extrnal web link')) - editExtLinkAct.setEnabled(False) - localActions['EditExtLink'] = editExtLinkAct - -- editIntLinkAct = QtGui.QAction(_('Internal &Link...'), self, -+ editIntLinkAct = QtWidgets.QAction(_('Internal &Link...'), self, - statusTip=_('Add or modify an internal node link')) - editIntLinkAct.setEnabled(False) - localActions['EditIntLink'] = editIntLinkAct - -- editClearFormatAct = QtGui.QAction(_('Clear For&matting'), self, -+ editClearFormatAct = QtWidgets.QAction(_('Clear For&matting'), self, - statusTip=_('Clear current or selected text formatting')) - editClearFormatAct.setEnabled(False) - localActions['EditClearFormat'] = editClearFormatAct - -- nodeRenameAct = QtGui.QAction(_('&Rename'), self, -+ nodeRenameAct = QtWidgets.QAction(_('&Rename'), self, - statusTip=_('Rename the current tree entry title')) - nodeRenameAct.triggered.connect(self.nodeRename) - localActions['NodeRename'] = nodeRenameAct - -- nodeInBeforeAct = QtGui.QAction(_('Insert Sibling &Before'), self, -+ nodeInBeforeAct = QtWidgets.QAction(_('Insert Sibling &Before'), self, - statusTip=_('Insert new sibling before selection')) - nodeInBeforeAct.triggered.connect(self.nodeInBefore) - localActions['NodeInsertBefore'] = nodeInBeforeAct - -- nodeInAfterAct = QtGui.QAction(_('Insert Sibling &After'), self, -+ nodeInAfterAct = QtWidgets.QAction(_('Insert Sibling &After'), self, - statusTip=_('Insert new sibling after selection')) - nodeInAfterAct.triggered.connect(self.nodeInAfter) - localActions['NodeInsertAfter'] = nodeInAfterAct - -- nodeAddChildAct = QtGui.QAction(_('Add &Child'), self, -+ nodeAddChildAct = QtWidgets.QAction(_('Add &Child'), self, - statusTip=_('Add new child to selected parent')) - nodeAddChildAct.triggered.connect(self.nodeAddChild) - localActions['NodeAddChild'] = nodeAddChildAct - -- nodeDeleteAct = QtGui.QAction(_('&Delete Node'), self, -+ nodeDeleteAct = QtWidgets.QAction(_('&Delete Node'), self, - statusTip=_('Delete the selected nodes')) - nodeDeleteAct.triggered.connect(self.nodeDelete) - localActions['NodeDelete'] = nodeDeleteAct - -- nodeIndentAct = QtGui.QAction(_('&Indent Node'), self, -+ nodeIndentAct = QtWidgets.QAction(_('&Indent Node'), self, - statusTip=_('Indent the selected nodes')) - nodeIndentAct.triggered.connect(self.nodeIndent) - localActions['NodeIndent'] = nodeIndentAct - -- nodeUnindentAct = QtGui.QAction(_('&Unindent Node'), self, -+ nodeUnindentAct = QtWidgets.QAction(_('&Unindent Node'), self, - statusTip=_('Unindent the selected nodes')) - nodeUnindentAct.triggered.connect(self.nodeUnindent) - localActions['NodeUnindent'] = nodeUnindentAct - -- nodeMoveUpAct = QtGui.QAction(_('&Move Up'), self, -+ nodeMoveUpAct = QtWidgets.QAction(_('&Move Up'), self, - statusTip=_('Move the selected nodes up')) - nodeMoveUpAct.triggered.connect(self.nodeMoveUp) - localActions['NodeMoveUp'] = nodeMoveUpAct - -- nodeMoveDownAct = QtGui.QAction(_('M&ove Down'), self, -+ nodeMoveDownAct = QtWidgets.QAction(_('M&ove Down'), self, - statusTip=_('Move the selected nodes down')) - nodeMoveDownAct.triggered.connect(self.nodeMoveDown) - localActions['NodeMoveDown'] = nodeMoveDownAct - -- nodeMoveFirstAct = QtGui.QAction(_('Move &First'), self, -+ nodeMoveFirstAct = QtWidgets.QAction(_('Move &First'), self, - statusTip=_('Move the selected nodes to be the first children')) - nodeMoveFirstAct.triggered.connect(self.nodeMoveFirst) - localActions['NodeMoveFirst'] = nodeMoveFirstAct - -- nodeMoveLastAct = QtGui.QAction(_('Move &Last'), self, -+ nodeMoveLastAct = QtWidgets.QAction(_('Move &Last'), self, - statusTip=_('Move the selected nodes to be the last children')) - nodeMoveLastAct.triggered.connect(self.nodeMoveLast) - localActions['NodeMoveLast'] = nodeMoveLastAct -@@ -612,45 +612,45 @@ class TreeLocalControl(QtCore.QObject): - key = globalref.keyboardOptions.getValue('DataNodeType') - if not key.isEmpty(): - title = '{0} ({1})'.format(title, key.toString()) -- self.typeSubMenu = QtGui.QMenu(title, -+ self.typeSubMenu = QtWidgets.QMenu(title, - statusTip=_('Set the node type for selected nodes')) - self.typeSubMenu.aboutToShow.connect(self.loadTypeSubMenu) - self.typeSubMenu.triggered.connect(self.dataSetType) -- typeContextMenuAct = QtGui.QAction(_('Set Node Type'), self.typeSubMenu) -+ typeContextMenuAct = QtWidgets.QAction(_('Set Node Type'), self.typeSubMenu) - typeContextMenuAct.triggered.connect(self.showTypeContextMenu) - localActions['DataNodeType'] = typeContextMenuAct - -- dataCopyTypeAct = QtGui.QAction(_('Copy Types from &File...'), self, -+ dataCopyTypeAct = QtWidgets.QAction(_('Copy Types from &File...'), self, - statusTip=_('Copy the configuration from another TreeLine file')) - dataCopyTypeAct.triggered.connect(self.dataCopyType) - localActions['DataCopyType'] = dataCopyTypeAct - -- dataFlatCatAct = QtGui.QAction(_('Flatten &by Category'), self, -+ dataFlatCatAct = QtWidgets.QAction(_('Flatten &by Category'), self, - statusTip=_('Collapse descendants by merging fields')) - dataFlatCatAct.triggered.connect(self.dataFlatCategory) - localActions['DataFlatCategory'] = dataFlatCatAct - -- dataAddCatAct = QtGui.QAction(_('Add Category &Level...'), self, -+ dataAddCatAct = QtWidgets.QAction(_('Add Category &Level...'), self, - statusTip=_('Insert category nodes above children')) - dataAddCatAct.triggered.connect(self.dataAddCategory) - localActions['DataAddCategory'] = dataAddCatAct - -- dataFlatLinkAct = QtGui.QAction(_('Flatten b&y Link...'), self, -+ dataFlatLinkAct = QtWidgets.QAction(_('Flatten b&y Link...'), self, - statusTip=_('Collapse descendants and insert parent links')) - dataFlatLinkAct.triggered.connect(self.dataFlatLink) - localActions['DataFlatLink'] = dataFlatLinkAct - -- dataArrangeLinkAct = QtGui.QAction(_('&Arrange by Link...'), self, -+ dataArrangeLinkAct = QtWidgets.QAction(_('&Arrange by Link...'), self, - statusTip=_('Arrange descendants using parent links')) - dataArrangeLinkAct.triggered.connect(self.dataArrangeLink) - localActions['DataArrangeLink'] = dataArrangeLinkAct - -- toolsSpellCheckAct = QtGui.QAction(_('&Spell Check...'), self, -+ toolsSpellCheckAct = QtWidgets.QAction(_('&Spell Check...'), self, - statusTip=_('Spell check the tree\')s text data')) - toolsSpellCheckAct.triggered.connect(self.toolsSpellCheck) - localActions['ToolsSpellCheck'] = toolsSpellCheckAct - -- winNewAct = QtGui.QAction(_('&New Window'), self, -+ winNewAct = QtWidgets.QAction(_('&New Window'), self, - statusTip=_('Open a new window for the same file')) - winNewAct.triggered.connect(self.windowNew) - localActions['WinNewWindow'] = winNewAct -@@ -673,7 +673,7 @@ class TreeLocalControl(QtCore.QObject): - if not self.filePath or self.imported: - self.fileSaveAs() - return -- QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) -+ QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) - saveFilePath = self.filePath - if backupFile: - saveFilePath += '~' -@@ -697,12 +697,12 @@ class TreeLocalControl(QtCore.QObject): - password = (globalref.mainControl.passwords. - get(self.filePath, '')) - if not password: -- QtGui.QApplication.restoreOverrideCursor() -+ QtWidgets.QApplication.restoreOverrideCursor() - dialog = miscdialogs.PasswordDialog(True, '', - self.activeWindow) -- if dialog.exec_() != QtGui.QDialog.Accepted: -+ if dialog.exec_() != QtWidgets.QDialog.Accepted: - return -- QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) -+ QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) - password = dialog.password - if miscdialogs.PasswordDialog.remember: - globalref.mainControl.passwords[self. -@@ -712,12 +712,12 @@ class TreeLocalControl(QtCore.QObject): - with open(saveFilePath, 'wb') as f: - f.write(data) - except IOError: -- QtGui.QApplication.restoreOverrideCursor() -- QtGui.QMessageBox.warning(self.activeWindow, 'TreeLine', -+ QtWidgets.QApplication.restoreOverrideCursor() -+ QtWidgets.QMessageBox.warning(self.activeWindow, 'TreeLine', - _('Error - could not write to {}'). - format(saveFilePath)) - else: -- QtGui.QApplication.restoreOverrideCursor() -+ QtWidgets.QApplication.restoreOverrideCursor() - if not backupFile: - self.model.formats.fileInfoFormat.updateFileInfo(self.filePath, - self.model.fileInfoNode) -@@ -749,8 +749,8 @@ class TreeLocalControl(QtCore.QObject): - initFilter = globalref.fileFilters['trl'] - defaultFilePath = globalref.mainControl.defaultFilePath() - defaultFilePath = os.path.splitext(defaultFilePath)[0] + '.trl' -- self.filePath, selectFilter = (QtGui.QFileDialog. -- getSaveFileNameAndFilter(self.activeWindow, -+ self.filePath, selectFilter = (QtWidgets.QFileDialog. -+ getSaveFileName(self.activeWindow, - _('TreeLine - Save As'), - defaultFilePath, - filters, initFilter)) -@@ -783,8 +783,8 @@ class TreeLocalControl(QtCore.QObject): - try: - exportControl.interactiveExport() - except IOError: -- QtGui.QApplication.restoreOverrideCursor() -- QtGui.QMessageBox.warning(self.activeWindow, 'TreeLine', -+ QtWidgets.QApplication.restoreOverrideCursor() -+ QtWidgets.QMessageBox.warning(self.activeWindow, 'TreeLine', - _('Error - could not write to file')) - - def fileProperties(self): -@@ -792,7 +792,7 @@ class TreeLocalControl(QtCore.QObject): - """ - origZeroBlanks = self.model.mathZeroBlanks - dialog = miscdialogs.FilePropertiesDialog(self, self.activeWindow) -- if dialog.exec_() == QtGui.QDialog.Accepted: -+ if dialog.exec_() == QtWidgets.QDialog.Accepted: - self.setModified() - if self.model.mathZeroBlanks != origZeroBlanks: - self.updateAll(False) -@@ -812,7 +812,7 @@ class TreeLocalControl(QtCore.QObject): - def editCut(self): - """Cut the branch or text to the clipboard. - """ -- widget = QtGui.QApplication.focusWidget() -+ widget = QtWidgets.QApplication.focusWidget() - try: - if widget.hasSelectedText(): - widget.cut() -@@ -837,7 +837,7 @@ class TreeLocalControl(QtCore.QObject): - return - except AttributeError: - pass -- widget = QtGui.QApplication.focusWidget() -+ widget = QtWidgets.QApplication.focusWidget() - try: - if widget.hasSelectedText(): - widget.copy() -@@ -851,12 +851,12 @@ class TreeLocalControl(QtCore.QObject): - """ - if self.activeWindow.treeView.hasFocus(): - if (self.currentSelectionModel().selectedNodes(). -- pasteMimeData(QtGui.QApplication.clipboard().mimeData())): -+ pasteMimeData(QtWidgets.QApplication.clipboard().mimeData())): - for node in self.currentSelectionModel().selectedNodes(): - node.expandInView() - self.updateAll() - else: -- widget = QtGui.QApplication.focusWidget() -+ widget = QtWidgets.QApplication.focusWidget() - try: - widget.paste() - except AttributeError: -@@ -1077,22 +1077,22 @@ class TreeLocalControl(QtCore.QObject): - globalref.fileFilters['trlgz'], - globalref.fileFilters['trlenc'], - globalref.fileFilters['all'])) -- fileName = QtGui.QFileDialog.getOpenFileName(self.activeWindow, -+ fileName = QtWidgets.QFileDialog.getOpenFileName(self.activeWindow, - _('TreeLine - Open Configuration File'), - globalref.mainControl.defaultFilePath(True), -- filters) -+ filters)[0] - if not fileName: - return -- QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) -+ QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) - tmpModel = None - try: - opener = treeopener.TreeOpener() - tmpModel = opener.readFile(fileName) -- QtGui.QApplication.restoreOverrideCursor() -+ QtWidgets.QApplication.restoreOverrideCursor() - except IOError: - pass - except treeopener.ParseError: -- QtGui.QApplication.restoreOverrideCursor() -+ QtWidgets.QApplication.restoreOverrideCursor() - compressed = False - encrypted = False - fileObj = open(fileName, 'rb') -@@ -1107,20 +1107,20 @@ class TreeLocalControl(QtCore.QObject): - decompressFile(fileName, fileObj)) - if compressed or encrypted: - try: -- QtGui.QApplication.setOverrideCursor(QtCore.Qt. -+ QtWidgets.QApplication.setOverrideCursor(QtCore.Qt. - WaitCursor) - tmpModel = opener.readFile(fileObj) - except (treeopener.ParseError, zlib.error): - pass - fileObj.close() - if not tmpModel: -- QtGui.QApplication.restoreOverrideCursor() -- QtGui.QMessageBox.warning(self.activeWindow, 'TreeLine', -+ QtWidgets.QApplication.restoreOverrideCursor() -+ QtWidgets.QMessageBox.warning(self.activeWindow, 'TreeLine', - _('Error - could not read file {0}'). - format(fileName)) - return - self.model.formats.copyTypes(tmpModel.formats, self.model) -- QtGui.QApplication.restoreOverrideCursor() -+ QtWidgets.QApplication.restoreOverrideCursor() - pluginInterface = globalref.mainControl.pluginInterface - if pluginInterface: - pluginInterface.execCallback(pluginInterface.formatChangeCallbacks) -@@ -1130,7 +1130,7 @@ class TreeLocalControl(QtCore.QObject): - - Overwrites data in any fields with the same name. - """ -- QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) -+ QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) - selectList = self.currentSelectionModel().uniqueBranches() - undo.BranchFormatUndo(self.model.undoList, selectList, - self.model.formats) -@@ -1141,7 +1141,7 @@ class TreeLocalControl(QtCore.QObject): - dialog = globalref.mainControl.configDialog - if dialog and dialog.isVisible(): - dialog.reset() -- QtGui.QApplication.restoreOverrideCursor() -+ QtWidgets.QApplication.restoreOverrideCursor() - - def dataAddCategory(self): - """Insert category nodes above children. -@@ -1152,15 +1152,15 @@ class TreeLocalControl(QtCore.QObject): - children.extend(node.childList) - fieldList = self.model.formats.commonFields(children) - if not fieldList: -- QtGui.QMessageBox.warning(self.activeWindow, 'TreeLine', -+ QtWidgets.QMessageBox.warning(self.activeWindow, 'TreeLine', - _('Cannot expand without common fields')) - return - dialog = miscdialogs.FieldSelectDialog(_('Category Fields'), - _('Select fields for new level'), - fieldList, self.activeWindow) -- if dialog.exec_() != QtGui.QDialog.Accepted: -+ if dialog.exec_() != QtWidgets.QDialog.Accepted: - return -- QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) -+ QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) - undo.BranchFormatUndo(self.model.undoList, selectList, - self.model.formats) - for node in selectList: -@@ -1169,7 +1169,7 @@ class TreeLocalControl(QtCore.QObject): - dialog = globalref.mainControl.configDialog - if dialog and dialog.isVisible(): - dialog.reset() -- QtGui.QApplication.restoreOverrideCursor() -+ QtWidgets.QApplication.restoreOverrideCursor() - - def dataFlatLink(self): - """Collapse descendants and insert parent links. -@@ -1178,9 +1178,9 @@ class TreeLocalControl(QtCore.QObject): - _('Enter a new field name for ' - 'parent links'), '', '', [], - self.activeWindow) -- if dialog.exec_() != QtGui.QDialog.Accepted: -+ if dialog.exec_() != QtWidgets.QDialog.Accepted: - return -- QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) -+ QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) - newFieldName = dialog.text - selectList = self.currentSelectionModel().uniqueBranches() - undo.BranchFormatUndo(self.model.undoList, selectList, -@@ -1191,7 +1191,7 @@ class TreeLocalControl(QtCore.QObject): - dialog = globalref.mainControl.configDialog - if dialog and dialog.isVisible(): - dialog.reset() -- QtGui.QApplication.restoreOverrideCursor() -+ QtWidgets.QApplication.restoreOverrideCursor() - - def dataArrangeLink(self): - """Arrange nodes using parent links. -@@ -1202,22 +1202,22 @@ class TreeLocalControl(QtCore.QObject): - children.extend(node.childList) - fieldList = self.model.formats.commonFields(children) - if not fieldList: -- QtGui.QMessageBox.warning(self.activeWindow, 'TreeLine', -+ QtWidgets.QMessageBox.warning(self.activeWindow, 'TreeLine', - _('Cannot expand without common fields')) - return -- linkField, ok = QtGui.QInputDialog.getItem(self.activeWindow, -+ linkField, ok = QtWidgets.QInputDialog.getItem(self.activeWindow, - _('Link Field'), - _('Select field with links ' - 'to parents'), fieldList, - 0, False) - if not ok: - return -- QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) -+ QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) - undo.BranchUndo(self.model.undoList, selectList) - for node in selectList: - node.arrangeByLink(linkField) - self.updateAll() -- QtGui.QApplication.restoreOverrideCursor() -+ QtWidgets.QApplication.restoreOverrideCursor() - - def findNodesByWords(self, wordList, titlesOnly=False, forward=True): - """Search for and select nodes that match the word list criteria. -@@ -1379,7 +1379,7 @@ class TreeLocalControl(QtCore.QObject): - fieldName -- if given, only find matches under this type name - replaceText -- if not None, replace a match with this string - """ -- QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) -+ QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) - dataUndo = undo.BranchUndo(self.model.undoList, self.model.root) - totalMatches = 0 - for node in self.model.root.descendantGen(): -@@ -1392,7 +1392,7 @@ class TreeLocalControl(QtCore.QObject): - self.updateAll(True) - else: - self.model.undoList.removeLastUndo(dataUndo) -- QtGui.QApplication.restoreOverrideCursor() -+ QtWidgets.QApplication.restoreOverrideCursor() - return totalMatches - - def findNodesByCondition(self, conditional, forward=True): -Index: TreeLine/source/treemaincontrol.py -=================================================================== ---- TreeLine.orig/source/treemaincontrol.py -+++ TreeLine/source/treemaincontrol.py -@@ -17,7 +17,7 @@ import os.path - import io - import gzip - import zlib --from PyQt4 import QtCore, QtGui, QtNetwork -+from PyQt5 import QtCore, QtGui, QtNetwork, QtWidgets - import globalref - import options - import optiondefaults -@@ -115,7 +115,7 @@ class TreeMainControl(QtCore.QObject): - globalref.toolbarOptions.readFile() - globalref.keyboardOptions.readFile() - except IOError: -- QtGui.QMessageBox.warning(QtGui.QApplication.activeWindow(), -+ QtWidgets.QMessageBox.warning(QtWidgets.QApplication.activeWindow(), - 'TreeLine', - _('Error - could not write config file to {}'). - format(options.Options.basePath)) -@@ -126,7 +126,7 @@ class TreeMainControl(QtCore.QObject): - globalref.toolIcons.loadAllIcons() - windowIcon = globalref.toolIcons.getIcon('treelogo') - if windowIcon: -- QtGui.QApplication.setWindowIcon(windowIcon) -+ QtWidgets.QApplication.setWindowIcon(windowIcon) - globalref.treeIcons = icondict.IconDict(iconPathList, ['', 'tree']) - self.recentFiles = recentfiles.RecentFileList() - if globalref.genOptions.getValue('AutoFileOpen') and not filePaths: -@@ -135,7 +135,7 @@ class TreeMainControl(QtCore.QObject): - filePaths = [recentPath] - self.allActions = {} - self.setupActions() -- QtGui.qApp.focusChanged.connect(self.updateActionsAvail) -+ QtWidgets.QApplication.instance().focusChanged.connect(self.updateActionsAvail) - if filePaths: - for path in filePaths: - self.openFile(path) -@@ -247,24 +247,24 @@ class TreeMainControl(QtCore.QObject): - self.createLocalControl() - return - try: -- QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) -+ QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) - self.createLocalControl(path) - self.recentFiles.addItem(path) - if globalref.genOptions.getValue('SaveTreeStates'): - self.recentFiles.retrieveTreeState(self.activeControl) -- QtGui.QApplication.restoreOverrideCursor() -+ QtWidgets.QApplication.restoreOverrideCursor() - if self.pluginInterface: - self.pluginInterface.execCallback(self.pluginInterface. - fileOpenCallbacks) - except IOError: -- QtGui.QApplication.restoreOverrideCursor() -- QtGui.QMessageBox.warning(QtGui.QApplication.activeWindow(), -+ QtWidgets.QApplication.restoreOverrideCursor() -+ QtWidgets.QMessageBox.warning(QtWidgets.QApplication.activeWindow(), - 'TreeLine', - _('Error - could not read file {0}'). - format(path)) - self.recentFiles.removeItem(path) - except treeopener.ParseError: -- QtGui.QApplication.restoreOverrideCursor() -+ QtWidgets.QApplication.restoreOverrideCursor() - compressed = False - encrypted = False - fileObj = open(path, 'rb') -@@ -274,13 +274,13 @@ class TreeMainControl(QtCore.QObject): - if not fileObj: - if not self.localControls: - self.createLocalControl() -- QtGui.QApplication.restoreOverrideCursor() -+ QtWidgets.QApplication.restoreOverrideCursor() - return - if encrypted and not compressed: - fileObj, compressed = self.decompressFile(path, fileObj) - if compressed or encrypted: - try: -- QtGui.QApplication.setOverrideCursor(QtCore.Qt. -+ QtWidgets.QApplication.setOverrideCursor(QtCore.Qt. - WaitCursor) - self.createLocalControl(fileObj) - self.recentFiles.addItem(path) -@@ -289,14 +289,14 @@ class TreeMainControl(QtCore.QObject): - activeControl) - self.activeControl.compressed = compressed - self.activeControl.encrypted = encrypted -- QtGui.QApplication.restoreOverrideCursor() -+ QtWidgets.QApplication.restoreOverrideCursor() - if self.pluginInterface: - self.pluginInterface.execCallback(self. - pluginInterface. - fileOpenCallbacks) - except (treeopener.ParseError, zlib.error): -- QtGui.QApplication.restoreOverrideCursor() -- QtGui.QMessageBox.warning(QtGui.QApplication. -+ QtWidgets.QApplication.restoreOverrideCursor() -+ QtWidgets.QMessageBox.warning(QtWidgets.QApplication. - activeWindow(), - 'TreeLine', - _('Error - {0} is not a ' -@@ -348,9 +348,9 @@ class TreeMainControl(QtCore.QObject): - if not password: - dialog = miscdialogs.PasswordDialog(False, - os.path.basename(path), -- QtGui.QApplication. -+ QtWidgets.QApplication. - activeWindow()) -- if dialog.exec_() != QtGui.QDialog.Accepted: -+ if dialog.exec_() != QtWidgets.QDialog.Accepted: - fileObj.close() - return (None, True) - password = dialog.password -@@ -380,17 +380,17 @@ class TreeMainControl(QtCore.QObject): - filePath = filePath + '~' - if not os.access(filePath, os.R_OK): - return True -- msgBox = QtGui.QMessageBox(QtGui.QMessageBox.Information, 'TreeLine', -+ msgBox = QtWidgets.QMessageBox(QtWidgets.QMessageBox.Information, 'TreeLine', - _('Backup file "{}" exists.\nA previous ' - 'session may have crashed').format(filePath), -- QtGui.QMessageBox.NoButton, -- QtGui.QApplication.activeWindow()) -+ QtWidgets.QMessageBox.NoButton, -+ QtWidgets.QApplication.activeWindow()) - restoreButton = msgBox.addButton(_('&Restore Backup'), -- QtGui.QMessageBox.ApplyRole) -+ QtWidgets.QMessageBox.ApplyRole) - deleteButton = msgBox.addButton(_('&Delete Backup'), -- QtGui.QMessageBox.DestructiveRole) -+ QtWidgets.QMessageBox.DestructiveRole) - cancelButton = msgBox.addButton(_('&Cancel File Open'), -- QtGui.QMessageBox.RejectRole) -+ QtWidgets.QMessageBox.RejectRole) - msgBox.exec_() - if msgBox.clickedButton() == restoreButton: - self.openFile(filePath) -@@ -400,7 +400,7 @@ class TreeMainControl(QtCore.QObject): - os.remove(basePath) - os.rename(filePath, basePath) - except OSError: -- QtGui.QMessageBox.warning(QtGui.QApplication.activeWindow(), -+ QtWidgets.QMessageBox.warning(QtWidgets.QApplication.activeWindow(), - 'TreeLine', - _('Error - could not rename "{0}" to "{1}"'). - format(filePath, basePath)) -@@ -411,7 +411,7 @@ class TreeMainControl(QtCore.QObject): - try: - os.remove(filePath) - except OSError: -- QtGui.QMessageBox.warning(QtGui.QApplication.activeWindow(), -+ QtWidgets.QMessageBox.warning(QtWidgets.QApplication.activeWindow(), - 'TreeLine', - _('Error - could not remove backup file {}'). - format(filePath)) -@@ -541,7 +541,7 @@ class TreeMainControl(QtCore.QObject): - if not self.pluginInstances: - self.pluginInterface = None - if errorList: -- QtGui.QMessageBox.warning(QtGui.QApplication.activeWindow(), -+ QtWidgets.QMessageBox.warning(QtWidgets.QApplication.activeWindow(), - 'TreeLine', - _('Warning - could not load plugin module {0}'). - format(', '.join(errorList))) -@@ -550,82 +550,82 @@ class TreeMainControl(QtCore.QObject): - def setupActions(self): - """Add the actions for contols at the global level. - """ -- fileNewAct = QtGui.QAction(_('&New...'), self, toolTip=_('New File'), -+ fileNewAct = QtWidgets.QAction(_('&New...'), self, toolTip=_('New File'), - statusTip=_('Start a new file')) - fileNewAct.triggered.connect(self.fileNew) - self.allActions['FileNew'] = fileNewAct - -- fileOpenAct = QtGui.QAction(_('&Open...'), self, -+ fileOpenAct = QtWidgets.QAction(_('&Open...'), self, - toolTip=_('Open File'), - statusTip=_('Open a file from disk')) - fileOpenAct.triggered.connect(self.fileOpen) - self.allActions['FileOpen'] = fileOpenAct - -- fileSampleAct = QtGui.QAction(_('Open Sa&mple...'), self, -+ fileSampleAct = QtWidgets.QAction(_('Open Sa&mple...'), self, - toolTip=_('Open Sample'), - statusTip=_('Open a sample file')) - fileSampleAct.triggered.connect(self.fileOpenSample) - self.allActions['FileOpenSample'] = fileSampleAct - -- fileImportAct = QtGui.QAction(_('&Import...'), self, -+ fileImportAct = QtWidgets.QAction(_('&Import...'), self, - statusTip=_('Open a non-TreeLine file')) - fileImportAct.triggered.connect(self.fileImport) - self.allActions['FileImport'] = fileImportAct - -- fileQuitAct = QtGui.QAction(_('&Quit'), self, -+ fileQuitAct = QtWidgets.QAction(_('&Quit'), self, - statusTip=_('Exit the application')) - fileQuitAct.triggered.connect(self.fileQuit) - self.allActions['FileQuit'] = fileQuitAct - -- editSelectAllAct = QtGui.QAction(_('&Select All'), self, -+ editSelectAllAct = QtWidgets.QAction(_('&Select All'), self, - statusTip=_('Select all text in an editor')) - editSelectAllAct.setEnabled(False) - editSelectAllAct.triggered.connect(self.editSelectAll) - self.allActions['EditSelectAll'] = editSelectAllAct - -- dataConfigAct = QtGui.QAction(_('&Configure Data Types...'), self, -+ dataConfigAct = QtWidgets.QAction(_('&Configure Data Types...'), self, - statusTip=_('Modify data types, fields & output lines'), - checkable=True) - dataConfigAct.triggered.connect(self.dataConfigDlg) - self.allActions['DataConfigType'] = dataConfigAct - -- dataSortAct = QtGui.QAction(_('Sor&t Nodes...'), self, -+ dataSortAct = QtWidgets.QAction(_('Sor&t Nodes...'), self, - statusTip=_('Define node sort operations'), - checkable=True) - dataSortAct.triggered.connect(self.dataSortDialog) - self.allActions['DataSortNodes'] = dataSortAct - -- dataNumberingAct = QtGui.QAction(_('Update &Numbering...'), self, -+ dataNumberingAct = QtWidgets.QAction(_('Update &Numbering...'), self, - statusTip=_('Update node numbering fields'), - checkable=True) - dataNumberingAct.triggered.connect(self.dataNumberingDialog) - self.allActions['DataNumbering'] = dataNumberingAct - -- toolsFindTextAct = QtGui.QAction(_('&Find Text...'), self, -+ toolsFindTextAct = QtWidgets.QAction(_('&Find Text...'), self, - statusTip=_('Find text in node titles & data'), - checkable=True) - toolsFindTextAct.triggered.connect(self.toolsFindTextDialog) - self.allActions['ToolsFindText'] = toolsFindTextAct - -- toolsFindConditionAct = QtGui.QAction(_('&Conditional Find...'), self, -+ toolsFindConditionAct = QtWidgets.QAction(_('&Conditional Find...'), self, - statusTip=_('Use field conditions to find nodes'), - checkable=True) - toolsFindConditionAct.triggered.connect(self.toolsFindConditionDialog) - self.allActions['ToolsFindCondition'] = toolsFindConditionAct - -- toolsFindReplaceAct = QtGui.QAction(_('Find and &Replace...'), self, -+ toolsFindReplaceAct = QtWidgets.QAction(_('Find and &Replace...'), self, - statusTip=_('Replace text strings in node data'), - checkable=True) - toolsFindReplaceAct.triggered.connect(self.toolsFindReplaceDialog) - self.allActions['ToolsFindReplace'] = toolsFindReplaceAct - -- toolsFilterTextAct = QtGui.QAction(_('&Text Filter...'), self, -+ toolsFilterTextAct = QtWidgets.QAction(_('&Text Filter...'), self, - statusTip=_('Filter nodes to only show text matches'), - checkable=True) - toolsFilterTextAct.triggered.connect(self.toolsFilterTextDialog) - self.allActions['ToolsFilterText'] = toolsFilterTextAct - -- toolsFilterConditionAct = QtGui.QAction(_('C&onditional Filter...'), -+ toolsFilterConditionAct = QtWidgets.QAction(_('C&onditional Filter...'), - self, - statusTip=_('Use field conditions to filter nodes'), - checkable=True) -@@ -633,42 +633,42 @@ class TreeMainControl(QtCore.QObject): - toolsFilterConditionDialog) - self.allActions['ToolsFilterCondition'] = toolsFilterConditionAct - -- toolsGenOptionsAct = QtGui.QAction(_('&General Options...'), self, -+ toolsGenOptionsAct = QtWidgets.QAction(_('&General Options...'), self, - statusTip=_('Set user preferences for all files')) - toolsGenOptionsAct.triggered.connect(self.toolsGenOptions) - self.allActions['ToolsGenOptions'] = toolsGenOptionsAct - -- toolsShortcutAct = QtGui.QAction(_('Set &Keyboard Shortcuts...'), self, -+ toolsShortcutAct = QtWidgets.QAction(_('Set &Keyboard Shortcuts...'), self, - statusTip=_('Customize keyboard commands')) - toolsShortcutAct.triggered.connect(self.toolsCustomShortcuts) - self.allActions['ToolsShortcuts'] = toolsShortcutAct - -- toolsToolbarAct = QtGui.QAction(_('C&ustomize Toolbars...'), self, -+ toolsToolbarAct = QtWidgets.QAction(_('C&ustomize Toolbars...'), self, - statusTip=_('Customize toolbar buttons')) - toolsToolbarAct.triggered.connect(self.toolsCustomToolbars) - self.allActions['ToolsToolbars'] = toolsToolbarAct - -- toolsFontsAct = QtGui.QAction(_('Customize Fo&nts...'), self, -+ toolsFontsAct = QtWidgets.QAction(_('Customize Fo&nts...'), self, - statusTip=_('Customize fonts in various views')) - toolsFontsAct.triggered.connect(self.toolsCustomFonts) - self.allActions['ToolsFonts'] = toolsFontsAct - -- helpBasicAct = QtGui.QAction(_('&Basic Usage...'), self, -+ helpBasicAct = QtWidgets.QAction(_('&Basic Usage...'), self, - statusTip=_('Display basic usage instructions')) - helpBasicAct.triggered.connect(self.helpViewBasic) - self.allActions['HelpBasic'] = helpBasicAct - -- helpFullAct = QtGui.QAction(_('&Full Documentation...'), self, -+ helpFullAct = QtWidgets.QAction(_('&Full Documentation...'), self, - statusTip=_('Open a TreeLine file with full documentation')) - helpFullAct.triggered.connect(self.helpViewFull) - self.allActions['HelpFull'] = helpFullAct - -- helpAboutAct = QtGui.QAction(_('&About TreeLine...'), self, -+ helpAboutAct = QtWidgets.QAction(_('&About TreeLine...'), self, - statusTip=_('Display version info about this program')) - helpAboutAct.triggered.connect(self.helpAbout) - self.allActions['HelpAbout'] = helpAboutAct - -- helpPluginAct = QtGui.QAction(_('&About &Plugins...'), self, -+ helpPluginAct = QtWidgets.QAction(_('&About &Plugins...'), self, - statusTip=_('Show loaded plugin modules')) - helpPluginAct.triggered.connect(self.helpPlugin) - self.allActions['HelpPlugin'] = helpPluginAct -@@ -691,7 +691,7 @@ class TreeMainControl(QtCore.QObject): - dialog = miscdialogs.TemplateFileDialog(_('New File'), - _('&Select Template'), - searchPaths) -- if dialog.exec_() == QtGui.QDialog.Accepted: -+ if dialog.exec_() == QtWidgets.QDialog.Accepted: - self.createLocalControl(dialog.selectedPath()) - self.activeControl.filePath = '' - self.activeControl.updateWindowCaptions() -@@ -707,11 +707,11 @@ class TreeMainControl(QtCore.QObject): - globalref.fileFilters['trlgz'], - globalref.fileFilters['trlenc'], - globalref.fileFilters['all'])) -- fileName = QtGui.QFileDialog.getOpenFileName(QtGui.QApplication. -+ fileName = QtWidgets.QFileDialog.getOpenFileName(QtWidgets.QApplication. - activeWindow(), - _('TreeLine - Open File'), - self.defaultFilePath(True), -- filters) -+ filters)[0] - if fileName: - self.openFile(fileName) - -@@ -724,7 +724,7 @@ class TreeMainControl(QtCore.QObject): - dialog = miscdialogs.TemplateFileDialog(_('Open Sample File'), - _('&Select Sample'), - searchPaths, False) -- if dialog.exec_() == QtGui.QDialog.Accepted: -+ if dialog.exec_() == QtWidgets.QDialog.Accepted: - self.createLocalControl(dialog.selectedPath()) - name = dialog.selectedName() + '.trl' - self.activeControl.filePath = name -@@ -750,7 +750,7 @@ class TreeMainControl(QtCore.QObject): - """Select all text in any currently focused editor. - """ - try: -- QtGui.QApplication.focusWidget().selectAll() -+ QtWidgets.QApplication.focusWidget().selectAll() - except AttributeError: - pass - -@@ -911,9 +911,9 @@ class TreeMainControl(QtCore.QObject): - """ - oldAutoSaveMinutes = globalref.genOptions.getValue('AutoSaveMinutes') - dialog = options.OptionDialog(globalref.genOptions, -- QtGui.QApplication.activeWindow()) -+ QtWidgets.QApplication.activeWindow()) - dialog.setWindowTitle(_('General Options')) -- if (dialog.exec_() == QtGui.QDialog.Accepted and -+ if (dialog.exec_() == QtWidgets.QDialog.Accepted and - globalref.genOptions.modified): - globalref.genOptions.writeFile() - self.recentFiles.updateNumEntries() -@@ -929,7 +929,7 @@ class TreeMainControl(QtCore.QObject): - """Show dialog to customize keyboard commands. - """ - actions = self.activeControl.activeWindow.allActions -- dialog = miscdialogs.CustomShortcutsDialog(actions, QtGui.QApplication. -+ dialog = miscdialogs.CustomShortcutsDialog(actions, QtWidgets.QApplication. - activeWindow()) - dialog.exec_() - -@@ -938,7 +938,7 @@ class TreeMainControl(QtCore.QObject): - """ - actions = self.activeControl.activeWindow.allActions - dialog = miscdialogs.CustomToolbarDialog(actions, self.updateToolbars, -- QtGui.QApplication. -+ QtWidgets.QApplication. - activeWindow()) - dialog.exec_() - -@@ -952,7 +952,7 @@ class TreeMainControl(QtCore.QObject): - def toolsCustomFonts(self): - """Show dialog to customize fonts in various views. - """ -- dialog = miscdialogs.CustomFontDialog(QtGui.QApplication. -+ dialog = miscdialogs.CustomFontDialog(QtWidgets.QApplication. - activeWindow()) - dialog.updateRequired.connect(self.updateCustomFonts) - dialog.exec_() -@@ -972,7 +972,7 @@ class TreeMainControl(QtCore.QObject): - if not self.basicHelpView: - path = self.findResourceFile('basichelp.html', 'doc', docPath) - if not path: -- QtGui.QMessageBox.warning(QtGui.QApplication.activeWindow(), -+ QtWidgets.QMessageBox.warning(QtWidgets.QApplication.activeWindow(), - 'TreeLine', - _('Error - basic help file not found')) - return -@@ -986,7 +986,7 @@ class TreeMainControl(QtCore.QObject): - """ - path = self.findResourceFile('documentation.trl', 'doc', docPath) - if not path: -- QtGui.QMessageBox.warning(QtGui.QApplication.activeWindow(), -+ QtWidgets.QMessageBox.warning(QtWidgets.QApplication.activeWindow(), - 'TreeLine', - _('Error - documentation file not found')) - return -@@ -1005,7 +1005,7 @@ class TreeMainControl(QtCore.QObject): - def helpAbout(self): - """ Display version info about this program. - """ -- QtGui.QMessageBox.about(QtGui.QApplication.activeWindow(), 'TreeLine', -+ QtWidgets.QMessageBox.about(QtWidgets.QApplication.activeWindow(), 'TreeLine', - _('TreeLine, Version {0}\nby {1}'). - format(__version__, __author__)) - -@@ -1013,6 +1013,6 @@ class TreeMainControl(QtCore.QObject): - """Show dialog with loaded plugin modules. - """ - dialog = miscdialogs.PluginListDialog(self.pluginDescriptions, -- QtGui.QApplication. -+ QtWidgets.QApplication. - activeWindow()) - dialog.exec_() -Index: TreeLine/source/treemodel.py -=================================================================== ---- TreeLine.orig/source/treemodel.py -+++ TreeLine/source/treemodel.py -@@ -15,7 +15,7 @@ - import copy - import io - from xml.etree import ElementTree --from PyQt4 import QtCore, QtGui -+from PyQt5 import QtCore - import treeformats - import nodeformat - import treenode -Index: TreeLine/source/treenodelist.py -=================================================================== ---- TreeLine.orig/source/treenodelist.py -+++ TreeLine/source/treenodelist.py -@@ -12,7 +12,7 @@ - # but WITTHOUT ANY WARRANTY. See the included LICENSE file for details. - #****************************************************************************** - --from PyQt4 import QtCore, QtGui -+from PyQt5 import QtCore, QtGui, QtWidgets - import undo - - -@@ -36,7 +36,7 @@ class TreeNodeList(list): - """ - if not self: - return -- clip = QtGui.QApplication.clipboard() -+ clip = QtWidgets.QApplication.clipboard() - if clip.supportsSelection(): - titleList = [] - for node in self: -Index: TreeLine/source/treeoutput.py -=================================================================== ---- TreeLine.orig/source/treeoutput.py -+++ TreeLine/source/treeoutput.py -@@ -14,7 +14,7 @@ - - import itertools - import copy --from PyQt4 import QtCore, QtGui -+from PyQt5 import QtCore, QtGui - import globalref - - -Index: TreeLine/source/treeselection.py -=================================================================== ---- TreeLine.orig/source/treeselection.py -+++ TreeLine/source/treeselection.py -@@ -13,13 +13,13 @@ - #****************************************************************************** - - import operator --from PyQt4 import QtCore, QtGui -+from PyQt5 import QtCore - import treenodelist - - - _maxHistoryLength = 10 - --class TreeSelection(QtGui.QItemSelectionModel): -+class TreeSelection(QtCore.QItemSelectionModel): - """Class override for the tree view's selection model. - - Provides methods for easier access to selected nodes. -@@ -87,7 +87,7 @@ class TreeSelection(QtGui.QItemSelection - self.blockSignals(True) - self.addToHistory([node]) - self.clear() -- self.setCurrentIndex(node.index(), QtGui.QItemSelectionModel.Select) -+ self.setCurrentIndex(node.index(), QtCore.QItemSelectionModel.Select) - self.blockSignals(False) - self.tempExpandedNodes = expandedNodes - -@@ -116,10 +116,10 @@ class TreeSelection(QtGui.QItemSelection - self.addToHistory(nodeList) - self.clear() - for node in nodeList: -- self.select(node.index(), QtGui.QItemSelectionModel.Select) -+ self.select(node.index(), QtCore.QItemSelectionModel.Select) - if nodeList: - self.setCurrentIndex(nodeList[0].index(), -- QtGui.QItemSelectionModel.Current) -+ QtCore.QItemSelectionModel.Current) - self.blockSignals(False) - self.tempExpandedNodes = expandedNodes - -Index: TreeLine/source/treeview.py -=================================================================== ---- TreeLine.orig/source/treeview.py -+++ TreeLine/source/treeview.py -@@ -12,14 +12,14 @@ - # but WITTHOUT ANY WARRANTY. See the included LICENSE file for details. - #****************************************************************************** - --from PyQt4 import QtCore, QtGui -+from PyQt5 import QtCore, QtGui, QtWidgets - import treenode - import treeselection - import miscdialogs - import globalref - - --class TreeView(QtGui.QTreeView): -+class TreeView(QtWidgets.QTreeView): - """Class override for the indented tree view. - - Sets view defaults and links with document for content. -@@ -40,13 +40,13 @@ class TreeView(QtGui.QTreeView): - self.menu = None - self.noMouseSelectMode = False - self.setSelectionModel(treeselection.TreeSelection(model, self)) -- self.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) -- self.header().setResizeMode(0, QtGui.QHeaderView.ResizeToContents) -+ self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) -+ self.header().setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeToContents) - self.header().setStretchLastSection(False) - self.setHeaderHidden(True) - self.setItemDelegate(TreeEditDelegate(self)) - self.updateTreeGenOptions() -- self.setDragDropMode(QtGui.QAbstractItemView.DragDrop) -+ self.setDragDropMode(QtWidgets.QAbstractItemView.DragDrop) - self.setDefaultDropAction(QtCore.Qt.MoveAction) - self.setDropIndicatorShown(True) - self.setUniformRowHeights(True) -@@ -57,9 +57,9 @@ class TreeView(QtGui.QTreeView): - """Set the tree to match the current general options. - """ - if globalref.genOptions.getValue('ClickRename'): -- self.setEditTriggers(QtGui.QAbstractItemView.SelectedClicked) -+ self.setEditTriggers(QtWidgets.QAbstractItemView.SelectedClicked) - else: -- self.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers) -+ self.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) - dragAvail = globalref.genOptions.getValue('DragTree') - self.setDragEnabled(dragAvail) - self.setAcceptDrops(dragAvail) -@@ -124,7 +124,7 @@ class TreeView(QtGui.QTreeView): - """Return the context menu, creating it if necessary. - """ - if not self.menu: -- self.menu = QtGui.QMenu(self) -+ self.menu = QtWidgets.QMenu(self) - self.menu.addAction(self.allActions['EditCut']) - self.menu.addAction(self.allActions['EditCopy']) - self.menu.addAction(self.allActions['EditPaste']) -@@ -180,7 +180,7 @@ class TreeView(QtGui.QTreeView): - self.contextMenu().popup(pos) - event.accept() - -- def scrollTo(self, index, hint=QtGui.QAbstractItemView.EnsureVisible): -+ def scrollTo(self, index, hint=QtWidgets.QAbstractItemView.EnsureVisible): - """Scroll the view to make node at index visible. - - Overriden to stop autoScroll from horizontally jumping when selecting -@@ -244,7 +244,7 @@ class TreeView(QtGui.QTreeView): - super().mousePressEvent(event) - - --class TreeEditDelegate(QtGui.QStyledItemDelegate): -+class TreeEditDelegate(QtWidgets.QStyledItemDelegate): - """Class override for editing tree items to capture shortcut keys. - """ - def __init__(self, parent=None): -@@ -282,7 +282,7 @@ class TreeEditDelegate(QtGui.QStyledItem - return super().eventFilter(editor, event) - - --class TreeFilterViewItem(QtGui.QListWidgetItem): -+class TreeFilterViewItem(QtWidgets.QListWidgetItem): - """Item container for the flat list of filtered nodes. - """ - def __init__(self, node, viewParent=None): -@@ -307,7 +307,7 @@ class TreeFilterViewItem(QtGui.QListWidg - iconName, True)) - - --class TreeFilterView(QtGui.QListWidget): -+class TreeFilterView(QtWidgets.QListWidget): - """View to show flat list of filtered nodes. - """ - skippedMouseSelect = QtCore.pyqtSignal(treenode.TreeNode) -@@ -332,12 +332,12 @@ class TreeFilterView(QtGui.QListWidget): - self.filterWhat = miscdialogs.FindFilterDialog.fullData - self.filterHow = miscdialogs.FindFilterDialog.keyWords - self.filterStr = '' -- self.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) -+ self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) - self.setItemDelegate(TreeEditDelegate(self)) - if globalref.genOptions.getValue('ClickRename'): -- self.setEditTriggers(QtGui.QAbstractItemView.SelectedClicked) -+ self.setEditTriggers(QtWidgets.QAbstractItemView.SelectedClicked) - else: -- self.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers) -+ self.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) - self.itemSelectionChanged.connect(self.updateSelectionModel) - self.itemChanged.connect(self.changeTitle) - -@@ -360,7 +360,7 @@ class TreeFilterView(QtGui.QListWidget): - if self.conditionalFilter: - self.conditionalUpdate() - return -- QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) -+ QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) - if self.filterHow == miscdialogs.FindFilterDialog.regExp: - criteria = [re.compile(self.filterStr)] - useRegExpFilter = True -@@ -395,12 +395,12 @@ class TreeFilterView(QtGui.QListWidget): - filterStr, - self.count()) - globalref.mainControl.currentStatusBar().showMessage(message) -- QtGui.QApplication.restoreOverrideCursor() -+ QtWidgets.QApplication.restoreOverrideCursor() - - def conditionalUpdate(self): - """Update filtered contents from model and conditional criteria. - """ -- QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) -+ QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) - self.blockSignals(True) - self.clear() - for node in self.model.root.descendantGen(): -@@ -413,7 +413,7 @@ class TreeFilterView(QtGui.QListWidget): - message = _('Conditional filtering, found {0} nodes').format(self. - count()) - globalref.mainControl.currentStatusBar().showMessage(message) -- QtGui.QApplication.restoreOverrideCursor() -+ QtWidgets.QApplication.restoreOverrideCursor() - - def selectItems(self, nodes, signalModel=False): - """Select items matching given nodes if in filtered view. -@@ -487,7 +487,7 @@ class TreeFilterView(QtGui.QListWidget): - """Return the context menu, creating it if necessary. - """ - if not self.menu: -- self.menu = QtGui.QMenu(self) -+ self.menu = QtWidgets.QMenu(self) - self.menu.addAction(self.allActions['EditCut']) - self.menu.addAction(self.allActions['EditCopy']) - self.menu.addAction(self.allActions['NodeRename']) -Index: TreeLine/source/treewindow.py -=================================================================== ---- TreeLine.orig/source/treewindow.py -+++ TreeLine/source/treewindow.py -@@ -14,7 +14,7 @@ - - import os.path - import base64 --from PyQt4 import QtCore, QtGui -+from PyQt5 import QtCore, QtGui, QtWidgets - import treeview - import outputview - import titlelistview -@@ -23,7 +23,7 @@ import treenode - import globalref - - --class TreeWindow(QtGui.QMainWindow): -+class TreeWindow(QtWidgets.QMainWindow): - """Class override for the main window. - - Contains main window views and controls. -@@ -31,8 +31,8 @@ class TreeWindow(QtGui.QMainWindow): - selectChanged = QtCore.pyqtSignal() - nodeModified = QtCore.pyqtSignal(treenode.TreeNode) - treeModified = QtCore.pyqtSignal() -- winActivated = QtCore.pyqtSignal(QtGui.QMainWindow) -- winClosing = QtCore.pyqtSignal(QtGui.QMainWindow) -+ winActivated = QtCore.pyqtSignal(QtWidgets.QMainWindow) -+ winClosing = QtCore.pyqtSignal(QtWidgets.QMainWindow) - def __init__(self, model, allActions, parent=None): - """Initialize the main window. - -@@ -48,16 +48,16 @@ class TreeWindow(QtGui.QMainWindow): - self.rightTabActList = [] - self.setAttribute(QtCore.Qt.WA_DeleteOnClose) - self.setAcceptDrops(True) -- self.setStatusBar(QtGui.QStatusBar()) -+ self.setStatusBar(QtWidgets.QStatusBar()) - self.setCaption() - self.setupActions() - self.setupMenus() - self.setupToolbars() - self.restoreToolbarPosition() - -- self.mainSplitter = QtGui.QSplitter() -+ self.mainSplitter = QtWidgets.QSplitter() - self.setCentralWidget(self.mainSplitter) -- self.treeStack = QtGui.QStackedWidget() -+ self.treeStack = QtWidgets.QStackedWidget() - self.mainSplitter.addWidget(self.treeStack) - self.treeView = treeview.TreeView(model, self.allActions) - self.treeView.shortcutEntered.connect(self.execShortcut) -@@ -76,12 +76,12 @@ class TreeWindow(QtGui.QMainWindow): - updateFromSelectionModel) - self.treeStack.setCurrentWidget(self.treeView) - -- self.rightTabs = QtGui.QTabWidget() -+ self.rightTabs = QtWidgets.QTabWidget() - self.mainSplitter.addWidget(self.rightTabs) -- self.rightTabs.setTabPosition(QtGui.QTabWidget.South) -+ self.rightTabs.setTabPosition(QtWidgets.QTabWidget.South) - self.rightTabs.tabBar().setFocusPolicy(QtCore.Qt.NoFocus) - -- self.outputSplitter = QtGui.QSplitter(QtCore.Qt.Vertical) -+ self.outputSplitter = QtWidgets.QSplitter(QtCore.Qt.Vertical) - self.rightTabs.addTab(self.outputSplitter, _('Data Output')) - parentOutputView = outputview.OutputView(self.treeView. - selectionModel(), False) -@@ -92,7 +92,7 @@ class TreeWindow(QtGui.QMainWindow): - childOutputView.highlighted[str].connect(self.statusBar().showMessage) - self.outputSplitter.addWidget(childOutputView) - -- self.editorSplitter = QtGui.QSplitter(QtCore.Qt.Vertical) -+ self.editorSplitter = QtWidgets.QSplitter(QtCore.Qt.Vertical) - self.rightTabs.addTab(self.editorSplitter, _('Data Edit')) - parentEditView = dataeditview.DataEditView(self.treeView. - selectionModel(), -@@ -125,7 +125,7 @@ class TreeWindow(QtGui.QMainWindow): - internalLinkSelected) - self.editorSplitter.addWidget(childEditView) - -- self.titleSplitter = QtGui.QSplitter(QtCore.Qt.Vertical) -+ self.titleSplitter = QtWidgets.QSplitter(QtCore.Qt.Vertical) - self.rightTabs.addTab(self.titleSplitter, _('Title List')) - parentTitleView = titlelistview.TitleListView(self.treeView. - selectionModel(), False) -@@ -302,45 +302,45 @@ class TreeWindow(QtGui.QMainWindow): - """ - winActions = {} - -- viewExpandBranchAct = QtGui.QAction(_('&Expand Full Branch'), self, -+ viewExpandBranchAct = QtWidgets.QAction(_('&Expand Full Branch'), self, - statusTip=_('Expand all children of the selected nodes')) - viewExpandBranchAct.triggered.connect(self.viewExpandBranch) - winActions['ViewExpandBranch'] = viewExpandBranchAct - -- viewCollapseBranchAct = QtGui.QAction(_('&Collapse Full Branch'), self, -+ viewCollapseBranchAct = QtWidgets.QAction(_('&Collapse Full Branch'), self, - statusTip=_('Collapse all children of the selected nodes')) - viewCollapseBranchAct.triggered.connect(self.viewCollapseBranch) - winActions['ViewCollapseBranch'] = viewCollapseBranchAct - -- viewPrevSelectAct = QtGui.QAction(_('&Previous Selection'), self, -+ viewPrevSelectAct = QtWidgets.QAction(_('&Previous Selection'), self, - statusTip=_('Return to the previous tree selection')) - viewPrevSelectAct.triggered.connect(self.viewPrevSelect) - winActions['ViewPrevSelect'] = viewPrevSelectAct - -- viewNextSelectAct = QtGui.QAction(_('&Next Selection'), self, -+ viewNextSelectAct = QtWidgets.QAction(_('&Next Selection'), self, - statusTip=_('Go to the next tree selection in history')) - viewNextSelectAct.triggered.connect(self.viewNextSelect) - winActions['ViewNextSelect'] = viewNextSelectAct - -- viewRightTabGrp = QtGui.QActionGroup(self) -- viewOutputAct = QtGui.QAction(_('Show Data &Output'), viewRightTabGrp, -+ viewRightTabGrp = QtWidgets.QActionGroup(self) -+ viewOutputAct = QtWidgets.QAction(_('Show Data &Output'), viewRightTabGrp, - statusTip=_('Show data output in right view'), - checkable=True) - winActions['ViewDataOutput'] = viewOutputAct - -- viewEditAct = QtGui.QAction(_('Show Data &Editor'), viewRightTabGrp, -+ viewEditAct = QtWidgets.QAction(_('Show Data &Editor'), viewRightTabGrp, - statusTip=_('Show data editor in right view'), - checkable=True) - winActions['ViewDataEditor'] = viewEditAct - -- viewTitleAct = QtGui.QAction(_('Show &Title List'), viewRightTabGrp, -+ viewTitleAct = QtWidgets.QAction(_('Show &Title List'), viewRightTabGrp, - statusTip=_('Show title list in right view'), - checkable=True) - winActions['ViewTitleList'] = viewTitleAct - self.rightTabActList = [viewOutputAct, viewEditAct, viewTitleAct] - viewRightTabGrp.triggered.connect(self.viewRightTab) - -- viewChildPaneAct = QtGui.QAction(_('&Show Child Pane'), self, -+ viewChildPaneAct = QtWidgets.QAction(_('&Show Child Pane'), self, - statusTip=_('Toggle showing right-hand child views'), - checkable=True) - viewChildPaneAct.setChecked(globalref.genOptions. -@@ -348,7 +348,7 @@ class TreeWindow(QtGui.QMainWindow): - viewChildPaneAct.triggered.connect(self.viewShowChildPane) - winActions['ViewShowChildPane'] = viewChildPaneAct - -- viewDescendAct = QtGui.QAction(_('Show Output &Descedants'), self, -+ viewDescendAct = QtWidgets.QAction(_('Show Output &Descedants'), self, - statusTip=_('Toggle showing output view indented descendants'), - checkable=True) - viewDescendAct.setChecked(globalref.genOptions. -@@ -356,7 +356,7 @@ class TreeWindow(QtGui.QMainWindow): - viewDescendAct.triggered.connect(self.viewDescendants) - winActions['ViewShowDescend'] = viewDescendAct - -- winCloseAct = QtGui.QAction(_('&Close Window'), self, -+ winCloseAct = QtWidgets.QAction(_('&Close Window'), self, - statusTip=_('Close this window')) - winCloseAct.triggered.connect(self.close) - winActions['WinCloseWindow'] = winCloseAct -@@ -541,9 +541,9 @@ class TreeWindow(QtGui.QMainWindow): - def viewExpandBranch(self): - """Expand all children of the selected nodes. - """ -- QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) -+ QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) - self.treeView.expandSelectedBranches() -- QtGui.QApplication.restoreOverrideCursor() -+ QtWidgets.QApplication.restoreOverrideCursor() - - def viewCollapseBranch(self): - """Collapse all children of the selected nodes. -@@ -631,7 +631,7 @@ class TreeWindow(QtGui.QMainWindow): - else: - if offset: - rect.adjust(offset, offset, offset, offset) -- desktop = QtGui.QApplication.desktop() -+ desktop = QtWidgets.QApplication.desktop() - if desktop.isVirtualDesktop(): - availRect = desktop.screen().rect() - else: -@@ -704,7 +704,7 @@ class TreeWindow(QtGui.QMainWindow): - """ - super().changeEvent(event) - if (event.type() == QtCore.QEvent.ActivationChange and -- QtGui.QApplication.activeWindow() == self): -+ QtWidgets.QApplication.activeWindow() == self): - self.winActivated.emit(self) - - def closeEvent(self, event): diff -Nru treeline-2.0.2/debian/patches/series treeline-3.0.1/debian/patches/series --- treeline-2.0.2/debian/patches/series 2018-05-24 19:06:54.000000000 +0000 +++ treeline-3.0.1/debian/patches/series 2018-10-17 13:06:15.000000000 +0000 @@ -1,2 +1 @@ pop_empty_list.patch -#qt4_to_qt5.patch diff -Nru treeline-2.0.2/debian/rules treeline-3.0.1/debian/rules --- treeline-2.0.2/debian/rules 2018-05-24 19:07:35.000000000 +0000 +++ treeline-3.0.1/debian/rules 2018-10-17 13:06:15.000000000 +0000 @@ -15,12 +15,11 @@ build-stamp: configure-stamp dh_testdir #make - export PATH=/usr/share/qt4/bin/:$$PATH ; \ - cd i18n/translations ; \ - for i in *.ts ; do \ - echo Compiling i18n file $$i ; \ - lrelease $$i ; \ - done + cd i18n/translations ; \ + for i in *.ts ; do \ + echo Compiling i18n file $$i ; \ + lrelease --qt=qt5 $$i ; \ + done touch $@ clean: @@ -28,7 +27,7 @@ dh_testroot rm -f build-stamp configure-stamp [ ! -d debian/patches ] || $(MAKE) -f /usr/share/quilt/quilt.make unpatch - rm -fv i18n/translations/*.qm + rm -fv i18n/translations/*.qm translations/*.qm dh_clean install: build diff -Nru treeline-2.0.2/debian/source/include-binaries treeline-3.0.1/debian/source/include-binaries --- treeline-2.0.2/debian/source/include-binaries 2011-05-17 19:21:42.000000000 +0000 +++ treeline-3.0.1/debian/source/include-binaries 2018-10-17 13:06:15.000000000 +0000 @@ -1 +1,2 @@ debian/treeline.png +debian/treeline-doc.png diff -Nru treeline-2.0.2/debian/source/options treeline-3.0.1/debian/source/options --- treeline-2.0.2/debian/source/options 2011-05-02 23:37:02.000000000 +0000 +++ treeline-3.0.1/debian/source/options 1970-01-01 00:00:00.000000000 +0000 @@ -1,5 +0,0 @@ -# Bzip2 compression for debian.tar -compression = "bzip2" -compression-level = 7 -# Do not generate diff for changes in config.(sub|guess) -extend-diff-ignore = "(^|/)config.(sub|guess)$" Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/debian/treeline-doc.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/debian/treeline-doc.png differ diff -Nru treeline-2.0.2/debian/treeline-doc.svg treeline-3.0.1/debian/treeline-doc.svg --- treeline-2.0.2/debian/treeline-doc.svg 1970-01-01 00:00:00.000000000 +0000 +++ treeline-3.0.1/debian/treeline-doc.svg 2018-10-17 13:06:15.000000000 +0000 @@ -0,0 +1,221 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + +   + + + + + + + + + + + + + + + + + diff -Nru treeline-2.0.2/debian/treeline.mime treeline-3.0.1/debian/treeline.mime --- treeline-2.0.2/debian/treeline.mime 2011-05-17 19:04:18.000000000 +0000 +++ treeline-3.0.1/debian/treeline.mime 2018-10-17 13:06:15.000000000 +0000 @@ -1 +1,2 @@ application/x-treeline; treeline %s; test=test "$DISPLAY" != ""; description=TreeLine Data; nametemplate=%s.trl; priority=6 +application/x-treeline; treeline %s; test=test "$DISPLAY" != ""; description=TreeLine Data; nametemplate=%s.trln; priority=6 Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/debian/treeline.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/debian/treeline.png differ diff -Nru treeline-2.0.2/debian/treeline.sharedmimeinfo treeline-3.0.1/debian/treeline.sharedmimeinfo --- treeline-2.0.2/debian/treeline.sharedmimeinfo 2011-05-17 19:04:18.000000000 +0000 +++ treeline-3.0.1/debian/treeline.sharedmimeinfo 2018-10-17 13:06:15.000000000 +0000 @@ -4,5 +4,7 @@ TreeLine Data Datos de TreeLine + + diff -Nru treeline-2.0.2/debian/treeline.svg treeline-3.0.1/debian/treeline.svg --- treeline-2.0.2/debian/treeline.svg 2018-04-08 20:12:57.000000000 +0000 +++ treeline-3.0.1/debian/treeline.svg 2018-10-17 13:06:15.000000000 +0000 @@ -14,11 +14,11 @@ viewBox="0 0 256 256" id="svg2" version="1.1" - inkscape:version="0.91 r13725" - sodipodi:docname="tl_logo.svg" - inkscape:export-filename="tl_logo_16.png" - inkscape:export-xdpi="5.6199999" - inkscape:export-ydpi="5.6199999"> + inkscape:version="0.92.3 (2405546, 2018-03-11)" + sodipodi:docname="treeline.svg" + inkscape:export-filename="/home/inniyah/treeline/TreeLine/debian/treeline.png" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96"> @@ -77,14 +77,15 @@   basichelp - - - -
-Views

-
Tree View - The left-hand view shows a tree of node titles. Parent nodes can be opened and closed to display or hide their indented descendant nodes. Clicking on an already selected node allows the title to be edited. Right-click context menus are available for commonly used functions.

-Right-hand Views - The right pane is tabbed to show one of three different views of the data. The "Data Output" view shows the formatted text, the "Data Edit" view shows a text edit boxes, and the "Title List" view shows an editable list of node titles.
-
-When a parent node is selected in the tree, the right view will default to showing information about the selected node in an upper pane and information about the selected node's children in a lower pane. The "View > Show Child Pane" command will toggle the display of the child nodes. If the selected node has no children, the view will show a single pane with information about the selected node only.
-
-When multiple nodes are selected in the tree (by holding down the shift or Ctrl keys while clicking), the right view will not display any child node information. It will instead show information about every selected node.

-Data Output View - The "Data Output" view shows formatted output text. It cannot be edited from this view.
-
-When the "View > Show Output Descendants" command is toggled, the "Data Output" view will show an indented list with information about every descendant of a single selected node.

-Data Edit View - The "Data Edit" view shows a text edit box for each data field within a node. It also shows the node types and the node titles. The types of edit boxes vary based on the field type. Some are just text editors, while others (such as choice fields, date fields, links, etc.) have pull-down menus or dialogs.

-Title List View - The "Title List" view shows a list of node titles that can be modified using typical text editor methods. If a new line is typed, a new node is created with that title. If a line is deleted, the corresponding node is removed from the tree.

-Editing

-
Node Menu - The commands in the "Node" menu operate on the selected nodes in the left tree view. There are commands to add or insert nodes, rename node titles and delete nodes. There are also commands to rearrange the tree by changing indent levels or moving nodes up or down. For many of the commands, the descendants of the selected nodes are also affected.

-Edit Menu - The edit menu includes undo and redo commands that can fix problems. Cut, copy and paste commands can operate either on text in the right-hand view (if selected or active) or to tree nodes. It also has text formatting commands that are active when using edit boxes in the Data Edit view.

-Shortcuts - There are several shortcuts for use in tree editing. Drag and drop will move (or copy if the Ctrl button is held) nodes. Clicking on a selected node will rename it. Pressing the delete key will remove the selected nodes. If desired, these shortcuts can be disabled in "Tools > General Options".

-Files

-
Templates - When starting a new file, a dialog box offers a choice of templates. The default has only a single text field for each node that contains the title. The Long Text template adds a second long text field for more output text. Other templates have various fields for contacts, book lists and to-do lists.

-Sample Files - Various TreeLine sample files can be opened by using the "File > Open Sample" command. These have more detail and example content than the new file templates.

-Data Types

-
Node Types - Multiple node data types can be defined in a TreeLine file. Each can contain different data fields and have different output formats. See the template and sample files for examples. Nodes can be set to a specific type using the "Data > Set Node Type" command.

-Type Config - The "Data > Configure Data Types" command is used to modify node data types, fields and output formatting. Refer to the Detailed Usage section of the full documentation for details.
-
- - + +Views
+ +
+ +Tree View - The left-hand view shows a tree of node titles. Parent +nodes can be opened and closed to display or hide their indented descendant +nodes. Clicking on an already selected node allows the title to be edited. +Right-click context menus are available for commonly used functions.

+ +Breadcrumb View - The top pane shows the parent and ancestors of the +selected node. It is blank if no nodes or multiple nodes are selected. +Ancestors with blue text can be clicked to select those nodes.

+ +Right-hand Views - The right pane is tabbed to show one of three +different views of the data. The "Data Output" view shows the formatted text, +the "Data Edit" view shows text edit boxes, and the "Title List" view shows +an editable list of node titles.

+ +When a parent node is selected in the tree, the right view will default to +showing information about the selected node in an upper pane and information +about the selected node's children in a lower pane. The "View > Show Child +Pane" command will toggle the display of the child nodes. If the selected node +has no children, the view will show a single pane with information about the +selected node only.

+ +When multiple nodes are selected in the tree (by holding down the shift or +Ctrl keys while clicking), the right view will not display any child node +information. It will instead show information about every selected node.

+ +When no nodes are selected in the tree (by clicking on a blank area or Ctrl +clicking to unselect), the right view will show information about the +top-level (root) nodes.

+ +Data Output View - The "Data Output" view shows formatted output text. +It cannot be edited from this view.

+ +When the "View > Show Output Descendants" command is toggled, the "Data +Output" view will show an indented list with information about every +descendant of a single selected node.

+ +Data Edit View - The "Data Edit" view shows a text edit box for each +data field within a node. It also shows the node types and the node titles. +The types of edit boxes vary based on the field type. Some are just text +editors, while others (such as choice fields, date fields, links, etc.) have +pull-down menus or dialogs.

+ +Title List View - The "Title List" view shows a list of node titles +that can be modified using typical text editor methods. If a new line is +typed, a new node is created with that title. If a line is deleted, the +corresponding node is removed from the tree.

+ +
+
+ +Editing
+
+ +
+ +Node Menu - The commands in the "Node" menu operate on the selected +nodes in the left tree view. There are commands to add or insert nodes, rename +node titles and delete nodes. There are also commands to rearrange the tree by +changing indent levels or moving nodes up or down. For many of the commands, +the descendants of the selected nodes are also affected.

+ +Edit Menu - The edit menu includes undo and redo commands that can fix +problems. Cut, copy and paste commands can operate either on text in the +right-hand view (if selected or active) or to tree nodes.

+ +Format Menu - The format menu has text formatting commands that are +active when using edit boxes in the Data Edit view.

+ +Shortcuts - There are several shortcuts for use in tree editing. Drag +and drop will move (or copy if the Ctrl button is held) nodes. Clicking on a +selected node will rename it. Pressing the delete key will remove the selected +nodes. If desired, these shortcuts can be disabled in "Tools > General +Options".

+ +
+
+ +Files
+
+ +
+ +Templates - When starting a new file, a dialog box offers a choice of +templates. The default has only a single text field for each node that +contains the title. The Long Text template adds a second long text field for +more output text. Other templates have various fields for contacts, book lists +and to-do lists.

+ +Sample Files - Various TreeLine sample files can be opened by using the +"File > Open Sample" command. These have more detail and example content +than the new file templates.

+ +
+
+ +Data Types
+
+ +
+ +Node Types - Multiple node data types can be defined in a TreeLine +file. Each can contain different data fields and have different output +formats. See the template and sample files for examples. Nodes can be set to a +specific type using the "Data > Set Node Type" command.

+ +Type Config - The "Data > Configure Data Types" command is used to +modify node data types, fields and output formatting. Refer to the Detailed +Usage section of the full documentation for details.
+ +
+ + diff -Nru treeline-2.0.2/doc/documentation.trl treeline-3.0.1/doc/documentation.trl --- treeline-2.0.2/doc/documentation.trl 2015-10-03 18:20:31.000000000 +0000 +++ treeline-3.0.1/doc/documentation.trl 1970-01-01 00:00:00.000000000 +0000 @@ -1,1514 +0,0 @@ - - -TreeLine Documentation - -Introduction - -Version -This document covers TreeLine, Version 2.0.2, released October 3, 2015 by Doug Bell. - - -Why TreeLine? -Do you have lots of sticky notes lying around with various useful information jotted down? Or many lists of books, movies, links, website logins, personal contacts, or things to do? Can you find them when you need them? Well, I often couldn't. So here's my answer. - - -Outliner vs. database -Treeline is both an outliner and a small database. It stores almost any kind of information. A tree structure makes it easy to keep things organized. And each node in the tree can contain several fields, forming a database. The output format for each node can be defined, and the output can be shown on the screen, printed, or exported to HTML or text. - - -License -Since I'm not in the software business, I'm making this program free for anyone to use, distribute and modify, as long as it stays non-proprietary. TreeLine is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either Version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY. See the LICENSE file provided with this program for more information. - - - -Features - -General - -Store information -Stores almost any type of information, including plain text, rich text, HTML, numbers, dates, times, booleans, URLs, etc. - - -Tree structure -The tree structure helps keep things organized. - - -Fields -Each node can have several fields that form a mini-database. - - -Node types -Several node types, with different sets of fields, can be included in one file. - - -Output format -The node format, including fields, output lines, formatting and tree-view icon, can be defined for each node type. - - - -Views - -Tree view -The left-hand view shows an indented list of titles - - -Right-hand views -The right-hand view can show one of three views - for showing output, editing node data and editing node titles. - - -Show parent and children -The right-hand view is normally split to show data from the parent node and its children. - - -Show multiple selection -If multiple nodes are selected, the right-hand view shows all of their data. - - -Show descendant output -The output view can be set to show indented output from all descendant nodes. - - - -Navigation - -Find commands -Find commands can search node data for text or for more specific rules. - - -Filtering -Filtering commands show only matching nodes in a flat left-hand view. - - -Toggle selections -Previous and next selection commands toggle selections to quickly move between parts of the tree. - - - -Formatting - -Configuration dialog -The dialog for data type configuration has several tabs to easily set all type, field and output parameters. - - -Format copies -Formatting information can be copied from another TreeLine file. - - - -File Handling - -Undo/redo -Undo and redo commands are available for all modifying operations. - - -File formats -TreeLine files are XML by default, with options for automatically compressing or encrypting the files. - - -Document templates -Document templates for new files are preformatted to cover basic needs. - - -Printing -The formatted output can be printed with parent/child lines, headers and footers. - - - -File Import and Export - -HTML export -The data can be exported to single or multiple HTML files with optional navigation panes. - - -Text import/export -Plain text, tab-indented text and tab-delimited table files can be imported and exported. - - -Bookmark import/export -Mozilla and XBEL format bookmark files can be imported and exported. - - -Generic XML import/export -Generic XML files can be imported and exported, allowing TreeLine to function as a crude XML editor. - - -ODF import/export -ODF text documents can be imported and exported as outlines. - - - -Linking - -Internal links -Internal link fields toggle the selection to a linked node when clicked. - - -External links -External link fields can be used to open URLs in web browsers. - - -Embedded links -Both internal and external links can be embedded into text fields. - - - -Data Manipulation - -Sorting -Nodes can be sorted by title or by predefined node type criteria. - - -Math Fields -Math fields can be defined that automatically calculate their contents based on numerical values in other nodes. - - -Numbering -Numbering fields can be defined and automatically updated. - - -Conditional types -A node's icon and output format can be changed conditionally based on its data. - - -Spell check -Text data can be spell checked (requires an external program - see the <a href="#system_requirements">System Requirements</a> section). - - -Arranging data -Data can be automatically arranged using either parent references or categories from data fields. - - - -Customization - -Customization -There are many options for customizing both general and file-based attributes. - - -Shortcuts and toolbars -There are editors for keyboard shortcuts and toolbar commands. - - -Fonts -Fonts used for editing and output can be customized. - - -Languages -The user interface is available in English, German and Portuguese. - - -Plugins -Plugin extension modules can be loaded to further customize TreeLine. - - - - -System Requirements - -Linux - -Python -Python (Version 3.2 or higher) - - -Qt -QT (Version 4.6, 4.7 or 4.8) - - -PyQt -PyQt (Version 4.8.x, 4.9.x, 4.10.x or 4.11.x) - - -Spell check -If spell checking is desired, either aspell, ispell or hunspell are required - - - -Windows - -Binary -Should run on any computer running Windows XP, Vista, 7 or 8. - - -Spell check -If spell checking is desired, an external program is required. Either aspell, ispell or hunspell must be installed. - - - - -Installation - -Linux - -Basic installation -Extract the source files from the treeline tar file, then change to the TreeLine directory in a terminal. For a basic installation, simply execute the following command as root: "python install.py". - - -Python 3 -If your distribution defaults to Python 2.x, you may need to substitute "python3", "python3.2" or "python3.3" for "python" in these commands. - - -Install options -To see all install options, use: "python install.py -h". To install TreeLine with a different prefix (the default is /usr/local), use: "python install.py -p /prefix/path". - - - -Windows - -All users -To install for all users, execute the "TreeLine-x.x.x-install-all.exe" file. Administrator permissions are required. - - -Single user -To install for a single user (administrator rights are not required), execute the "TreeLine-x.x.x-install-user.exe" file. - - -Portable install -For a portable install, execute the "TreeLine-x.x.x-install-user.exe" file. The file association, shortcuts and uninstaller tasks should be unchecked. When TreeLine starts and prompts for the config file location, choose the program directory option. - - - - -Basic Usage - -Views - -Tree View -The left-hand view shows a tree of node titles. Parent nodes can be opened and closed to display or hide their indented descendant nodes. Clicking on an already selected node allows the title to be edited. Right-click context menus are available for commonly used functions. - - -Right-hand Views -The right pane is tabbed to show one of three different views of the data. The "Data Output" view shows the formatted text, the "Data Edit" view shows a text edit boxes, and the "Title List" view shows an editable list of node titles.<br /> -<br /> -When a parent node is selected in the tree, the right view will default to showing information about the selected node in an upper pane and information about the selected node's children in a lower pane. The "View &gt; Show Child Pane" command will toggle the display of the child nodes. If the selected node has no children, the view will show a single pane with information about the selected node only.<br /> -<br /> -When multiple nodes are selected in the tree (by holding down the shift or Ctrl keys while clicking), the right view will not display any child node information. It will instead show information about every selected node. - - -Data Output View -The "Data Output" view shows formatted output text. It cannot be edited from this view.<br /> -<br /> -When the "View &gt; Show Output Descendants" command is toggled, the "Data Output" view will show an indented list with information about every descendant of a single selected node. - - -Data Edit View -The "Data Edit" view shows a text edit box for each data field within a node. It also shows the node types and the node titles. The types of edit boxes vary based on the field type. Some are just text editors, while others (such as choice fields, date fields, links, etc.) have pull-down menus or dialogs. - - -Title List View -The "Title List" view shows a list of node titles that can be modified using typical text editor methods. If a new line is typed, a new node is created with that title. If a line is deleted, the corresponding node is removed from the tree. - - - -Editing - -Node Menu -The commands in the "Node" menu operate on the selected nodes in the left tree view. There are commands to add or insert nodes, rename node titles and delete nodes. There are also commands to rearrange the tree by changing indent levels or moving nodes up or down. For many of the commands, the descendants of the selected nodes are also affected. - - -Edit Menu -The edit menu includes undo and redo commands that can fix problems. Cut, copy and paste commands can operate either on text in the right-hand view (if selected or active) or to tree nodes. It also has text formatting commands that are active when using edit boxes in the Data Edit view. - - -Shortcuts -There are several shortcuts for use in tree editing. Drag and drop will move (or copy if the Ctrl button is held) nodes. Clicking on a selected node will rename it. Pressing the delete key will remove the selected nodes. If desired, these shortcuts can be disabled in "Tools &gt; General Options". - - - -Files - -Templates -When starting a new file, a dialog box offers a choice of templates. The default has only a single text field for each node that contains the title. The Long Text template adds a second long text field for more output text. Other templates have various fields for contacts, book lists and to-do lists. - - -Sample Files -Various TreeLine sample files can be opened by using the "File &gt; Open Sample" command. These have more detail and example content than the new file templates. - - - -Data Types - -Node Types -Multiple node data types can be defined in a TreeLine file. Each can contain different data fields and have different output formats. See the template and sample files for examples. Nodes can be set to a specific type using the "Data &gt; Set Node Type" command. - - -Type Config -The "Data &gt; Configure Data Types" command is used to modify node data types, fields and output formatting. Refer to the Detailed Usage section of the full documentation for details. - - - - -Detailed Usage - -Tree Navigation and Search - -Keyboard Shortcuts -There are several keyboard commands that can be used for tree navigation. The up and down arrow keys move the selection. The left and right arrows open and close the current node. Holding the CTRL and SHIFT keys with "J" or "K" moves between siblings, skipping children. The "Home", "End", "Page Up" and "Page Down" keys can be used to move quickly through the tree. The CTRL and SHIFT keys can be held with the "Page Up" and "Page Down" keys to move the tree view even when a different pane is focused.<br /> -<br /> -Another way to move through the tree is to type the first letter of a visible node title. Hitting the letter again moves to the next possibility. - - -Selection -Multiple nodes can be selected by holding down the CTRL or the SHIFT key when changing the active node. Individual nodes are added or removed from the selection when the CTRL key is held. The selection of all nodes between the old and new active nodes are toggled when SHIFT is held. The active node can be changed by using the mouse or by using any of the keyboard navigation methods.<br /> -<br /> -Note that multiple nodes selected with the CTRL key are output in the order selected.<br /> -<br /> -The "View &gt; Previous Selection" and "View &gt; Next Selection" commands can be used to toggle through a history of selections, allowing faster navigation through the tree. - - -Searching -The "Tools &gt; Find Text" command will search for text within the tree structure. The dialog box has options for searching all of the node data or only the node titles. There are also options for how to interpret the search text. Key words will match nodes with the search words found anywhere in the node. Key full words will only match complete words anywhere in the node. Full phrase will only match the complete phrase in the proper sequence. Finally, the regular expression option will search using Python regular expressions.<br /> -<br /> -The "Tools &gt; Conditional Find" command will search in particular node types and node fields. Various comparison operators can be selected to exactly match, to match a greater or lesser value, or part of the value. And the True/False operators give the same result regardless of the values. In general, the value is interpreted using the edit format for special field types. Multiple rules can be added, connected with logical "and" or "or" operators. The "All Types" option makes fields from every type are available, so that multiple node types to be part of the same search. The condition will be false for node types that do not contain that field name. - - -Filtering -There are two filtering commands, "Tools &gt; Text Filter" and "Tools &gt; Conditional Filter". They work like the corresponding search commands above, except that they show all of the matching nodes in a flat list that replaces the tree view. The nodes can be selected and edited from this view. Use the "End Filter" button to restore the full tree view. - - - -Defining Node Types - -Type List -The "Type List" is the first tab of the "Data &gt; Configure Types Dialog". The list of data types can be modified by the buttons on the right. New types can be added, and existing types can be copied, renamed or deleted. - - -Type Config -"Type Config" is the second tab of the Configure Types Dialog. It contains a selection for the default child type. If set, this will be the initial type used for new children with this type of parent. If set to "[None]", children will default to either the type of their siblings or their parent.<br /> -<br /> -The "Change Icon" button allows the selection of a custom tree icon for this data type. The "Clear Select" button on the icon dialog can be used to set the icon to "None", so that no icon will be displayed for this type. To avoid showing any tree icons, the "Show icons in the tree view" general option can be unset.<br /> -<br /> -There are also options here for adding blanks lines between nodes, allowing HTML tags in the common format text, and changing the output to add bullets or tables. - - -Field List -The "Field List" is the third tab of the Configure Types Dialog. The list of fields within a data type can be modified by using the buttons on the right. New fields can be added, and existing fields can be moved, renamed or deleted. - - -Field Config -"Field Config" is the fourth tab of the Configure Types Dialog. The field type and its output format string can be set, if applicable to the field. Extra prefix and suffix text to be output with the field can also be set, and a default field value for new nodes can be entered. Finally, the number of lines displayed in the editor for the field can be specified. - - -Output -"Output" is the last tab of the Configure Types Dialog. The left half of the dialog shows the fields. The right half shows the formatting for the title (used for the node text in the tree view) and the node output. The formatting consists of text lines with embedded fields. The fields are shown as "{*field_name*}". The field that is selected in the list can be added to a format at the cursor position with the "&gt;&gt;" keys. The field reference at the cursor can be removed with the "&lt;&lt;" keys. - - - -Field Types - -Field Options -The field type and options are set in the "Field Config" tab of the "Data &gt; Configure Types Dialog". The many different field types are described in the paragraphs below.<br /> -<br /> -Several of the field types use a formatting string to define their output. For a list of available formatting characters, use the "Format Help" button. Entries in the data editor which do not match the format will cause a blue triangle to show in the upper left corner of the edit box, and the output for that field will be replaced with "#####". - - -Text Type -The default field type is a text field. It is the most commonly used field. These fields are edited using edit boxes in the data editor view. There are several commands in the Edit menu (and also in the context menu) for setting the font style and adding external or internal links. The edit box height expands when re-displayed after adding several lines of text. The minimum edit box height can also be set explicitly in the "Field Config" tab. - - -HTML Text Type -This type allows simple HTML tags to be used in the text. Commonly used tags include "&lt;b&gt;bold&lt;/b&gt;", "&lt;u&gt;underline&lt;/u&gt;", "line break&lt;br/&gt;", "horizontal line&lt;hr/&gt;", and various font tags. Complex block tags should generally be avoided. Carriage returns are ignored and non-escaped "&lt;", "&gt;" and "&amp;" symbols do not display. - - -One Line Text Type -This type restricts the text length to a single line. It does not allow carriage returns, but does not restrict line wrapping of a single long line. - - -Spaced Text Type -This type holds plain text and preserves all spacing. Other formatting of the text is not permitted. It could be useful to use the "Tools &gt; Customize Fonts" command to set the editor font to a mono-spaced font when using this field type. - - -Number Type -In the number type, special characters in the format define the display of the numbers. The format uses a string of "#" (optional digit) and "0" (required digit) characters to define the output formatting. For example, pi formatted with "#.#" is "3.1" and formatted with "00.00" is "03.14". Regardless of the formatting, digits to the left of the decimal point are not truncated, since that would display an incorrect result. But use care to show enough decimal places (either optional or required) to avoid problems with round-off error.<br /> -<br /> -The radix character can be specified as either "." or "," to handle internationalization. For use as a thousands separator, use "\," or "\.". For example, a large number may be formatted as "#\,###\,###.##" or as "#\.###\.###,##". Press the "Format Help" button from the field format dialog for more formatting details. <br /> -<br /> -Unlike most other formats, the number type also uses the output format for display in the Data Editor. Of course, any new entry with a reasonable format is correctly interpreted (but the correct radix character must be used). - - -Boolean Type -This type gives two choices corresponding to true/false values. The format help menu includes typical values such as "yes/no", "true/false" and "1/0", but users can also enter their own word pair. The data editor boxes will accept either the currently set format or any of the typical values. - - -Date Type -In the date field type, special characters in the format are replaced by elements of the data, similar to number fields. Press the "Format Help" button from the field format dialog for formatting details. <br /> -<br /> -There is also an edit format under "Tools &gt; General Options &gt; Data Editor Formats". This controls how date fields are displayed in the Data Editor view. Generally, entries in the data editor with various formats will be correctly interpreted regardless of this setting, but dates must use the correct day-month-year sequence. Also note that the date editor format does not support days of the week.<br /> -<br /> -A default initial field value of "Now" can be used to enter the date of node creation. - - -Time Type -In the time field type, special characters in the format are replaced by elements of the data, similar to number fields. Press the "Format Help" button from the field format dialog for formatting details. <br /> -<br /> -There is also an edit format under "Tools &gt; General Options &gt; Data Editor Formats". This controls how time fields are displayed in the Data Editor view. Generally, entries in the data editor with various formats will be correctly interpreted regardless of this setting.<br /> -<br /> -A default initial field value of "Now" can be used to enter the time of node creation. - - -Choice Type -The choice field type allows for the selection of text items from a pull-down edit list. The formatting strings for these types list the items separated with the "/" character (use "//" to get a literal "/" in an item). Entries in the data editor which do not match the choices will cause a blue triangle to show in the upper left corner of the edit box, and the output for that field will be replaced with "#####". - - -Auto Choice Type -This field type is similar to the choice type, but without a format string. The entries in the pull-down menu are automatically generated from all previously used entries. Any entries that are typed will be available in the pull-down menu for future use. - - -Combination Type -This is basically the equivalent of the choice type with multiple selection. The formatting string entries are separated by the "/" character. The pull-down menu shows check-boxes that are checked for currently selected nodes. By default, the selected entries are output separated by a comma and a space. This can be changed in the "Type Config" tab of the "Data &gt; Configure Types Dialog". Click the "Show Advanced" button to see the separator setting. - - -Auto Combination Type -This field type is similar to the combination type, but without a format string. The entries in the pull-down menu are automatically generated from all previously used entries. Any entries that are typed will be available in the pull-down menu for future use. - - -External Link Type -This type can support various link protocols, including http and https for web pages, mailto for email addresses and file for local files. A pull down dialog in the data editor allows the selection of the protocol type and the entry of an address and a display name. In the edit boxes, the display name shows up in [brackets], and it is used as the text of the link in the output view. The "file" protocol also provides a button to browse for a path and buttons to choose either absolute or relative path names.<br /> -<br /> -Clicking on the link in the output view or choosing "Open Link" from the right-click context menu in the edit box will open the link in a web browser. In Linux, setting the "BROWSER" environment variable to a string like "mozilla %s" will result in the desired browser being used. - - -Internal Link Type -These links create shortcuts to select nodes elsewhere in the tree structure. The unique ID of the referenced node is entered along with a display name. The pull down dialog in the data editor has an "Enable click on target" button. Once clicked, the target node can be selected and the appropriate unique node ID will be entered. Like external links, this link can be clicked in the output view, or "Open Link" can be chosen from the right-click context menu. - - -Picture Type -These links add referenced pictures to the output view. A pull down dialog in the data editor has a button to browse for picture files to be linked. It also allows absolute or relative paths to be used and has a small image preview. - - -Math Type -Math field types are configured by defining equations. The equations can reference number fields, date fields, time fields, boolean fields, text fields and/or other math fields. The resulting values of math fields are automatically calculated for each node.<br /> -<br /> -To define a math field equation, press the "Define Equation" button in the "Field Config" tab of the "Data &gt; Configure Types Dialog". This brings up a dialog with fields to reference on the left and math operators on the right. The "Reference Level" pull-down determines whether the reference is from the same node, the node's parent, the root node, or the node's children. The "Result Type" pull-down allows numeric, date, time, boolean or text results to be chosen. The "Operator Type" pull-down allows numeric, comparison or text operators to be shown in the operator list. The down-arrow buttons below the references and operators add the selected item to the equation text below. Portions of equations can also be typed directly in the equation text line editor.<br /> -<br /> -References to child nodes must be enclosed in a grouping function, such as sum, max, min, mean or join. A math field can contain a parent or child reference to itself, but not a same-level reference to itself (a circular reference). The references only contain the field name, so they will reference a parent or child field with that name even if it is a different node type.<br /> -<br /> -In equations, date fields are represented by the number of days since January 1, 1970, and time fields are the number of seconds since midnight. So date fields can be subtracted to give the number of days elapsed, and numbers of days can be added to or subtracted from dates to result in new dates. Time fields can be subtracted to give the number of seconds elapsed, and numbers of seconds can be added to or subtracted from times to result in new times.<br /> -<br /> -The "if" comparison operator can be used to make the result depend on the value of another field. The expression is written as "true_value if condition else false_value". Of course, the "true_value", "condition" and "false_value" strings must be replaced with valid fields or expressions. If this operator is inserted from the operator list (under comparisons), it will include parenthesis as placeholders. These parenthesis are optional in the equations.<br /> -<br /> -The "join" text function is used to combine text from several other fields (or from child nodes). The first argument to the function is a separator string that is placed between each piece of text. The remaining argument(s) are the text to be joined.<br /> -<br /> -By default, math fields are shown in the data edit view, but they are read-only. To hide them, uncheck the "Show math fields in the Data Edit View" box under "Tools &gt; General Options &gt; Features Available".<br /> -<br /> -There is also an option under "File &gt; Properties" that toggles whether blank fields are treated as zeros. If checked (the default), a blank field that is referenced by a math field has a value of zero (for numeric operations) or blank (for text operations). If unchecked, any blank references also cause equations that reference them to be blank.<br /> - - - -Numbering Type -This type (not to be confused with the number type above) provides fields that are automatically filled in with the "Data &gt; Update Numbering" command. The "Format Help" button in the field format dialog shows the output format options. A single format level will result in a simple sequential numbering scheme. Use of the "/" level separator will result in an outline-type numbering with different sequences at different levels. Use of the "." section separator will result in a "2.3.5" type numbering scheme.<br /> -<br /> -Since numbering fields are automatically populated, by default they are not shown in the data edit view. To show them, check the "Show numbering fields in the Data Edit View" box under "Tools &gt; General Options &gt; Features Available". When they are shown in the data edit view, they show up in section numbering style, regardless of the output format. - - -Regular Expression Type -This type allows arbitrary format strings to be matched that restrict the data to a particular format. It uses Python regular expression syntax. Entries in the data editor which do not match the format string expression will cause a blue triangle to show in the upper left corner of the edit box, and the output for that field will be replaced with "#####". - - - -Output Formatting Details - -Output Format Examples -Here is an example of output formatting for a book list:<br /> -<br /> -"{*Title*}"<br /> -(c) {*Copyright*}, Rating: {*Rating*}<br /> -{*PlotDescription*}<br /> -<br /> -Each of the field names in enclosed in {* *}, curly brackets and asterisks. For more examples, see the sample files that can be opened using the "File &gt; Open Sample" command. - - -Title Formats -When a node in the tree is renamed, the program attempts to match the title formatting pattern to set the appropriate fields (the same title matching occurs when editing lines in the "Title List" view). If the title formatting is too complex, it may not correctly guess the intent. Things like adjacent fields with no characters separating them should be avoided unless you do not wish to rename nodes from the tree.<br /> -<br /> -If the text data used for a tree view title has multiple lines, only the first line will be used as the title. - - -Skipped Output Lines -If a line in the output formatting contains one or more fields and all of those fields for a given node are empty, the line is skipped. No blank line or embedded text will be output for that line. Note that this does not apply to a line without any fields (only embedded text). Also, when a line ending with a &lt;br/&gt; or an &lt;hr/&gt; tag is skipped, the ending tag is retained. - - -HTML Tags -By default, the "Allow HTML rich text in formats" option is unchecked in the "Type Config" tab of the Configure Types Dialog. So any HTML tags are treated as plain text. If the option is enabled, simple HTML formatting tags can be used in node output formats.<br /> -<br /> -Commonly used tags include "&lt;b&gt;bold&lt;/b&gt;", "&lt;u&gt;underline&lt;/u&gt;", "line break&lt;br/&gt;", "horizontal line&lt;hr/&gt;", and various font tags. Complex block tags should generally be avoided. - - -Other Field References -References to fields that are not contained within the node can be added to the output. Pushing the "Show Advanced" button on the "Output" tab of the configure dialog makes a reference level selection become visible.<br /> -<br /> -If the reference level is changed to "File Info Reference", fields containing file meta-data can be added to the output. These include the file name, path, size, modified day and modified time. A field referencing the node's unique ID is also included here. These special fields are shown as "{*!field_name*}" in the title and output format editors.<br /> -<br /> -There are field references to various ancestor nodes (parents, grandparents, etc.). These require the data type of the reference to be specified. This selection determines the field names that are available, but the data from any type with a matching field name will be shown in the output. References to fields from parent and grandparent nodes are shown as "{**field_name*}" and "{***field_name*}", respectively. There are also general ancestor references, shown as "{*?field_name*}", that take data from the closest ancestor with a matching field.<br /> -<br /> -References to child nodes can also be added. These also require that the child data type be specified. The child data becomes embedded in the parent output. The child data is delimited with a separator string defined on the "Type Config" tab (with show advanced active). The separator defaults to a comma and a space, but can be set to &lt;br/&gt; or anything else.<br /> -<br /> -Finally, a "Child Count" reference can be added. This field will show the number of children ("Level1" field) or grandchildren ("Level2" field) of a node. These are shown as {*#Level1*} in the format editors.<br /> -<br /> -For examples of these fields, see the "sample_other_fields" file (by using the "File &gt; Open Sample" command). - - - -Type Format Details - -Unique IDs -Every node has a Unique ID string associated with it. The IDs are used for addresses in internal links and in other places where TreeLine needs to identify a particular node.By default, the text in the first field is used to generate the ID. This can be changed in the "Type Config" tab of the "Data &gt; Configure Types Dialog". Click the "Show Advanced" button to see the "Unique ID Reference Field" setting. <br /> -<br /> -To create the Unique ID, text in the reference field is modified by removing spaces and other special characters, shortening long entries, and adding extra characters if required to be unique in the file. If the content of the reference field changes, the Unique ID is updated, along with any links that reference it.<br /> -<br /> -The Unique IDs can be shown in the data edit view by checking the "Show node unique IDs in the Data Edit View" box under "Tools &gt; General Options &gt; Features Available". - - -Generic and Derived Types -Data types can be set to derive their field settings from a generic type. This allows types with different output formatting to always use the same set of fields. Any changes to the generic's list of fields and field types are automatically reflected in the fields of all derived types. This does not apply to a field's output formatting, which can still be set independently.<br /> -<br /> -There are two methods for creating derived types. First, a derived option can be selected when copying a type on the "Type List" tab of the "Data &gt; Configure Types Dialog". Alternately, a generic type can be specified from the derived type's "Type Config" tab of the dialog if the advanced functions are shown. - - -Conditional Types -Conditional expressions can be used to automatically assign a data type based on each node's content. Conditions can be assigned only to a generic type and its associated derived types. This allows the automatic assignment of different output formatting or different icons depending on each node's field data.<br /> -<br /> -The conditional dialog box is accessed from a button on the "Type Config" tab of the "Data-&gt;Configure Types Dialog" if the advanced functions are shown. Each line of the condition includes a field, an operator and a comparison value. The operators include equality, greater than, less than, starts with, ends with, and contains. There are also True and False operators that will toggle the type of all nodes simultaneously.<br /> -<br /> -For special field types such as dates, times, and booleans, the comparison value should be entered in the same format that is used in the Data Editor window. In general, the starts with, ends with, and contains operators should not be used for these special fields, since the comparison is done using an internal data representation. Dates and times also support a special comparison value of "now", which is always interpreted as the current date and time.<br /> -<br /> -The "Add New Rule" button is used to add additional condition lines. The lines can be joined with "and" or "or" operators. The "Remove Rule" button deletes the last condition line. If only a single line is present, the "Remove Rule" button completely removes the condition.<br /> -<br /> -Conditions do not have to be set for all types in a family. If no conditions are true for a node, the program will select a blank condition over a false one.<br /> -<br /> -For an example, see the "sample_conditional_todo" file (by using the "File &gt; Open Sample" command). - - -Copying Formats -Another method for changing data type formatting is to copy the formats from another TreeLine file. This is done with the "Data &gt; Copy Types from File" command. All types from the chosen file are copied. Any types in the current file with matching names are overwritten, but types with unique names are retained. - - - -Tree Data Operations - -Sorting -The "Data &gt; Sort Nodes" command can sort nodes based on either node titles or key fields predefined in the node type configuration. The predefined sort fields can be changed in the "Field Config" tab of the Configure Types Dialog. The "Sort Keys" button brings up a list of fields that define a sort key sequence. The fields higher in the sequence have a higher priority. The direction for each key field can be flipped. - - -Numbering -The "Data &gt; Update Numbering" command updates the contents of fields with a special numbering field type. The field's output format defines how the numbers are displayed in the output, including whether individual numbers, outline numbers or a section numbering scheme are shown. See the <a href="#numbering_type">Numbering Field Type</a> for more information.<br /> -<br /> -Note that numbering fields are not shown in the data edit view unless "Show numbering fields in the Data Edit View" is checked under "Tools &gt; General Options &gt; Features Available". When they are shown in the data edit view, they show up in section numbering style, regardless of the output format. - - -Find and Replace -The "Tools &gt; Find and Replace" command can be used to change the text in several nodes. The search text and replacement text are entered. Searching can be based on any match, full words only, or a Python regular expression. The operation can optionally be restricted to a particular node type and to a particular node field.<br /> -<br /> -Replacement using regular expressions is quite powerful. Searching for ".*" will match all of the text in the field. The replacement string can contain back references that consist of a backslash and a number. The back references get replaced with the corresponding parenthesized group from the match. For example, "\2" will be replaced with the text that matched the second group of parenthesis. The "\g&lt;0&gt;" back-reference can be used to substitute the entire matching string. - - -Spell Check -There is a spell check command in the "Tools" menu. Use of this command requires an external program to be installed (either aspell, ispell or hunspell- see the <a href="#system_requirements">System Requirements</a> section). If there are any misspelled words in the selected branch, a dialog will allow the word to be ignored, added to the dictionary, replaced with a suggestion or edited. This will spell check the text in all data fields of each node. - - -Category-Based Arrangement -The "Data" menu contains commands for arranging and flattening the data by category and by internal links. These methods are used to automatically add and remove levels of nodes below the current node in the tree.<br /> -<br /> -The "Add Category Level" command allows you to select one or more of the fields that the child nodes have in common. These fields are used to create new parent nodes for the children, grouping them by common categories. For example, in a list of books, picking the "author_first_name" and "author_last_name" fields will result in a tree with the books under new nodes for each unique author.<br /> -<br /> -The "Flatten by Category" command is almost the opposite of "Add Category Level". It eliminates any descendant nodes with children, transferring their data fields to their children. It will rename fields instead of overwriting data with the same field names, but this command is most useful when the children and parents are different types with unique field names. - - -Link-Based Arrangement -The "Data &gt; Flatten by Link" command will move all descendant nodes directly under a root node in a flat structure. It will add an internal link field to each node that points to the node's former parent.<br /> -<br /> -The "Data &gt; Arrange by Link" command does the opposite, placing each node under a parent node that is referenced by an internal link field. - - - -Printing - -Print Setup -The dialog for print setup contains four tabs. The first, General Options, includes settings for what part of the tree to print, whether to include lines to child nodes and whether to allow page breaks between a parent an its first child node. The second tab, Page Setup, includes paper size, orientation, margins and columns. The third tab selects the font for printing. The last tab, Header/Footer, defines text and file data meta-fields for inclusion in headers and footers. - - -Print Preview -The Print Preview window shows how the printout will look with current print settings. It can be dragged larger to show more detail, or the zoom settings can be changed. It also includes buttons for the Print Setup dialog and for printing. - - -Print to PDF -This command prompts for a path and file name to export a PDF file. It uses the current printer settings. - - - -File Handling - -File Compression -A TreeLine file is in an XML text format. There are also options to work with compressed files (gzip format) to save storage space. Individual files can be set to compressed mode from either "File &gt; Properties" or from the file type pull-down in the save-as dialog. - - -File Encryption -There is a file encryption option to password protect TreeLine files. Individual files can be set to encrypted mode from either "File &gt; Properties" or from the file type pull-down in the save-as dialog. The encryption uses an SHA hash function as a stream cipher - it should be fairly secure. - - -Auto-Save -An auto-save feature can store unsaved files with a "~" appended to the file name. The backup files are automatically removed when the file is saved or TreeLine exits cleanly. The auto-save time interval is set in the general options. Setting the interval to zero disables this feature. - - -Saved Tree States -When opening a recently used file, TreeLine will restore the states of open and selected nodes. This information is stored in the user's TreeLine configuration files. If desired, this feature can be disabled with a general option. - - - -File Import - -General Information -A TreeLine file is in a specific XML text format. Other types of files can be imported using the "File &gt; Import" command, which will show a dialog box where the type of import can be selected. Alternatively, using the "File &gt; Open" command with a non-TreeLine file will also show this dialog. - - -Text Import -There are four different text formats that can be selected for import. Tab indented text creates a node title from each line in the file, structured based on the number of tabs before each line. A tab delimited text table uses the first line as a header row to create field names, then each additional row becomes a node with field data taken from each column. The plain text, one node per line import creates a flat tree of node titles. Finally, the plain text paragraph import creates long text nodes from text separated by blank lines. - - -Treepad Import -Files from the Treepad shareware program can be imported. Only Treepad text nodes are supported. - - -XML Import -TreeLine will import and export generic XML files. These routines do not have much intelligence - each XML element becomes a node and each XML attribute becomes a field. XML text content become fields named "Element_Data". This lets TreeLine function as a crude XML editor. - - -ODF Import -TreeLine will import Open Document Format (ODF) text documents, from applications such as Apache OpenOffice and LibreOffice. The node structure is formed based on the heading styles assigned in the document. Any text under each heading is assigned to that heading's node. The import filter is intended for simple text outlines only. No formatting is maintained, and objects such as tables and pictures are not imported. - - -Bookmarks Import -TreeLine will import bookmark files in both the Mozilla HTML format (Firefox browser) and the XBEL format (Konqueror, Galeon and Elinks browsers). Each bookmark becomes a node with a name and a link field. Some information in the files, such as visited dates and icon references, is not imported. For an example, see the "sample_bookmarks" file (by using the "File &gt; Open Sample" command).<br /> - - - - -File Export - -General Information -Files are exported using the "File &gt; Export" command. This will show a dialog box of available export types and options. - - -HTML Export -The HMTL export subtypes can export to a single page or to multiple pages. The single page export contains all of the indented output. A navigation pane on the left with links to anchors at node positions is optional.<br /> -<br /> -The multiple HTML pages export has one web page per node. It includes a navigation pane on the left with links to the pages with sibling, parent and aunt/uncle nodes.<br /> -<br /> -Multiple HTML data tables export creates a table in each HTML file that contains the data for a set of siblings, as well as links to the parent and child pages. - - -Text Export -Data can be exported to tabbed title text and tab-delimited tables. These formats are the same as the corresponding import formats. When exporting to a table, only the first level of children is used, so you may wish to flatten the data before exporting.<br /> -<br /> -There is also an unformatted text export the dumps all of the output into a text file without preserving the tree structure. - - -XML Export -TreeLine will import and export generic XML files. These routines do not have much intelligence - each node becomes an XML element and each field becomes an XML attribute, except for fields named "Element_Data" that become the element's text. This lets TreeLine function as a crude XML editor. - - -ODF Export -TreeLine will export an outline to an Open Document Format (ODF) text document, compatible with Apache OpenOffice and LibreOffice. The title of each node is assigned a heading style at the appropriate level. Any other text in the output of each node becomes normal text under the heading. The export filter is intended for simple text outlines only. Any HTML formatting is stripped, and objects such as tables and pictures are not supported. - - -Bookmarks Export -TreeLine will export bookmark files in both the Mozilla HTML format (Firefox browser) and the XBEL format (Konqueror, Galeon and Elinks browsers). TreeLine will look for a link field in each node that becomes the target of the bookmark. - - - -Customizations - -Options -TreeLine's behavior can be modified with several settings available in "Tools &gt; General Options". Most of these options are covered elsewhere in this document. - - -Keyboard Shortcuts -Keyboard shortcuts can be customized by using the "Tools &gt; Set Keyboard Shortcuts" command. Simply type the new key sequence with the appropriate field selected. - - -Toolbars -An editor to customize the toolbars is available from "Tools &gt; Customize Toolbars". The number of toolbars can be set, and the buttons on each can be defined. - - -Fonts -Fonts used in the tree views, the output view and the editor views can be set in the "Tools &gt; Customize Fonts" menu. - - -Tree Icons -There is an icons directory in the user configuration directory ("~/.treeline-x.x/icons" on Linux, "Users\&lt;user&gt;\AppData\roaming\bellz\treeline-x.x\icons" on Windows). Image files (PNG or BMP) placed into this directory are available for use as tree icons. - - - -Plugins - -Description -TreeLine has an interface for plugin extension modules. This allows extensions to be written by various programmers to provide features needed by a few users without adding bloat to the main application. The plugins can add new menu items, access and modify TreeLine data, and read and write TreeLine Files.<br /> -<br /> -Currently available plugins are listed on the "Downloads" page of the TreeLine web site. - - -Installation -The plugins are installed by copying their Python file (*.py) to a plugins directory. This can be a directory in the TreeLine installation ("&lt;prefix&gt;/share/treeline/plugins/" on Linux or "TreeLine-2\plugins\" under the Program Files or Application Data folders on Windows) or in the user configuration directory ("~/.treeline-2.x/plugins/" on Linux, "Documents and Settings\&lt;user&gt;\Application Data\bellz\treeline-2.x\plugins\" on Windows). A list of loaded plugins can be found using the "Help &gt;About Plugins" command. - - -Programming Interface -Information needed to write new plugins can be found in the "plugininterface.py" file in the TreeLine source code. - - - - -Revision History - -October 3, 2015 - Release 2.0.2 (new stable release) - -Bug Fixes - -Data edit regression -Fixed a major regression in 2.0.1 that broke data editors for most specialized field types (number, math, boolean, choice, etc.) - - - -Compatibility Notes - -File format -There are some file format changes between TreeLine 1.4.x and this version of TreeLine. - - -File conversion -Older files opened in this version are automatically converted when saved. - - -File compatibility -Files saved in this version may not be fully compatible with TreeLine 1.4.x. - - - - -September 26, 2015 - Release 2.0.1 (new stable release) - -Updates - -Plugin options -Added methods to the plugin interface that allow general program options to be queried and changed. - - -Table import errors -Improve text table import error messages by including the line number where the problem is found. - - - -Bug Fixes - -Data edit undo -Reduce the amount of work that a single undo command removes from editors in the data edit view. - - -Configuration changes -Fixed a bug that prevented setting the unique ID reference field on a newly created data type. - - -Legacy newline convert -Preserve hard newlines in text fields when converting TreeLine 1.4.x files to this version. - - -Indent expand status -Fix problems preserving expand/collapse node states when indenting and unindenting nodes. - - - - -May 17, 2015 - Release 2.0.0 (new stable release) - -Updates - -Treepad import update -Modified the Treepad file import to use SpacedText fields to more closely match Treepad formatting. - - -Treepad export -An optional plugin was written that can export files to the Treepad text file format. - - -New icon -The TreeLine icon was replaced with a new one. Thanks to David Reimer for contributing the artwork. - - -Translation updates -The German and Portuguese GUI translations were updated. - - -Sample file updates -Updated the long text sample file to include the SpacedText field type, and added a conditional equation to the math sample file. - - -Documentation update -Updated the Math Field section of the documentation. - - - -Compatibility Notes - -File format -There are some file format changes between TreeLine 1.4.x and this version of TreeLine. - - -File conversion -Older files opened in this version are automatically converted when saved. - - -File compatibility -Files saved in this version may not be fully compatible with TreeLine 1.4.x. - - - - -March 29, 2015 - Release 1.9.7 (unstable development snapshot) - -New Features - -Math field conditions -Added comparison operators and conditional if statements to math field equations. The operators can be used with a new boolean result type, or as a part of numeric, date, time or text expressions. - - -Math field text operations -Text operators were made available in math field equations, and the result type can be set to text. This allows math fields to combine text from other fields, replace substrings and change capitalization. - - - -Updates - -Saved status bar message -Added a "file saved" status bar message. - - -German translation -Added a German GUI translation. - - -Portuguese translation -Updated the Portuguese GUI translation. - - - -Bug Fixes - -Combination editor -Fixed a focus problem that made Combination and AutoCombination field editor pull-downs unusable. - - -Choice editor -Fixed a focus problem that made the pull-downs in choice and boolean field editors unusable on Linux. - - - - -March 10, 2015 - Release 1.9.6 (unstable development snapshot) - -New Features - -OneLineText field -Added a new OneLineText field type that restricts the text length to a single line. - - -All Types conditional test -Added an All Types option to the conditional find and filter commands. This allows multiple node types to be found or filtered at the same time. The fields from every type are available for use in conditions. The conditions give a false result for all node types that do not contain that field name. - - -Math field dates and times -Added support for date and time calculations using math fields. Math equation result types can be set to numeric, date or time output. Date fields can be subtracted to give the number of days elapsed, and numbers of days can be added to or subtracted from dates, resulting in new dates. Time fields can be subtracted to give the number of seconds elapsed, and numbers of seconds can be added to or subtracted from times, resulting in new times. - - -Math field root references -In math fields, added an equation reference level to reference fields in the root node. This provides a place for "constant" field values that can be referenced from any node but only need to be changed in one location. - - -Plugin interface -Support was added for plugin extension modules. Most of the interface methods from TreeLine 1.4.x were duplicated to ease porting of old plugins. Of course, old plugins must be ported from Python 2.x to Python 3.x, and there are multi-window implementation differences. New interfaces allow the creation of new field types and the execution of any menu command. Sample plugins are available on the TreeLine download page. - - -Automatic HTML tags -Added text formatting and link commands for HTML fields that add tags to the HTML content. - - -Open in folder -An open in folder command was added to external link field editors to open the directory in a file manager. - - -Select All command -Added the Select All command to global menus with a different default shortcut key (ctrl-L) to avoid conflicts with the add child shortcut. - - - -Updates - -Data edit view focus -Improved the focus handling for data edit view edit boxes. This eliminates the blue outline for boxes in inactive data edit views. It also makes tab-to-focus more predictable, including fully selecting single-line field types when they receive tab focus. - - -Data editor resize -Data edit boxes are now automatically resized when editing is complete and the focus moves to another row. - - -Drag external links -Allow files to be drag &amp; dropped on data edit boxes to create external links. - - -Stay-on-top dialogs -Small, non-modal dialogs, such as those for sorting, numbering, finding and filtering, have been set to stay on top, so they won't be obscured by TreeLine windows. - - -Opening external links -Made opening associated programs from external file links more consistent, especially in Linux. - - -Copy types from file -The copy types from file command now supports encrypted and compressed files. - - -Wait cursors -Added wait cursors to TreeLine operations that could be time consuming. - - -Context menus -Improved the consistency of context menus and shortcut commands used in edit boxes. - - -Portuguese translation -Added a nearly complete Portuguese GUI translation. - - -Sample file languages -Added support for sample TreeLine files to be provided in alternate languages. - - -Documentation translation -Prepared the help file for translation into other languages, - - - -Bug Fixes - -Duplicate unique IDs -Corrupted TreeLine files with the same unique ID assigned to multiple nodes no longer fail to open. The user is warned that unique IDs have been updated, which could break some internal links. - - -Conditional field list -Fixed missing fields in the pull-down list in the conditional find and filter dialog boxes when a rule was re-used after a node type change. - - -Math field missing references -Fixed problems with math fields that reference non-existing fields in parent or child nodes. - - -Math field type updates -Made math fields update properly after node type changes. - - -Rename bullet/table fields -Fixed inconsistent updates after renaming fields used with bulleted or tabled output. - - -Copying descendants -Made node copy-paste and drag-and-drop work when the initial selection includes both parent and child nodes. - - -Tabbed text import -When importing a tabbed text file with multiple top-level nodes, create a single higher-level node to prevent failure. - - -Custom toolbar dialog -Fixed the availability of the move up button in the customize toolbar dialog when the second command in the list is selected. - - - - -December 31, 2014 - Release 1.9.5 (unstable development snapshot) - -New Features - -Math field type -Added a math field type that is configured by defining an equation. The field value is automatically calculated based on references to numerical values in other nodes. See the "sample_math_fields" file for a usage example. - - -Clear formatting -Added a Clear Formatting command that removes font changes and links from data editor text. - - -GUI Translations -Made source code changes necessary to support user interface translations into other languages. The actual translation work remains to be done. - - - -Updates - -Combination field scroll bars -Add scroll bars to the pull-down editors for combination fields with many entries. - - -External link truncation -Reduce the truncation of external link URLs when generating default display names. - - -File newlines -Use Unix-style newlines for saved TreeLine files to keep files consistent across platforms. - - -HTML panes -Update the CSS code in exported HTML with navigation panes to improve the appearance in some browsers. - - -Undo optimization -Optimize some undo information to reduce the amount of data in memory. - - -Move sample files -Move sample files into a separate directory to avoid future translation conflicts. - - -Installer clarification -Clarify a Linux installer error message when checking for the Python 3 version of PyQt. - - -Include MSVC files -Include MSVCR DLL files in the Windows installer. - - - -Bug Fixes - -Applying config dialog settings -Fix problems applying multiple configuration changes while the Configure Data Types dialog box remains open. - - -Title list line split -In the Title List editor, splitting a title into two lines now creates a new node without losing the children and parameters of the original node. - - -Title list undo -Fix the undo command in the Title List View so that deleted lines/nodes are properly restored. - - -Sorting by fields -When sorting nodes by fields, properly handle a reverse direction. - - -Find and replace -Fix problems with the find and replace command when a particular node type is specified. - - -Conditional types -Fix problems defining conditional types from the Configure Data Types dialog box. - - -Apply font sizes -Correctly apply font size formatting to selections with mixed font sizes. - - -Editor height -Fix the height of long text field editors with customized data editor fonts. - - -Base 64 image export -Fix HTML export of Base 64 images. - - - - -March 8, 2014 - Release 1.9.4 (unstable development snapshot) - -New Features - -Documentation -Created new documentation, including a TreeLine file with details and a text file with basic usage instructions. Both are accessible from the help menu. - - -Font settings -Added customizing of default fonts used in the tree, output and editor views. - - -Spaced text field -Added a new SpacedText field type that holds plain text and preserves all spacing. - - -Combination editor -Combination and auto combination field types now use a simpler checkbox style pull-down editor. - - -Autosave option -An autosave option was added. - - -Unique ID output -A unique ID reference field was added to the file info fields to allow node unique IDs to be included in output formats. - - -Paste plain text -A plain text paste command was added to paste non-formatted text to data editors. - - -Indent option -Added an option to set the child indent offset amount. - - - -Updates - -Print preview size -The last size and position of the print preview window are remembered and restored. - - -Page breaks in nodes -When printing, nodes with long text content are split between pages. - - -Export relative links -In multiple page HTML exports, relative links in the content are adjusted based on the directory depth. - - - -Bug Fixes - -Blank nodes -Fixed problems outputting completely blank nodes. - - -Conditional rules -Problems with the contains and true/false conditional rules were fixed. - - -Link dialogs -Fixed issues displaying several editor link dialogs in quick succession. - - -Output config cursor -In the configure data types dialog, the output format cursor no longer moves when switching to other field references. - - - - -January 19, 2014 - Release 1.9.3 (unstable development snapshot) - -New Features - -Keyboard shortcuts -Added controls in the Tools menu for customizing TreeLine's keyboard shortcuts. - - -Toolbars -Controls for customizing TreeLine's toolbar buttons were added to the Tools menu. - - -Conditional search -New dialogs were created for conditional finding and filtering of nodes. Specific conditions can be applied to individual types and fields, and the conditions can be saved. - - -Find and replace -Find and replace functionality was added to search and change nodes' text data. The search can be limited to specific types and fields. - - -Table output -A new option for output data in tables was added to the Type Config pane of the Configure Data Types dialog. Each line of the output format becomes a column. Any text at the start of the format line that is followed by a colon becomes a table heading. - - -Bullet output -An option to add bullets to the output of child nodes was added to the Type Config pane of the Configure Data Types dialog. - - -Toggle selections -Added View &gt; Previous Selection and Next Selection commands to step through the node selection history. - - - -Updates - -Drop to reorder -Nodes can now be reordered by dragging and dropping them between sibling nodes. - - -Date entry formats -The keyboard entry of dates and times into fields was made more flexible by allowing entries such as 4-digit years that don't exactly match the entry format. - - -Default link names -Any text selection is now used as the default name for links inserted into text fields. - - -Anchor links -Links to local named anchors in a node's HTML text content now work if they don't conflict with any node unique IDs. - - -Line spacing -Line spacing in output views was made more consistent. - - -Saved states -Improved the efficiency of restoring node open/close states when opening files. - - - -Bug Fixes - -Paste font sizes -Errors when pasting text with varying font sizes into data editors were fixed. - - -Conditional data types -Various issues with conditional data types were fixed, including problems with pasting conditional nodes and prompt updating when the types change. - - -Drag undo -Problems with undoing the dragging and dropping of a node were fixed. - - -Expand branches -Fix the extremely slow operation of the View &gt; Expand Full Branch and Collapse Full Branch commands. - - -Printed lines -Fix improperly placed branch lines in printed output. - - - - -October 22, 2013 - Release 1.9.2 (unstable development snapshot) - -New Features - -Spell check -Added a spell check tool. This requires either aspell, ispell or hunspell to be installed. - - -Numbering field type -Included a node numbering field type with several formatting options. An update numbering command fills in the sequence. - - -Add category level -Added a command to add a category level based on a subset of data fields. - - -Flatten by category -Added a flatten by category command to combine parent fields into child nodes. - - -Flatten by link -A new flatten by link command flattens the structure and provides internal links to the former parent nodes. - - -Arrange by link -An arrange by link command restores the structure based on parent internal links. - - - -Updates - -URL drag-and-drop -Allow file URL drag-and-drop on active external link data edit widgets. - - -Share directory -Change the Linux installer to use the 'share' directory in place of 'lib' for python files. - - -Windows binary -Update the Windows binary to use version 3.3 of Python. - - - -Bug Fixes - -Output line sequence -Fix out of sequence output lines when output formats are longer than ten lines. - - -Node indenting -Fix problems with unique IDs and internal links when indenting and unindenting nodes. - - -Widget focus problems -Avoid widget focus problems when editing data on conditional types. - - -Format cursor position -Maintain the output format cursor position when changing fields in the configure dialog. - - -XML import -Fix importing of generic XML documents that have nodes with no data. - - - - -May 2, 2013 - Release 1.9.1 (unstable development snapshot) - -Updates - -Compression and encryption -Added TreeLine file compression and file encryption, controlled from a File &gt; Properties dialog box. - - -Restore tree states -Tree node open/close states are restored for recent files. - - -Derived data types -Added derived data types that keep the field list of their generic type. - - -Conditional type setting -Added conditional type setting that changes icons or output format based on field contents. - - -Other field references -Other field references (file info, ancestors, children) can be used in node output formats and in print headers &amp; footers. - - -HTML file export -Added an HTML file export to a single file with a navigation pane on the side. - - -Embedded blank lines -Allow embedded blank lines in non-HTML node output formats. - - -Separator config option -Added an output separator config option for combination fields and child references. - - -First day of week option -Add an option to set the first day of the week for calendar widgets. - - - -Bug Fixes - -Drag multiple nodes -Fixed problems with pasting or dragging multiple nodes. - - -Remove fields from multi-line output formats -Made removing fields from multi-line output formats work properly. - - -Prevent duplicate unique IDs -Prevent duplicate unique IDs from being created after undoing the deletion of a branch. - - -Fixed non-text nodes sorting -Fixed node sorting of non-text nodes (numbers, dates, times, etc.) - - -Calendar widgets -Avoid placing calendar widgets partially off screen if near the bottom. - - -Automatic cell height -Limit the automatic height increases for text edit cells to avoid confusing double scroll bars. - - - - -February 6, 2013 - Release 1.9.0 (unstable development snapshot) - -New Features - -Full Python 3 rewrite -TreeLine has been fully rewritten using Python 3. - - -Direct model-view and ElementTree -Improved performance due to direct use of model-view classes for views and ElementTree for input/output. - - -Table based data editor pane -A table based data editor pane (much faster). - - -WYSIWYG data editor view. -WYSIWYG formatting in the data editor view. - - -Real-time window updates -Real-time updates of the same file shown in multiple windows. - - -Improved printing -Improved printing and print preview performance. - - -Saves print options -Saves print options with the TreeLine file. - - -Built-in PDF -Built-in print to PDF function. - - -Searching options. -More searching options. - - -Filtering command -A filtering command shows matches in a simple list. - - -Saved sorting parameters -Sorting parameters can be saved with each data type. - - -Basic text field type -The basic text field type allows formatting, preserves line breaks and allows HTML restricted characters. - - -Calendar widget -A calendar widget can be used for editing date fields. - - -Easy internal links -Internal link fields and inline internal links are easier to use. - - -Unified external links -An external link type supports http, https, file and mailto protocols. - - -Support relative paths -Relative paths are supported for external links and pictures. - - -Regular expression field type -A regular expression field type can match patterns (phone numbers, email addresses, etc.) - - -Node IDs automatically generated -Unique node IDs are automatically generated and updated. - - -New windows installer -A new windows installer allows a single-user, non-administrator install. - - -Portable installs -Includes better support for portable installs. - - - - - -Contacts - -Mailing list -There is a low-volume mailing list for users to discuss anything and everything about TreeLine. This is the place for development discussions (from roadmaps to feature suggestions to beta testing), release announcements, bug reports, and general user discussions (from new uses to tips &amp; tricks to configuration samples).<br /> -<br /> -To subscribe, go to <a href="https://lists.sourceforge.net/lists/listinfo/treeline-users">lists.sourceforge.net/lists/listinfo/treeline-users</a><br /> - - - -Email -If you do not wish to subscribe to the mailing list, I can be contacted by email at: doug101 AT bellz DOT org - - -Updates -I welcome any feedback, including reports of any bugs you find. Also, you can periodically check back to <a href="http://treeline.bellz.org">treeline.bellz.org</a> for any updates. - - - diff -Nru treeline-2.0.2/doc/documentation.trln treeline-3.0.1/doc/documentation.trln --- treeline-2.0.2/doc/documentation.trln 1970-01-01 00:00:00.000000000 +0000 +++ treeline-3.0.1/doc/documentation.trln 2018-09-28 01:30:07.000000000 +0000 @@ -0,0 +1,5072 @@ +{ +"formats": [ +{ +"bullets": true, +"fields": [ +{ +"fieldname": "Name", +"fieldtype": "Text" +}, +{ +"fieldname": "Text", +"fieldtype": "Text", +"lines": 2 +} +], +"formathtml": true, +"formatname": "BULLETS", +"icon": "bullet_1", +"outputlines": [ +"{*Text*}" +], +"spacebetween": false, +"titleline": "{*Name*}" +}, +{ +"childtype": "BULLETS", +"fields": [ +{ +"fieldname": "Name", +"fieldtype": "Text" +} +], +"formathtml": true, +"formatname": "BULLET_HEADING", +"outputlines": [ +"{*Name*}" +], +"spacebetween": false, +"titleline": "{*Name*}" +}, +{ +"fields": [ +{ +"fieldname": "Name", +"fieldtype": "Text" +} +], +"formathtml": true, +"formatname": "HEADINGS", +"outputlines": [ +"{*Name*}" +], +"titleline": "{*Name*}" +}, +{ +"fields": [ +{ +"fieldname": "Name", +"fieldtype": "Text" +}, +{ +"fieldname": "Text", +"fieldtype": "Text", +"lines": 8 +} +], +"formathtml": true, +"formatname": "HEAD_PARA", +"icon": "bullet_2", +"outputlines": [ +"{*Name*} - {*Text*}" +], +"titleline": "{*Name*}" +}, +{ +"fields": [ +{ +"fieldname": "Name", +"fieldtype": "Text" +}, +{ +"fieldname": "Text", +"fieldtype": "Text", +"lines": 8 +} +], +"formatname": "PARAGRAPH", +"icon": "bullet_2", +"outputlines": [ +"{*Text*}" +], +"titleline": "{*Name*}" +} +], +"nodes": [ +{ +"children": [], +"data": { +"Name": "Automatic clone creation", +"Text": "Cloned nodes can be created automatically from all identical nodes by using the \"Data > Clone All Matched Nodes\" command." +}, +"format": "BULLETS", +"uid": "04a868f4bb0611e7aea13417ebd53aeb" +}, +{ +"children": [], +"data": { +"Name": "Multiple top-level nodes", +"Text": "Multiple top-level (root) nodes are now permitted. When no nodes are selected in the tree (by clicking on a blank area or Ctrl clicking to unselect), the right-hand view will show information about all of the top-level nodes." +}, +"format": "BULLETS", +"uid": "04da8386ba4811e7b3de3417ebd53aeb" +}, +{ +"children": [], +"data": { +"Name": "Right view update", +"Text": "Properly update the right-hand views after using a new window command." +}, +"format": "BULLETS", +"uid": "145bd91a334c11e896b1d66a6ab671cb" +}, +{ +"children": [], +"data": { +"Name": "Avoid unfocused title edit", +"Text": "A click on a tree node to restore tree focus no longer starts editing the node title." +}, +"format": "BULLETS", +"uid": "15da7fde786a11e881c8a44cc8e97404" +}, +{ +"children": [], +"data": { +"Name": "Remove unique ID", +"Text": "To improve efficiency, user visible node unique IDs that depend on the node's data have been removed." +}, +"format": "BULLETS", +"uid": "1d015318bb0511e7a2ac3417ebd53aeb" +}, +{ +"children": [], +"data": { +"Name": "Reuse TreeLine windows", +"Text": "When a file is opened in an existing window, the window is now reused (not just replaced) to eliminate flashing." +}, +"format": "BULLETS", +"uid": "1e3a82e4bb0411e7b7f33417ebd53aeb" +}, +{ +"children": [], +"data": { +"Name": "Option to keep inaccessible recent files", +"Text": "A new general option controls whether inaccessible files are removed from the recent file list at startup. The files are removed by default, but this can be changed to avoid losing listings of files stored on removable drives." +}, +"format": "BULLETS", +"uid": "283565ba334211e8a34bd66a6ab671cb" +}, +{ +"children": [], +"data": { +"Name": "Breadcrumb View", +"Text": "The top pane shows the parent and ancestors of the selected node. It is blank if no nodes or multiple nodes are selected. Ancestors with blue text can be clicked to select those nodes." +}, +"format": "HEAD_PARA", +"uid": "28f8844ab58711e79d173417ebd53aeb" +}, +{ +"children": [], +"data": { +"Name": "Conditional type copy/paste & drag/drop", +"Text": "Fixed problems with copy / paste and drag / drop on nodes with conditional type settings." +}, +"format": "BULLETS", +"uid": "299ba9e4334a11e881a6d66a6ab671cb" +}, +{ +"children": [], +"data": { +"Name": "Open with bad references", +"Text": "TreeLine files with invalid child references can now be opened. A warning message is given about the missing child nodes in the corrupt file." +}, +"format": "BULLETS", +"uid": "2df5b030bbf711e88a1ca44cc8e97404" +}, +{ +"children": [], +"data": { +"Name": "Live HTML Export", +"Text": "A live tree HTML export creates an interactive view with expandable nodes and a descendant output pane." +}, +"format": "BULLETS", +"uid": "31304c06792b11e888cfa44cc8e97404" +}, +{ +"children": [], +"data": { +"Name": "Multiple paste commands", +"Text": "New paste commands have been added for adding nodes before and after siblings. There are also multiple paste commands for adding clones of nodes." +}, +"format": "BULLETS", +"uid": "39c001f0bb0311e79e793417ebd53aeb" +}, +{ +"children": [], +"data": { +"Name": "Major rewrite", +"Text": "This is a major rewrite of TreeLine. Once it becomes more stable, it will be released as TreeLine version 3.0.0. The 2.1.x unstable series is being discontinued (no stable 2.2.0 release is planned)." +}, +"format": "BULLETS", +"uid": "3d2bb12eba3c11e79b223417ebd53aeb" +}, +{ +"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." +}, +"format": "BULLETS", +"uid": "40ec6d5cbbf711e8bb93a44cc8e97404" +}, +{ +"children": [], +"data": { +"Name": "Title edit mouse move", +"Text": "Fixed node title editing errors caused by moving the mouse to a data edit box with the title edit still active." +}, +"format": "BULLETS", +"uid": "45491730786a11e8b8e2a44cc8e97404" +}, +{ +"children": [], +"data": { +"Name": "Enter key selects current node", +"Text": "The enter key now selects a tree node after the first title letter(s) are typed to make it current (shown with a box around the title)." +}, +"format": "BULLETS", +"uid": "456aa5ca793111e89a30a44cc8e97404" +}, +{ +"children": [], +"data": { +"Name": "Relative paths", +"Text": "Avoid issues resolving relative paths between different Windows drive letters in external link fields." +}, +"format": "BULLETS", +"uid": "46fc4054334b11e89299d66a6ab671cb" +}, +{ +"children": [], +"data": { +"Name": "Major rewrite", +"Text": "This is a major rewrite of TreeLine. Once it becomes fully stable, it will be released as TreeLine version 3.0.0." +}, +"format": "BULLETS", +"uid": "4bc47247786711e88b26a44cc8e97404" +}, +{ +"children": [ +"4bc47249786711e8a3d5a44cc8e97404", +"4bc4724c786711e8a8d9a44cc8e97404", +"4bc4724f786711e89bdca44cc8e97404" +], +"data": { +"Name": "June 30, 2018 - Release 2.9.2 (unstable development snapshot)" +}, +"format": "HEADINGS", +"uid": "4bc47248786711e8a8bfa44cc8e97404" +}, +{ +"children": [ +"4bc4724a786711e8a4dda44cc8e97404", +"4bc47247786711e88b26a44cc8e97404", +"4bc4724b786711e895e2a44cc8e97404" +], +"data": { +"Name": "Notes" +}, +"format": "BULLET_HEADING", +"uid": "4bc47249786711e8a3d5a44cc8e97404" +}, +{ +"children": [], +"data": { +"Name": "Development snapshot", +"Text": "This is a development snapshot of TreeLine that should be considered a release candidate for an upcoming stable release. Many bugs have been fixed. User testing and bug reports are appreciated." +}, +"format": "BULLETS", +"uid": "4bc4724a786711e8a4dda44cc8e97404" +}, +{ +"children": [], +"data": { +"Name": "Translations", +"Text": "A GUI translations is available in German. All other translations are out of date and have not been included. Volunteers are needed to update translations in several languages." +}, +"format": "BULLETS", +"uid": "4bc4724b786711e895e2a44cc8e97404" +}, +{ +"children": [ +"57c4de74786911e89c3fa44cc8e97404", +"71b708ac786911e88d64a44cc8e97404", +"a7d7f61e786911e89bbca44cc8e97404", +"15da7fde786a11e881c8a44cc8e97404", +"456aa5ca793111e89a30a44cc8e97404" +], +"data": { +"Name": "Updates" +}, +"format": "BULLET_HEADING", +"uid": "4bc4724c786711e8a8d9a44cc8e97404" +}, +{ +"children": [ +"e4fc680a786911e899d3a44cc8e97404", +"45491730786a11e8b8e2a44cc8e97404", +"f011a5d8786911e8ace3a44cc8e97404" +], +"data": { +"Name": "Bug Fixes" +}, +"format": "BULLET_HEADING", +"uid": "4bc4724f786711e89bdca44cc8e97404" +}, +{ +"children": [], +"data": { +"Name": "Clone removal", +"Text": "The new \"Data > Detach Clones\" command converts cloned nodes in selected branches back into independent nodes." +}, +"format": "BULLETS", +"uid": "5647bc8cbb0611e783853417ebd53aeb" +}, +{ +"children": [], +"data": { +"Name": "German translation", +"Text": "The German GUI translation was updated (thanks to Maria Seliger)." +}, +"format": "BULLETS", +"uid": "57c4de74786911e89c3fa44cc8e97404" +}, +{ +"children": [], +"data": { +"Name": "Linux menus", +"Text": "Added a desktop specification file to the Linux installer for desktop environment menu support." +}, +"format": "BULLETS", +"uid": "58dddee4010f11e88925d66a6ab671cb" +}, +{ +"children": [], +"data": { +"Name": "Setting types", +"Text": "Each tree node can be set to a type format independently." +}, +"format": "BULLETS", +"uid": "5c1d58ac792a11e8b78ca44cc8e97404" +}, +{ +"children": [], +"data": { +"Name": "Category level swap", +"Text": "A new \"Swap Category Levels\" command swaps child and grandchild nodes beneath a selected node. A child node with multiple nodes under it will become cloned under each one. Any existing grandchild clones will become individual nodes with multiple children." +}, +"format": "BULLETS", +"uid": "5c90e0b0bb0611e78e443417ebd53aeb" +}, +{ +"children": [], +"data": { +"Name": "Cloned nodes", +"Text": "Cloned nodes (the same nodes with multiple parents/locations) can be created using special paste commands or by automatically matching identical nodes." +}, +"format": "BULLETS", +"uid": "5f12f768b32c11e7a41a3417ebd53aeb" +}, +{ +"children": [], +"data": { +"Name": "TreeLine Export", +"Text": "Files can be exported that are compatible with older versions of TreeLine (1.x and 2.x), with a \".trl\" file extension. Some newer features, such as cloned nodes and multiple root nodes may not be completely preserved. TreeLine subtrees can also be exported to save just selected branches of the tree to a file using the current TreeLine version." +}, +"format": "HEAD_PARA", +"uid": "65289b36b97611e783eb3417ebd53aeb" +}, +{ +"children": [], +"data": { +"Name": "Custom time dialog", +"Text": "Added a custom time select dialog box for editing Time and TimeDate fields." +}, +"format": "BULLETS", +"uid": "653f2f90334c11e8a7c9d66a6ab671cb" +}, +{ +"children": [ +"67e95a5fbbf611e8b8bda44cc8e97404", +"67e95a60bbf611e8a80ca44cc8e97404" +], +"data": { +"Name": "September 29, 2018 - Release 3.0.1 (stable release)" +}, +"format": "HEADINGS", +"uid": "67e95a5bbbf611e891f7a44cc8e97404" +}, +{ +"children": [ +"f966db0cc23f11e8b80fd66a6ab671cb", +"67e95a61bbf611e8989ca44cc8e97404" +], +"data": { +"Name": "Updates" +}, +"format": "BULLET_HEADING", +"uid": "67e95a5fbbf611e8b8bda44cc8e97404" +}, +{ +"children": [ +"67e95a64bbf611e8960fa44cc8e97404", +"2df5b030bbf711e88a1ca44cc8e97404", +"dc700fb4c24211e8b88dd66a6ab671cb", +"40ec6d5cbbf711e8bb93a44cc8e97404", +"9550a81ac24111e89029d66a6ab671cb", +"71e2f25cc24211e88524d66a6ab671cb", +"a757f64ac24211e8a95fd66a6ab671cb", +"bdc8628ec2bd11e889e57054d2175f18" +], +"data": { +"Name": "Bug Fixes" +}, +"format": "BULLET_HEADING", +"uid": "67e95a60bbf611e8a80ca44cc8e97404" +}, +{ +"children": [], +"data": { +"Name": "About dialog text box", +"Text": "Added a read-only text box to the Help > About dialog to allow copying of the version and library information." +}, +"format": "BULLETS", +"uid": "67e95a61bbf611e8989ca44cc8e97404" +}, +{ +"children": [], +"data": { +"Name": "Old file import", +"Text": "Fixed a problem opening old TreeLine files that had format changes in File Info fields." +}, +"format": "BULLETS", +"uid": "67e95a64bbf611e8960fa44cc8e97404" +}, +{ +"children": [], +"data": { +"Name": "Child type limits", +"Text": "Child node types that are available to be set can be limited. A new pull-down list of child type limits is in the \"Type Config\" tab of the configuration dialog if the advanced functions are shown." +}, +"format": "BULLETS", +"uid": "6dc88226bb0711e7894d3417ebd53aeb" +}, +{ +"children": [ +"6df9a0c6ba3b11e7925d3417ebd53aeb", +"6df9a0c9ba3b11e7a9793417ebd53aeb" +], +"data": { +"Name": "January 28, 2018 - Release 2.9.0 (unstable development snapshot)" +}, +"format": "HEADINGS", +"uid": "6df9a0c5ba3b11e78e7e3417ebd53aeb" +}, +{ +"children": [ +"6df9a0c7ba3b11e7b7bd3417ebd53aeb", +"3d2bb12eba3c11e79b223417ebd53aeb", +"6df9a0c8ba3b11e78d783417ebd53aeb" +], +"data": { +"Name": "Notes" +}, +"format": "BULLET_HEADING", +"uid": "6df9a0c6ba3b11e7925d3417ebd53aeb" +}, +{ +"children": [], +"data": { +"Name": "Unstable snapshot", +"Text": "This is an unstable development snapshot of TreeLine. It could contain bugs. Testing and bug reports are appreciated, but the stable release (TreeLine 2.0.x) is recommended for critical work." +}, +"format": "BULLETS", +"uid": "6df9a0c7ba3b11e7b7bd3417ebd53aeb" +}, +{ +"children": [], +"data": { +"Name": "Translations", +"Text": "The GUI and documentation translations are out of date and have not been included. Volunteers are needed to update translations in several languages." +}, +"format": "BULLETS", +"uid": "6df9a0c8ba3b11e78d783417ebd53aeb" +}, +{ +"children": [ +"6df9a0caba3b11e78b283417ebd53aeb", +"d1d8e56eba4011e784ab3417ebd53aeb", +"04da8386ba4811e7b3de3417ebd53aeb", +"e6ad9f6eba4311e796bd3417ebd53aeb", +"cd28e024bb0211e79c553417ebd53aeb", +"1e3a82e4bb0411e7b7f33417ebd53aeb", +"98132868ba4511e7b7283417ebd53aeb", +"39c001f0bb0311e79e793417ebd53aeb", +"04a868f4bb0611e7aea13417ebd53aeb", +"5647bc8cbb0611e783853417ebd53aeb", +"5c90e0b0bb0611e78e443417ebd53aeb", +"6dc88226bb0711e7894d3417ebd53aeb", +"7697b826bb0411e788433417ebd53aeb", +"1d015318bb0511e7a2ac3417ebd53aeb", +"f5983b38bb0411e795e13417ebd53aeb", +"58dddee4010f11e88925d66a6ab671cb" +], +"data": { +"Name": "New Features" +}, +"format": "BULLET_HEADING", +"uid": "6df9a0c9ba3b11e7a9793417ebd53aeb" +}, +{ +"children": [], +"data": { +"Name": "New file format", +"Text": "TreeLine files now uses a new JSON format in place of the old XML format. This provides more flexibility for structuring new features like cloned nodes and multiple root nodes. A new file extension (\".trln) helps to distinguish these files." +}, +"format": "BULLETS", +"uid": "6df9a0caba3b11e78b283417ebd53aeb" +}, +{ +"children": [], +"data": { +"Name": "Maintain expanded nodes", +"Text": "The expand/collapse status of nearby nodes is maintained while editing the node structure. This includes adding, deleting, moving and indenting nodes." +}, +"format": "BULLETS", +"uid": "71b708ac786911e88d64a44cc8e97404" +}, +{ +"children": [], +"data": { +"Name": "Complex data menu commands", +"Text": "Fixed various errors when applying advanced clone and category commands from the Data menu to complex node structures." +}, +"format": "BULLETS", +"uid": "71e2f25cc24211e88524d66a6ab671cb" +}, +{ +"children": [], +"data": { +"Name": "Date and time formats", +"Text": "The Date, Time and DateTime fields have new format strings that enable extra text to be added to the date output." +}, +"format": "BULLETS", +"uid": "7697b826bb0411e788433417ebd53aeb" +}, +{ +"children": [], +"data": { +"Name": "Number field formatting", +"Text": "Fixed problems with number field formatting when using a \",\" radix with exponents." +}, +"format": "BULLETS", +"uid": "7716c96e334a11e896bfd66a6ab671cb" +}, +{ +"children": [], +"data": { +"Name": "Translations", +"Text": "Translations of the TreeLine GUI are available in German and Spanish. The translation should load automatically if the OS locale is properly set. Alternatively, \"--lang xx\" can be appended to the command that starts TreeLine, where \"xx\" is the two-letter language code (\"en\", \"de\" or \"es\")." +}, +"format": "HEAD_PARA", +"uid": "7e9b3b88c24511e896c7d66a6ab671cb" +}, +{ +"children": [], +"data": { +"Name": "Copy command errors", +"Text": "Fix errors shown when using copy commands after closing TreeLine windows." +}, +"format": "BULLETS", +"uid": "90dbc4e4334b11e892d0d66a6ab671cb" +}, +{ +"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." +}, +"format": "BULLETS", +"uid": "9550a81ac24111e89029d66a6ab671cb" +}, +{ +"children": [], +"data": { +"Name": "Old TreeLine Import", +"Text": "Files from older versions of TreeLine (1.x or 2.x) can be imported. These files are in XML format and generally have a \".trl\" file extension (current files are in JSON format with a \".trln\" file extension). Unlike other file imports, these older files will import without showing the import dialog box when using the \"File > Open\" command." +}, +"format": "HEAD_PARA", +"uid": "95547c38b97411e7a42a3417ebd53aeb" +}, +{ +"children": [], +"data": { +"Name": "Major rewrite", +"Text": "This is a major rewrite of TreeLine. Once it becomes more stable, it will be released as TreeLine version 3.0.0. The 2.1.x unstable series is being discontinued (no stable 2.2.0 release is planned)." +}, +"format": "BULLETS", +"uid": "95a6e905333f11e886edd66a6ab671cb" +}, +{ +"children": [ +"95a6e90b333f11e8976fd66a6ab671cb", +"95a6e90e333f11e88696d66a6ab671cb", +"b16866da333f11e88adbd66a6ab671cb" +], +"data": { +"Name": "March 31, 2018 - Release 2.9.1 (unstable development snapshot)" +}, +"format": "HEADINGS", +"uid": "95a6e90a333f11e89efed66a6ab671cb" +}, +{ +"children": [ +"95a6e90c333f11e8b766d66a6ab671cb", +"95a6e905333f11e886edd66a6ab671cb", +"95a6e90d333f11e8b88ed66a6ab671cb" +], +"data": { +"Name": "Notes" +}, +"format": "BULLET_HEADING", +"uid": "95a6e90b333f11e8976fd66a6ab671cb" +}, +{ +"children": [], +"data": { +"Name": "Unstable snapshot", +"Text": "This is an unstable development snapshot of TreeLine. It has improved but could still contain bugs. Testing and bug reports are appreciated, but those with very critical work should consider using the stable release (TreeLine 2.0.x)." +}, +"format": "BULLETS", +"uid": "95a6e90c333f11e8b766d66a6ab671cb" +}, +{ +"children": [], +"data": { +"Name": "Translations", +"Text": "The GUI and documentation translations are out of date and have not been included. Volunteers are needed to update translations in several languages." +}, +"format": "BULLETS", +"uid": "95a6e90d333f11e8b88ed66a6ab671cb" +}, +{ +"children": [ +"b1cd1f82334111e8b89ad66a6ab671cb", +"dd22e382334111e894e6d66a6ab671cb", +"653f2f90334c11e8a7c9d66a6ab671cb", +"283565ba334211e8a34bd66a6ab671cb" +], +"data": { +"Name": "New Features" +}, +"format": "BULLET_HEADING", +"uid": "95a6e90e333f11e88696d66a6ab671cb" +}, +{ +"children": [], +"data": { +"Name": "Native cloned nodes", +"Text": "Cloned nodes now have a more native and efficient implementation. This allows the same nodes to have multiple parents. The new breadcrumb view shows a list of clones for the selected node that can be clicked to select the other instances." +}, +"format": "BULLETS", +"uid": "98132868ba4511e7b7283417ebd53aeb" +}, +{ +"children": [], +"data": { +"Name": "Format Menu", +"Text": "The format menu has text formatting commands that are active when using edit boxes in the Data Edit view." +}, +"format": "HEAD_PARA", +"uid": "997af664b58811e7ac243417ebd53aeb" +}, +{ +"children": [], +"data": { +"Name": "Ancestor field references", +"Text": "Fixed problems with advanced ancestor field output references in some tree locations." +}, +"format": "BULLETS", +"uid": "9a8a1400334c11e89838d66a6ab671cb" +}, +{ +"children": [], +"data": { +"Name": "Old TreeLine import/export", +"Text": "Files from older versions of TreeLine (2.x and 1.x) can be imported and exported." +}, +"format": "BULLETS", +"uid": "9bd80f34b32b11e7be613417ebd53aeb" +}, +{ +"children": [], +"data": { +"Name": "Complex undo operations", +"Text": "Fixed some problems with using the Undo command after complex operations." +}, +"format": "BULLETS", +"uid": "a757f64ac24211e8a95fd66a6ab671cb" +}, +{ +"children": [], +"data": { +"Name": "Save state for discarded files", +"Text": "The expanded/collapsed and selected tree states are now saved even if changes were discarded in the previous session." +}, +"format": "BULLETS", +"uid": "a7d7f61e786911e89bbca44cc8e97404" +}, +{ +"children": [], +"data": { +"Name": "Pull-downs on repeated dialog boxes", +"Text": "Fixed issues on Windows with pull-down combo boxes not working after repeatedly showing some dialogs (mostly find and filter dialogs)." +}, +"format": "BULLETS", +"uid": "a7fa190c334911e88d18d66a6ab671cb" +}, +{ +"children": [], +"data": { +"Name": "Child Type Limits", +"Text": "In addition to setting the default child type, the child node types that are available to be set can be limited. There is a pull-down list of child type limits in the \"Type Config\" tab of the configuration dialog if the advanced functions are shown. Only checked types will show up in the type lists when using the \"Data > Set Node Type\" command on a child node." +}, +"format": "HEAD_PARA", +"uid": "aa6f6ca4bb0711e7bbf43417ebd53aeb" +}, +{ +"children": [ +"90dbc4e4334b11e892d0d66a6ab671cb", +"299ba9e4334a11e881a6d66a6ab671cb", +"e46163b4334b11e88512d66a6ab671cb", +"a7fa190c334911e88d18d66a6ab671cb", +"f817e5b6334a11e89349d66a6ab671cb", +"7716c96e334a11e896bfd66a6ab671cb", +"46fc4054334b11e89299d66a6ab671cb", +"9a8a1400334c11e89838d66a6ab671cb", +"145bd91a334c11e896b1d66a6ab671cb", +"edd50b80334c11e8b5f5d66a6ab671cb", +"b7ec7052334a11e88325d66a6ab671cb" +], +"data": { +"Name": "Bug Fixes" +}, +"format": "BULLET_HEADING", +"uid": "b16866da333f11e88adbd66a6ab671cb" +}, +{ +"children": [], +"data": { +"Name": "Breadcrumb view", +"Text": "An upper breadcrumb view shows titles of the selected node's ancestors; the titles can be clicked to select them in the tree." +}, +"format": "BULLETS", +"uid": "b1b5d434b32911e780fd3417ebd53aeb" +}, +{ +"children": [], +"data": { +"Name": "Live tree HTML export", +"Text": "A new HTML export uses Javascript and CSS to create a live tree view. Nodes in the tree can be expanded and collapsed, and a separate pane shows the output for all descendants of the selected node. One form of this export creates separate files that are linked to the existing TreeLine file. These files are intended for use on a web server (browser security usually prevents local access). Only the TreeLine file needs to be replaced to update the data (re-export is not required). The second form creates a single file (with embedded data) that can be accessed from a local web browser." +}, +"format": "BULLETS", +"uid": "b1cd1f82334111e8b89ad66a6ab671cb" +}, +{ +"children": [], +"data": { +"Name": "Output table spacing", +"Text": "Fixed minor spacing issues for tables shown in output views and HTML exports." +}, +"format": "BULLETS", +"uid": "b7ec7052334a11e88325d66a6ab671cb" +}, +{ +"children": [], +"data": { +"Name": "Math Fields", +"Text": "For more complex output formatting, the math field can be used to combine other fields using various mathematical, relational and string operators. See the Math Type section for details." +}, +"format": "HEAD_PARA", +"uid": "bbbf735ac24711e8b560d66a6ab671cb" +}, +{ +"children": [], +"data": { +"Name": "Cut last node", +"Text": "Fix an error caused by attempting to use the Edit > Cut command when there is only one node." +}, +"format": "BULLETS", +"uid": "bdc8628ec2bd11e889e57054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Data editor hover", +"Text": "By default, the edit boxes in the data edit view now activate when the mouse hovers over them. This eliminates the need to click to activate them. This feature can be disabled in general options if desired." +}, +"format": "BULLETS", +"uid": "cd28e024bb0211e79c553417ebd53aeb" +}, +{ +"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 > Open\" command." +}, +"format": "BULLETS", +"uid": "d1d8e56eba4011e784ab3417ebd53aeb" +}, +{ +"children": [], +"data": { +"Name": "Sort root nodes", +"Text": "The Sort command now properly sorts multiple root nodes when sorting the entire tree." +}, +"format": "BULLETS", +"uid": "dc700fb4c24211e8b88dd66a6ab671cb" +}, +{ +"children": [], +"data": { +"Name": "Multi-level CSV import / export", +"Text": "New forms of CSV table text imports and exports can work with multiple levels of child nodes, preserving the tree structure. The first column of the table includes a level number that is incremented for child relationships." +}, +"format": "BULLETS", +"uid": "dd22e382334111e894e6d66a6ab671cb" +}, +{ +"children": [], +"data": { +"Name": "Error after empty filter", +"Text": "Avoid problems showing the tree view after closing a filter command that found no matches." +}, +"format": "BULLETS", +"uid": "e46163b4334b11e88512d66a6ab671cb" +}, +{ +"children": [], +"data": { +"Name": "Deletion errors", +"Text": "Several errors occurring when deleting or removing nodes have been fixed. These include problems when the mouse is pre-highlighting descendant nodes and when nodes to be deleted are selected in a separate window." +}, +"format": "BULLETS", +"uid": "e4fc680a786911e899d3a44cc8e97404" +}, +{ +"children": [], +"data": { +"Name": "Breadcrumb view", +"Text": "A new breadcrumb view pane above the other panes shows a sequence of ancestor titles for a selected node. The ancestors can be clicked to select them. For cloned nodes, multiple lines provide links to each instance's ancestor sequence." +}, +"format": "BULLETS", +"uid": "e6ad9f6eba4311e796bd3417ebd53aeb" +}, +{ +"children": [], +"data": { +"Name": "Windows binary", +"Text": "The Windows binary is now built using updated libraries (Qt 5.11 and PyQt 5.11)." +}, +"format": "BULLETS", +"uid": "e7776f4da14511e88aefa44cc8e97404" +}, +{ +"children": [], +"data": { +"Name": "Major rewrite", +"Text": "This is the first stable release of a major TreeLine rewrite. It includes all of the new features shown below in the 2.1.x and 2.9.x development series." +}, +"format": "BULLETS", +"uid": "e8e10d32a14311e8ab90a44cc8e97404" +}, +{ +"children": [ +"e8e10d34a14311e899cca44cc8e97404", +"e8e10d38a14311e8a0c3a44cc8e97404", +"e8e10d37a14311e8b0b3a44cc8e97404" +], +"data": { +"Name": "August 19, 2018 - Release 3.0.0 (new stable release)" +}, +"format": "HEADINGS", +"uid": "e8e10d33a14311e88e24a44cc8e97404" +}, +{ +"children": [ +"e8e10d32a14311e8ab90a44cc8e97404", +"e8e10d35a14311e8aca9a44cc8e97404", +"e8e10d36a14311e8b832a44cc8e97404" +], +"data": { +"Name": "Notes" +}, +"format": "BULLET_HEADING", +"uid": "e8e10d34a14311e899cca44cc8e97404" +}, +{ +"children": [], +"data": { +"Name": "Compatibility", +"Text": "TreeLine files now use a JSON format in place of the old XML format. This provides more flexibility for structuring new features like cloned nodes and multiple root nodes. A new file extension (\".trln) helps to distinguish files with the new format. 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 > Open\" command." +}, +"format": "BULLETS", +"uid": "e8e10d35a14311e8aca9a44cc8e97404" +}, +{ +"children": [], +"data": { +"Name": "Translations", +"Text": "A GUI translations is available in German. All other translations are out of date and have not been included. Volunteers are needed to update translations in several languages." +}, +"format": "BULLETS", +"uid": "e8e10d36a14311e8b832a44cc8e97404" +}, +{ +"children": [ +"e7776f4da14511e88aefa44cc8e97404" +], +"data": { +"Name": "Updates" +}, +"format": "BULLET_HEADING", +"uid": "e8e10d37a14311e8b0b3a44cc8e97404" +}, +{ +"children": [ +"e8e10d3ca14311e8b71da44cc8e97404" +], +"data": { +"Name": "New Features" +}, +"format": "BULLET_HEADING", +"uid": "e8e10d38a14311e8a0c3a44cc8e97404" +}, +{ +"children": [], +"data": { +"Name": "Default font", +"Text": "The default font for the application can now be set in Tools > Customize Fonts. This is useful for enlarging fonts on some systems with high screen resolutions. Options still exist to individually specify tree view, output view and editor fonts." +}, +"format": "BULLETS", +"uid": "e8e10d3ca14311e8b71da44cc8e97404" +}, +{ +"children": [], +"data": { +"Name": "Category command disable", +"Text": "Avoid problems running data category-based commands on selections without any child nodes." +}, +"format": "BULLETS", +"uid": "edd50b80334c11e8b5f5d66a6ab671cb" +}, +{ +"children": [], +"data": { +"Name": "Initial field values", +"Text": "Fixed errors occurring when field types are changed after initial default values have been set." +}, +"format": "BULLETS", +"uid": "f011a5d8786911e8ace3a44cc8e97404" +}, +{ +"children": [], +"data": { +"Name": "Cloned nodes", +"Text": "Nodes that are duplicated by cloning show up as separate clickable paths in the upper breadcrumb view." +}, +"format": "BULLETS", +"uid": "f08b97be792b11e88588a44cc8e97404" +}, +{ +"children": [ +"f16f7f70a25a11e7b7c67054d2175f18", +"f16f87b8a25a11e7b7c67054d2175f18", +"f16fbef4a25a11e7b7c67054d2175f18", +"f16fc7aaa25a11e7b7c67054d2175f18", +"f16fd236a25a11e7b7c67054d2175f18", +"f16fe6fea25a11e7b7c67054d2175f18", +"f1703d8ea25a11e7b7c67054d2175f18", +"f1713464a25a11e7b7c67054d2175f18" +], +"data": { +"Name": "TreeLine Documentation" +}, +"format": "HEADINGS", +"uid": "f16f7d90a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f16f82aea25a11e7b7c67054d2175f18", +"f16f8498a25a11e7b7c67054d2175f18", +"f16f85b0a25a11e7b7c67054d2175f18", +"f16f86aaa25a11e7b7c67054d2175f18" +], +"data": { +"Name": "Introduction" +}, +"format": "HEADINGS", +"uid": "f16f7f70a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Version", +"Text": "This document covers TreeLine, Version 3.0.1, a stable release, dated September 29, 2018 by Doug Bell." +}, +"format": "PARAGRAPH", +"uid": "f16f82aea25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Why TreeLine?", +"Text": "Do you have lots of sticky notes lying around with various useful information jotted down? Or many lists of books, movies, links, website logins, personal contacts, or things to do? Can you find them when you need them? Well, I often couldn't. So here's my answer." +}, +"format": "PARAGRAPH", +"uid": "f16f8498a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Outliner vs. database", +"Text": "TreeLine is both an outliner and a small database. It stores almost any kind of information. A tree structure makes it easy to keep things organized. And each node in the tree can contain several fields, forming a database. The output format for each node can be defined, and the output can be shown on the screen, printed, or exported to HTML or text." +}, +"format": "PARAGRAPH", +"uid": "f16f85b0a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "License", +"Text": "Since I'm not in the software business, I'm making this program free for anyone to use, distribute and modify, as long as it stays non-proprietary. TreeLine is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either Version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY. See the LICENSE file provided with this program for more information." +}, +"format": "PARAGRAPH", +"uid": "f16f86aaa25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f16f8aeca25a11e7b7c67054d2175f18", +"f16f9654a25a11e7b7c67054d2175f18", +"f16f9c44a25a11e7b7c67054d2175f18", +"f16fa040a25a11e7b7c67054d2175f18", +"f16fa31aa25a11e7b7c67054d2175f18", +"f16fa806a25a11e7b7c67054d2175f18", +"f16fadf6a25a11e7b7c67054d2175f18", +"f16fb1dea25a11e7b7c67054d2175f18", +"f16fb90ea25a11e7b7c67054d2175f18" +], +"data": { +"Name": "Features" +}, +"format": "HEADINGS", +"uid": "f16f87b8a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f16f9050a25a11e7b7c67054d2175f18", +"f16f921ca25a11e7b7c67054d2175f18", +"f16f932aa25a11e7b7c67054d2175f18", +"f16f9424a25a11e7b7c67054d2175f18", +"f16f9550a25a11e7b7c67054d2175f18" +], +"data": { +"Name": "General" +}, +"format": "BULLET_HEADING", +"uid": "f16f8aeca25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Store information", +"Text": "Stores almost any type of information, including plain text, rich text, HTML, numbers, dates, times, booleans, URLs, etc." +}, +"format": "BULLETS", +"uid": "f16f9050a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Tree structure", +"Text": "The tree structure helps keep things organized." +}, +"format": "BULLETS", +"uid": "f16f921ca25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Fields", +"Text": "Each node can have several fields that form a mini-database." +}, +"format": "BULLETS", +"uid": "f16f932aa25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Node types", +"Text": "Several node types, with different sets of fields, can be included in one file." +}, +"format": "BULLETS", +"uid": "f16f9424a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Output format", +"Text": "The node format, including fields, output lines, formatting and tree-view icon, can be defined for each node type." +}, +"format": "BULLETS", +"uid": "f16f9550a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f16f9744a25a11e7b7c67054d2175f18", +"f16f9834a25a11e7b7c67054d2175f18", +"f16f992ea25a11e7b7c67054d2175f18", +"f16f9a32a25a11e7b7c67054d2175f18", +"f16f9b36a25a11e7b7c67054d2175f18", +"b1b5d434b32911e780fd3417ebd53aeb" +], +"data": { +"Name": "Views" +}, +"format": "BULLET_HEADING", +"uid": "f16f9654a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Tree view", +"Text": "The left-hand view shows an indented, expandable list of titles" +}, +"format": "BULLETS", +"uid": "f16f9744a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Right-hand views", +"Text": "The right-hand view can show one of three views - for showing output, editing node data and editing node titles." +}, +"format": "BULLETS", +"uid": "f16f9834a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Show parent and children", +"Text": "The right-hand view is normally split to show data from the parent node and its children." +}, +"format": "BULLETS", +"uid": "f16f992ea25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Show multiple selection", +"Text": "If multiple nodes are selected, the right-hand view shows all of their data." +}, +"format": "BULLETS", +"uid": "f16f9a32a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Show descendant output", +"Text": "The output view can be set to show indented output from all descendant nodes." +}, +"format": "BULLETS", +"uid": "f16f9b36a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f16f9d2aa25a11e7b7c67054d2175f18", +"f16f9e2ea25a11e7b7c67054d2175f18", +"f16f9f3ca25a11e7b7c67054d2175f18" +], +"data": { +"Name": "Navigation" +}, +"format": "BULLET_HEADING", +"uid": "f16f9c44a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Find commands", +"Text": "Find commands can search node data for text matches or for more specific rules." +}, +"format": "BULLETS", +"uid": "f16f9d2aa25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Filtering", +"Text": "Filtering commands show only matching nodes in a flat left-hand view." +}, +"format": "BULLETS", +"uid": "f16f9e2ea25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Toggle selections", +"Text": "Previous and next selection commands toggle selections to quickly move between parts of the tree." +}, +"format": "BULLETS", +"uid": "f16f9f3ca25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f16fa130a25a11e7b7c67054d2175f18", +"5c1d58ac792a11e8b78ca44cc8e97404", +"f16fa220a25a11e7b7c67054d2175f18" +], +"data": { +"Name": "Formatting" +}, +"format": "BULLET_HEADING", +"uid": "f16fa040a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Configuration dialog", +"Text": "The dialog for data type configuration has several tabs to easily set all type, field and output parameters." +}, +"format": "BULLETS", +"uid": "f16fa130a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Format copies", +"Text": "Formatting information can be copied from another TreeLine file." +}, +"format": "BULLETS", +"uid": "f16fa220a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f16fa40aa25a11e7b7c67054d2175f18", +"f16fa504a25a11e7b7c67054d2175f18", +"f16fa5fea25a11e7b7c67054d2175f18", +"f16fa70ca25a11e7b7c67054d2175f18" +], +"data": { +"Name": "File Handling" +}, +"format": "BULLET_HEADING", +"uid": "f16fa31aa25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Undo/redo", +"Text": "Undo and redo commands are available for all modifying operations." +}, +"format": "BULLETS", +"uid": "f16fa40aa25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "File formats", +"Text": "TreeLine files use a JSON format, with options for automatically compressing or encrypting the files." +}, +"format": "BULLETS", +"uid": "f16fa504a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Document templates", +"Text": "Document templates for new files are preformatted to cover basic needs." +}, +"format": "BULLETS", +"uid": "f16fa5fea25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Printing", +"Text": "The formatted output can be printed with parent/child lines, headers and footers." +}, +"format": "BULLETS", +"uid": "f16fa70ca25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f16fa8f6a25a11e7b7c67054d2175f18", +"31304c06792b11e888cfa44cc8e97404", +"f16fa9f0a25a11e7b7c67054d2175f18", +"9bd80f34b32b11e7be613417ebd53aeb", +"f16faaf4a25a11e7b7c67054d2175f18", +"f16fabf8a25a11e7b7c67054d2175f18", +"f16facf2a25a11e7b7c67054d2175f18" +], +"data": { +"Name": "File Import and Export" +}, +"format": "BULLET_HEADING", +"uid": "f16fa806a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "HTML export", +"Text": "The data can be exported to single or multiple HTML files with optional navigation panes." +}, +"format": "BULLETS", +"uid": "f16fa8f6a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Text import/export", +"Text": "Plain text, tab-indented text and delimited table files can be imported and exported." +}, +"format": "BULLETS", +"uid": "f16fa9f0a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Bookmark import/export", +"Text": "Mozilla and XBEL format bookmark files can be imported and exported." +}, +"format": "BULLETS", +"uid": "f16faaf4a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Generic XML import/export", +"Text": "Generic XML files can be imported and exported, allowing TreeLine to function as a crude XML editor." +}, +"format": "BULLETS", +"uid": "f16fabf8a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "ODF import/export", +"Text": "ODF text documents can be imported and exported as outlines." +}, +"format": "BULLETS", +"uid": "f16facf2a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f16faee6a25a11e7b7c67054d2175f18", +"f16fafe0a25a11e7b7c67054d2175f18", +"f16fb0daa25a11e7b7c67054d2175f18", +"f08b97be792b11e88588a44cc8e97404" +], +"data": { +"Name": "Linking" +}, +"format": "BULLET_HEADING", +"uid": "f16fadf6a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Internal links", +"Text": "Internal link fields select a linked node when clicked." +}, +"format": "BULLETS", +"uid": "f16faee6a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "External links", +"Text": "External link fields can be used to open URLs in web browsers." +}, +"format": "BULLETS", +"uid": "f16fafe0a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Embedded links", +"Text": "Both internal and external links can be embedded into text fields." +}, +"format": "BULLETS", +"uid": "f16fb0daa25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f16fb2cea25a11e7b7c67054d2175f18", +"f16fb3c8a25a11e7b7c67054d2175f18", +"f16fb4eaa25a11e7b7c67054d2175f18", +"f16fb5e4a25a11e7b7c67054d2175f18", +"5f12f768b32c11e7a41a3417ebd53aeb", +"f16fb6dea25a11e7b7c67054d2175f18", +"f16fb7f6a25a11e7b7c67054d2175f18" +], +"data": { +"Name": "Data Manipulation" +}, +"format": "BULLET_HEADING", +"uid": "f16fb1dea25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Sorting", +"Text": "Nodes can be sorted by title or by predefined key fields." +}, +"format": "BULLETS", +"uid": "f16fb2cea25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Math Fields", +"Text": "Math fields can be defined that automatically calculate their contents based on numerical values in other nodes." +}, +"format": "BULLETS", +"uid": "f16fb3c8a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Numbering", +"Text": "Numbering fields can be defined and automatically updated." +}, +"format": "BULLETS", +"uid": "f16fb4eaa25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Conditional types", +"Text": "A node's icon and output format can be changed conditionally based on its data." +}, +"format": "BULLETS", +"uid": "f16fb5e4a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Spell check", +"Text": "Text data can be spell checked (requires an external program - see the System Requirements section)." +}, +"format": "BULLETS", +"uid": "f16fb6dea25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Arranging data", +"Text": "Data can be automatically re-arranged using categories from data fields." +}, +"format": "BULLETS", +"uid": "f16fb7f6a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f16fba08a25a11e7b7c67054d2175f18", +"f16fbb02a25a11e7b7c67054d2175f18", +"f16fbc06a25a11e7b7c67054d2175f18", +"f16fbd00a25a11e7b7c67054d2175f18" +], +"data": { +"Name": "Customization" +}, +"format": "BULLET_HEADING", +"uid": "f16fb90ea25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Customization", +"Text": "There are many options for customizing both general and file-based attributes." +}, +"format": "BULLETS", +"uid": "f16fba08a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Shortcuts and toolbars", +"Text": "There are editors for keyboard shortcuts and toolbar commands." +}, +"format": "BULLETS", +"uid": "f16fbb02a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Fonts", +"Text": "Fonts used in the GUI, editors and output views can be customized." +}, +"format": "BULLETS", +"uid": "f16fbc06a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Languages", +"Text": "The user interface is available in English, German and Spanish. Translations into other languages are TBD." +}, +"format": "BULLETS", +"uid": "f16fbd00a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f16fbfeea25a11e7b7c67054d2175f18", +"f16fc4bca25a11e7b7c67054d2175f18" +], +"data": { +"Name": "System Requirements" +}, +"format": "HEADINGS", +"uid": "f16fbef4a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f16fc0d4a25a11e7b7c67054d2175f18", +"f16fc1cea25a11e7b7c67054d2175f18", +"f16fc2c8a25a11e7b7c67054d2175f18", +"f16fc3c2a25a11e7b7c67054d2175f18" +], +"data": { +"Name": "Linux" +}, +"format": "BULLET_HEADING", +"uid": "f16fbfeea25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Python", +"Text": "Python (Version 3.5 or higher)" +}, +"format": "BULLETS", +"uid": "f16fc0d4a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Qt", +"Text": "QT (Version 5.8 or higher)" +}, +"format": "BULLETS", +"uid": "f16fc1cea25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "PyQt", +"Text": "PyQt (Version 5.8 or higher)" +}, +"format": "BULLETS", +"uid": "f16fc2c8a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Spell check", +"Text": "If spell checking is desired, either aspell, ispell or hunspell are required" +}, +"format": "BULLETS", +"uid": "f16fc3c2a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f16fc5aca25a11e7b7c67054d2175f18", +"f16fc6b0a25a11e7b7c67054d2175f18" +], +"data": { +"Name": "Windows" +}, +"format": "BULLET_HEADING", +"uid": "f16fc4bca25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Binary", +"Text": "Should run on any computer running Windows Vista, 7, 8 or 10." +}, +"format": "BULLETS", +"uid": "f16fc5aca25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Spell check", +"Text": "If spell checking is desired, an external program is required. Either aspell, ispell or hunspell must be installed." +}, +"format": "BULLETS", +"uid": "f16fc6b0a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f16fc89aa25a11e7b7c67054d2175f18", +"f16fcc96a25a11e7b7c67054d2175f18" +], +"data": { +"Name": "Installation" +}, +"format": "HEADINGS", +"uid": "f16fc7aaa25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f16fc98aa25a11e7b7c67054d2175f18", +"f16fcaa2a25a11e7b7c67054d2175f18", +"f16fcb9ca25a11e7b7c67054d2175f18" +], +"data": { +"Name": "Linux" +}, +"format": "HEADINGS", +"uid": "f16fc89aa25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Basic installation", +"Text": "Extract the source files from the treeline tar file, then change to the TreeLine directory in a terminal. For a basic installation, simply execute the following command as root: \"python install.py\"." +}, +"format": "PARAGRAPH", +"uid": "f16fc98aa25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Python 3", +"Text": "If your distribution defaults to Python 2.x, you may need to substitute \"python3\" for \"python\" in these commands." +}, +"format": "PARAGRAPH", +"uid": "f16fcaa2a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Install options", +"Text": "To see all install options, use: \"python install.py -h\". To install TreeLine with a different prefix (the default is /usr/local), use: \"python install.py -p /prefix/path\"." +}, +"format": "PARAGRAPH", +"uid": "f16fcb9ca25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f16fcf3ea25a11e7b7c67054d2175f18", +"f16fd042a25a11e7b7c67054d2175f18", +"f16fd132a25a11e7b7c67054d2175f18" +], +"data": { +"Name": "Windows" +}, +"format": "HEADINGS", +"uid": "f16fcc96a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "All users", +"Text": "To install for all users, execute the \"TreeLine-x.x.x-install-all.exe\" file. Administrator permissions are required." +}, +"format": "PARAGRAPH", +"uid": "f16fcf3ea25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Single user", +"Text": "To install for a single user (administrator rights are not required), execute the \"TreeLine-x.x.x-install-user.exe\" file." +}, +"format": "PARAGRAPH", +"uid": "f16fd042a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Portable install", +"Text": "For a portable install, execute the \"TreeLine-x.x.x-install-user.exe\" file. The file association, shortcuts and uninstaller tasks should be unchecked. When TreeLine starts and prompts for the config file location, choose the program directory option." +}, +"format": "PARAGRAPH", +"uid": "f16fd132a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f16fd326a25a11e7b7c67054d2175f18", +"f16fdccca25a11e7b7c67054d2175f18", +"f16fe0b4a25a11e7b7c67054d2175f18", +"f16fe3fca25a11e7b7c67054d2175f18" +], +"data": { +"Name": "Basic Usage" +}, +"format": "HEADINGS", +"uid": "f16fd236a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f16fd682a25a11e7b7c67054d2175f18", +"28f8844ab58711e79d173417ebd53aeb", +"f16fd858a25a11e7b7c67054d2175f18", +"f16fd9aca25a11e7b7c67054d2175f18", +"f16fdabaa25a11e7b7c67054d2175f18", +"f16fdbc8a25a11e7b7c67054d2175f18" +], +"data": { +"Name": "Views" +}, +"format": "HEADINGS", +"uid": "f16fd326a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Tree View", +"Text": "The left-hand view shows a tree of node titles. Parent nodes can be opened and closed to display or hide their indented descendant nodes. Clicking on an already selected node allows the title to be edited. Right-click context menus are available for commonly used functions." +}, +"format": "HEAD_PARA", +"uid": "f16fd682a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Right-hand Views", +"Text": "The right pane is tabbed to show one of three different views of the data. The \"Data Output\" view shows the formatted text, the \"Data Edit\" view shows text edit boxes, and the \"Title List\" view shows an editable list of node titles.
\n
\nWhen a parent node is selected in the tree, the right view will default to showing information about the selected node in an upper pane and information about the selected node's children in a lower pane. The \"View > Show Child Pane\" command will toggle the display of the child nodes. If the selected node has no children, the view will show a single pane with information about the selected node only.
\n
\nWhen multiple nodes are selected in the tree (by holding down the shift or Ctrl keys while clicking), the right view will not display any child node information. It will instead show information about every selected node.
\n
\nWhen no nodes are selected in the tree (by clicking on a blank area or Ctrl clicking to unselect), the right view will show information about the top-level (root) nodes." +}, +"format": "HEAD_PARA", +"uid": "f16fd858a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Data Output View", +"Text": "The \"Data Output\" view shows formatted output text. It cannot be edited from this view.
\n
\nWhen the \"View > Show Output Descendants\" command is toggled, the \"Data Output\" view will show an indented list with information about every descendant of a single selected node." +}, +"format": "HEAD_PARA", +"uid": "f16fd9aca25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Data Edit View", +"Text": "The \"Data Edit\" view shows a text edit box for each data field within a node. It also shows the node types and the node titles. The types of edit boxes vary based on the field type. Some are just text editors, while others (such as choice fields, date fields, links, etc.) have pull-down menus or dialogs." +}, +"format": "HEAD_PARA", +"uid": "f16fdabaa25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Title List View", +"Text": "The \"Title List\" view shows a list of node titles that can be modified using typical text editor methods. If a new line is typed, a new node is created with that title. If a line is deleted, the corresponding node is removed from the tree." +}, +"format": "HEAD_PARA", +"uid": "f16fdbc8a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f16fddbca25a11e7b7c67054d2175f18", +"f16fdeb6a25a11e7b7c67054d2175f18", +"997af664b58811e7ac243417ebd53aeb", +"f16fdfb0a25a11e7b7c67054d2175f18" +], +"data": { +"Name": "Editing" +}, +"format": "HEADINGS", +"uid": "f16fdccca25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Node Menu", +"Text": "The commands in the \"Node\" menu operate on the selected nodes in the left tree view. There are commands to add or insert nodes, rename node titles and delete nodes. There are also commands to rearrange the tree by changing indent levels or moving nodes up or down. For many of the commands, the descendants of the selected nodes are also affected." +}, +"format": "HEAD_PARA", +"uid": "f16fddbca25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Edit Menu", +"Text": "The edit menu includes undo and redo commands that can fix problems. Cut, copy and paste commands can operate either on text in the right-hand view (if selected or active) or to tree nodes." +}, +"format": "HEAD_PARA", +"uid": "f16fdeb6a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Shortcuts", +"Text": "There are several shortcuts for use in tree editing. Drag and drop will move (or copy if the Ctrl button is held) nodes. Clicking on a selected node will rename it. Pressing the delete key will remove the selected nodes. If desired, these shortcuts can be disabled in \"Tools > General Options\"." +}, +"format": "HEAD_PARA", +"uid": "f16fdfb0a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f16fe1cca25a11e7b7c67054d2175f18", +"f16fe2c6a25a11e7b7c67054d2175f18" +], +"data": { +"Name": "Files" +}, +"format": "HEADINGS", +"uid": "f16fe0b4a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Templates", +"Text": "When starting a new file, a dialog box offers a choice of templates. The default has only a single text field for each node that contains the title. The Long Text template adds a second long text field for more output text. Other templates have various fields for contacts, book lists and to-do lists." +}, +"format": "HEAD_PARA", +"uid": "f16fe1cca25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Sample Files", +"Text": "Various TreeLine sample files can be opened by using the \"File > Open Sample\" command. These have more detail and example content than the new file templates." +}, +"format": "HEAD_PARA", +"uid": "f16fe2c6a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f16fe4eca25a11e7b7c67054d2175f18", +"f16fe5f0a25a11e7b7c67054d2175f18" +], +"data": { +"Name": "Data Types" +}, +"format": "HEADINGS", +"uid": "f16fe3fca25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Node Types", +"Text": "Multiple node data types can be defined in a TreeLine file. Each can contain different data fields and have different output formats. See the template and sample files for examples. Nodes can be set to a specific type using the \"Data > Set Node Type\" command." +}, +"format": "HEAD_PARA", +"uid": "f16fe4eca25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Type Config", +"Text": "The \"Data > Configure Data Types\" command is used to modify node data types, fields and output formatting. Refer to the Detailed Usage section of the full documentation for details." +}, +"format": "HEAD_PARA", +"uid": "f16fe5f0a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f16fe7e4a25a11e7b7c67054d2175f18", +"f16fed0ca25a11e7b7c67054d2175f18", +"f16ff2f2a25a11e7b7c67054d2175f18", +"f1700936a25a11e7b7c67054d2175f18", +"f1700f58a25a11e7b7c67054d2175f18", +"f170146ca25a11e7b7c67054d2175f18", +"f1701caaa25a11e7b7c67054d2175f18", +"f170207ea25a11e7b7c67054d2175f18", +"f170256aa25a11e7b7c67054d2175f18", +"f1702c4aa25a11e7b7c67054d2175f18", +"f170335ca25a11e7b7c67054d2175f18" +], +"data": { +"Name": "Detailed Usage" +}, +"format": "HEADINGS", +"uid": "f16fe6fea25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f16fe8dea25a11e7b7c67054d2175f18", +"f16fe9e2a25a11e7b7c67054d2175f18", +"f16feaf0a25a11e7b7c67054d2175f18", +"f16fec08a25a11e7b7c67054d2175f18" +], +"data": { +"Name": "Tree Navigation and Search" +}, +"format": "HEADINGS", +"uid": "f16fe7e4a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Keyboard Shortcuts", +"Text": "There are several keyboard commands that can be used for tree navigation. The up and down arrow keys move the selection. The left and right arrows open and close the current node. The \"Home\", \"End\", \"Page Up\" and \"Page Down\" keys can be used to move quickly through the tree.
\n
\nAnother way to move through the tree is to type the first letter of a visible node title. Hitting the letter again highlights to the next possibility." +}, +"format": "HEAD_PARA", +"uid": "f16fe8dea25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Selection", +"Text": "Multiple nodes can be selected by holding down the CTRL or the SHIFT key when changing the active node. Individual nodes are added or removed from the selection when the CTRL key is held. The selection of all nodes between the old and new active nodes are toggled when SHIFT is held. The active node can be changed by using the mouse or by using any of the keyboard navigation methods.
\n
\nThe \"View > Previous Selection\" and \"View > Next Selection\" commands can be used to toggle through a history of selections, allowing faster navigation through the tree." +}, +"format": "HEAD_PARA", +"uid": "f16fe9e2a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Searching", +"Text": "The \"Tools > Find Text\" command will search for text within the tree structure. The dialog box has options for searching all of the node data or only the node titles. There are also options for how to interpret the search text. Key words will match nodes with the search words found anywhere in the node. Key full words will only match complete words anywhere in the node. Full phrase will only match the complete phrase in the proper sequence. Finally, the regular expression option will search using Python regular expressions.
\n
\nThe \"Tools > Conditional Find\" command will search in particular node types and node fields. Various comparison operators can be selected to exactly match, to match a greater or lesser value, or part of the value. And the True/False operators give the same result regardless of the values. In general, the value is interpreted using the edit format for special field types. Multiple rules can be added, connected with logical \"and\" or \"or\" operators. The \"All Types\" option makes fields from every type are available, so that multiple node types to be part of the same search. The condition will be false for node types that do not contain that field name.
\n
\nThere is also a quick, incremental search of node titles. By default, it's bound to ctrl+/. Then, matching titles are found as the search string is typed. The F3 and shift+F3 keys can be used to go to the next or previous matches, respectively." +}, +"format": "HEAD_PARA", +"uid": "f16feaf0a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Filtering", +"Text": "There are two filtering commands, \"Tools > Text Filter\" and \"Tools > Conditional Filter\". They work like the corresponding search commands above, except that they show all of the matching nodes in a flat list that replaces the tree view. The nodes can be selected and edited from this view. Use the \"End Filter\" button to restore the full tree view.
\n
\nSearches for Conditional Find and Conditional Filter can be saved in the dialog boxes and loaded again at another time." +}, +"format": "HEAD_PARA", +"uid": "f16fec08a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f16fedfca25a11e7b7c67054d2175f18", +"f16feef6a25a11e7b7c67054d2175f18", +"f16feffaa25a11e7b7c67054d2175f18", +"f16ff0f4a25a11e7b7c67054d2175f18", +"f16ff1e4a25a11e7b7c67054d2175f18" +], +"data": { +"Name": "Defining Node Types" +}, +"format": "HEADINGS", +"uid": "f16fed0ca25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Type List", +"Text": "The \"Type List\" is the first tab of the \"Data > Configure Types Dialog\". The list of data types can be modified by the buttons on the right. New types can be added, and existing types can be copied, renamed or deleted." +}, +"format": "HEAD_PARA", +"uid": "f16fedfca25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Type Config", +"Text": "\"Type Config\" is the second tab of the Configure Types Dialog. It contains a selection for the default child type. If set, this will be the initial type used for new children with this type of parent. If set to \"[None]\", children will default to either the type of their siblings or their parent.
\n
\nThe \"Change Icon\" button allows the selection of a custom tree icon for this data type. The \"Clear Select\" button on the icon dialog can be used to set the icon to \"None\", so that no icon will be displayed for this type. To avoid showing any tree icons, the \"Show icons in the tree view\" general option can be unset.
\n
\nThere are also options here for adding blanks lines between nodes, allowing HTML tags in the common format text, and changing the output to add bullets or tables." +}, +"format": "HEAD_PARA", +"uid": "f16feef6a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Field List", +"Text": "The \"Field List\" is the third tab of the Configure Types Dialog. The list of fields within a data type can be modified by using the buttons on the right. New fields can be added, and existing fields can be moved, renamed or deleted. Sort keys can also be defined to specify the fields that are compared when nodes are sorted." +}, +"format": "HEAD_PARA", +"uid": "f16feffaa25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Field Config", +"Text": "\"Field Config\" is the fourth tab of the Configure Types Dialog. The field type and its output format string can be set, if applicable to the field. Extra prefix and suffix text to be output with the field can also be set, and a default field value for new nodes can be entered. The number of lines displayed in the editor for the field can also be specified. Finally, an option to evaluate HTML tags can be set to recognize HTML tags in Choice, AutoChoice, Combination, AutoCombination and RegularExpression fields. If this is set, special characters (angled brackets and quotation marks) will need to be manually escaped to show up in the field text." +}, +"format": "HEAD_PARA", +"uid": "f16ff0f4a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Output", +"Text": "\"Output\" is the last tab of the Configure Types Dialog. The left half of the dialog shows the fields. The right half shows the formatting for the title (used for the node text in the tree view) and the node output. The formatting consists of text lines with embedded fields. The fields are shown as \"{*field_name*}\". The fields that are selected in the list (multiple fields can be selected by holding Ctrl or Shift keys while clicking) can be added to a format at the cursor position with the \">>\" keys. The field reference at the cursor can be removed with the \"<<\" keys." +}, +"format": "HEAD_PARA", +"uid": "f16ff1e4a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f16ff3e2a25a11e7b7c67054d2175f18", +"f16ff4dca25a11e7b7c67054d2175f18", +"f16ff5f4a25a11e7b7c67054d2175f18", +"f16ff7c0a25a11e7b7c67054d2175f18", +"f16ff8f6a25a11e7b7c67054d2175f18", +"f16ff9faa25a11e7b7c67054d2175f18", +"f17005f8a25a11e7b7c67054d2175f18", +"f1700710a25a11e7b7c67054d2175f18", +"f16ffc02a25a11e7b7c67054d2175f18", +"f16ffd06a25a11e7b7c67054d2175f18", +"f16ffe14a25a11e7b7c67054d2175f18", +"f16ffb08a25a11e7b7c67054d2175f18", +"f16fff18a25a11e7b7c67054d2175f18", +"f1700008a25a11e7b7c67054d2175f18", +"f1700102a25a11e7b7c67054d2175f18", +"f1700206a25a11e7b7c67054d2175f18", +"f1700300a25a11e7b7c67054d2175f18", +"f1700404a25a11e7b7c67054d2175f18", +"f17004fea25a11e7b7c67054d2175f18", +"f170081ea25a11e7b7c67054d2175f18" +], +"data": { +"Name": "Field Types" +}, +"format": "HEADINGS", +"uid": "f16ff2f2a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Field Options", +"Text": "The field type and options are set in the \"Field Config\" tab of the \"Data > Configure Types Dialog\". The many different field types are described in the paragraphs below.
\n
\nSeveral of the field types use a formatting string to define their output. For a list of available formatting characters, use the \"Format Help\" button. Entries in the data editor which do not match the format will cause a blue triangle to show in the upper left corner of the edit box, and the output for that field will be replaced with \"#####\"." +}, +"format": "HEAD_PARA", +"uid": "f16ff3e2a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Text Type", +"Text": "The default field type is a text field. It is the most commonly used field. These fields are edited using edit boxes in the data editor view. There are several commands in the Format menu (and also in the context menu) for setting the font style and adding external or internal links. The edit box height expands when re-displayed after adding several lines of text. The minimum edit box height can also be set explicitly in the \"Field Config\" tab." +}, +"format": "HEAD_PARA", +"uid": "f16ff4dca25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "HTML Text Type", +"Text": "This type allows simple HTML tags to be used in the text. Commonly used tags include \"<b>bold</b>\", \"<u>underline</u>\", \"line break<br/>\", \"horizontal line<hr/>\", and various font tags. Complex block tags should generally be avoided. Carriage returns are ignored, and non-escaped \"<\", \">\" and \"&\" symbols do not display. " +}, +"format": "HEAD_PARA", +"uid": "f16ff5f4a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "One Line Text Type", +"Text": "This type restricts the text length to a single line. It does not allow carriage returns but does not restrict line wrapping of a single long line." +}, +"format": "HEAD_PARA", +"uid": "f16ff7c0a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Spaced Text Type", +"Text": "This type holds plain text and preserves all spacing. Other formatting of the text is not permitted. It could be useful to use the \"Tools > Customize Fonts\" command to set the editor font to a mono-spaced font when using this field type." +}, +"format": "HEAD_PARA", +"uid": "f16ff8f6a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Number Type", +"Text": "In the number type, special characters in the format define the display of the numbers. The format uses a string of \"#\" (optional digit) and \"0\" (required digit) characters to define the output formatting. For example, pi formatted with \"#.#\" is \"3.1\" and formatted with \"00.00\" is \"03.14\". Regardless of the formatting, digits to the left of the decimal point are not truncated, since that would display an incorrect result. But use care to show enough decimal places (either optional or required) to avoid problems with round-off error.
\n
\nThe radix character can be specified as either \".\" or \",\" to handle internationalization. For use as a thousands separator, use \"\\,\" or \"\\.\". For example, a large number may be formatted as \"#\\,###\\,###.##\" or as \"#\\.###\\.###,##\". Press the \"Format Help\" button from the field format dialog for more formatting details.
\n
\nUnlike most other formats, the number type also uses the output format for display in the Data Editor. Of course, any new entry with a reasonable format is correctly interpreted (but the correct radix character must be used)." +}, +"format": "HEAD_PARA", +"uid": "f16ff9faa25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Boolean Type", +"Text": "This type gives two choices corresponding to true/false values. The format help menu includes typical values such as \"yes/no\", \"true/false\" and \"1/0\", but users can also enter their own word pair. The data editor boxes will accept either the currently set format or any of the typical values." +}, +"format": "HEAD_PARA", +"uid": "f16ffb08a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Date Type", +"Text": "In the date field type, special characters in the format (all starting with \"%\") are replaced by elements of the data, similar to number fields. Press the \"Format Help\" button from the field format dialog for formatting details. Non-special characters will be output as themselves.
\n
\nThere is also an edit format under \"Tools > General Options > Data Editor Formats\". This controls how date fields are displayed in the Data Editor view. Generally, entries in the data editor with various formats will be correctly interpreted regardless of this setting, but dates must use the correct day-month-year sequence. Also note that the date editor format does not support days of the week.
\n
\nA default initial field value of \"Now\" can be used to enter the date of node creation." +}, +"format": "HEAD_PARA", +"uid": "f16ffc02a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Time Type", +"Text": "In the time field type, special characters in the format (all starting with \"%\") are replaced by elements of the data, similar to number fields. Press the \"Format Help\" button from the field format dialog for formatting details. Non-special characters will be output as themselves.
\n
\nThere is also an edit format under \"Tools > General Options > Data Editor Formats\". This controls how time fields are displayed in the Data Editor view. Generally, entries in the data editor with various formats will be correctly interpreted regardless of this setting.
\n
\nA default initial field value of \"Now\" can be used to enter the time of node creation." +}, +"format": "HEAD_PARA", +"uid": "f16ffd06a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "DateTime Type", +"Text": "The DateTime field combines dates and times into a single field that is useful for timestamps. Special characters in the format (all starting with \"%\") are replaced by elements of the data, as in date and time fields. Press the \"Format Help\" button from the field format dialog for formatting details. Non-special characters will be output as themselves.
\n
\nThe DateTime edit format is uses the date and time formats located in \"Tools > General Options > Data Editor Formats\". The date and time formats are combined, separated by a space character.
\n
\nA default initial field value of \"Now\" can be used to enter the date and time of node creation." +}, +"format": "HEAD_PARA", +"uid": "f16ffe14a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Choice Type", +"Text": "The choice field type allows for the selection of text items from a pull-down edit list. The formatting strings for these types list the items separated with the \"/\" character (use \"//\" to get a literal \"/\" in an item). Entries in the data editor which do not match the choices will cause a blue triangle to show in the upper left corner of the edit box, and the output for that field will be replaced with \"#####\"." +}, +"format": "HEAD_PARA", +"uid": "f16fff18a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Auto Choice Type", +"Text": "This field type is similar to the choice type, but without a format string. The entries in the pull-down menu are automatically generated from all previously used entries. Any entries that are typed will be available in the pull-down menu for future use. " +}, +"format": "HEAD_PARA", +"uid": "f1700008a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Combination Type", +"Text": "This is basically the equivalent of the choice type with multiple selection. The formatting string entries are separated by the \"/\" character. The pull-down menu shows check-boxes that are checked for currently selected nodes. By default, the selected entries are output separated by a comma and a space. This can be changed in the \"Type Config\" tab of the \"Data > Configure Types Dialog\". Click the \"Show Advanced\" button to see the separator setting." +}, +"format": "HEAD_PARA", +"uid": "f1700102a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Auto Combination Type", +"Text": "This field type is similar to the combination type, but without a format string. The entries in the pull-down menu are automatically generated from all previously used entries. Any entries that are typed will be available in the pull-down menu for future use. " +}, +"format": "HEAD_PARA", +"uid": "f1700206a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "External Link Type", +"Text": "This type can support various link protocols, including http and https for web pages, mailto for email addresses and file for local files. A pull down dialog in the data editor allows the selection of the protocol type and the entry of an address and a display name. In the edit boxes, the display name shows up in [brackets], and it is used as the text of the link in the output view. The \"file\" protocol also provides a button to browse for a path and buttons to choose either absolute or relative path names.
\n
\nClicking on the link in the output view or choosing \"Open Link\" from the right-click context menu in the edit box will open the link in a web browser. In Linux, setting the \"BROWSER\" environment variable to a string like \"mozilla %s\" will result in the desired browser being used." +}, +"format": "HEAD_PARA", +"uid": "f1700300a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Internal Link Type", +"Text": "These links create shortcuts to select nodes elsewhere in the tree structure. To create a link, click the pull-down arrow and then select the target node in the tree view. The display name is shown in brackets. It is initially set to the target node title, but it can be edited. The right-click context menu can be used to clear the link or to select the target node.
\n
\nClicking the link in the output view will select the target node in the tree view." +}, +"format": "HEAD_PARA", +"uid": "f1700404a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Picture Type", +"Text": "These links add referenced pictures to the output view. A pull down dialog in the data editor has a button to browse for picture files to be linked. It also allows absolute or relative paths to be used and has a small image preview." +}, +"format": "HEAD_PARA", +"uid": "f17004fea25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Math Type", +"Text": "Math field types are configured by defining equations. The equations can reference number fields, date fields, time fields, boolean fields, text fields and/or other math fields. The resulting values of math fields are automatically calculated for each node. Various mathematical, relational and string operators are available. The results can be numbers, text, boolean, dates or times.
\n
\nTo define a math field equation, press the \"Define Equation\" button in the \"Field Config\" tab of the \"Data > Configure Types Dialog\". This brings up a dialog with fields to reference on the left and math operators on the right. The \"Reference Level\" pull-down determines whether the reference is from the same node, the node's parent, the root node, or the node's children. The \"Result Type\" pull-down allows arithmetic, date, time, boolean or text results to be chosen. The \"Operator Type\" pull-down allows numeric, comparison or text operators to be shown in the operator list. The down-arrow buttons below the references and operators add the selected item to the equation text below. Portions of equations can also be typed directly in the equation text line editor.
\n
\nReferences to child nodes must be enclosed in a grouping function, such as sum, max, min, mean or join. A math field can contain a parent or child reference to itself, but not a same-level reference to itself (a circular reference). The references only contain the field name, so they will reference a parent or child field with that name even if it is a different node type.
\n
\nIn equations, date fields are represented by the number of days since January 1, 1970, and time fields are the number of seconds since midnight. So date fields can be subtracted to give the number of days elapsed, and numbers of days can be added to or subtracted from dates to result in new dates. Time fields can be subtracted to give the number of seconds elapsed, and numbers of seconds can be added to or subtracted from times to result in new times.
\n
\nThe \"if\" comparison operator can be used to make the result depend on the value of another field. The expression is written as \"true_value if condition else false_value\". Of course, the \"true_value\", \"condition\" and \"false_value\" strings must be replaced with valid fields or expressions. If this operator is inserted from the operator list (under comparisons), it will include parenthesis as placeholders. These parenthesis are optional in the equations.
\n
\nThe \"join\" text function is used to combine text from several other fields (or from child nodes). The first argument to the function is a separator string that is placed between each piece of text. The remaining argument(s) are the text to be joined.
\n
\nBy default, math fields are shown in the data edit view, but they are read-only. To hide them, uncheck the \"Show math fields in the Data Edit View\" box under \"Tools > General Options > Features Available\".
\n
\nThere is also an option under \"File > Properties\" that toggles whether blank fields are treated as zeros. If checked (the default), a blank field that is referenced by a math field has a value of zero (for numeric operations) or blank (for text operations). If unchecked, any blank references also cause equations that reference them to be blank.
\n" +}, +"format": "HEAD_PARA", +"uid": "f17005f8a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Numbering Type", +"Text": "This type (not to be confused with the number type above) provides fields that are automatically filled in with the \"Data > Update Numbering\" command. The \"Format Help\" button in the field format dialog shows the output format options. A single format level will result in a simple sequential numbering scheme. Use of the \"/\" level separator will result in an outline-type numbering with different sequences at different levels. Use of the \".\" section separator will result in a \"2.3.5\" type numbering scheme.
\n
\nSince numbering fields are automatically populated, by default they are not shown in the data edit view. To show them, check the \"Show numbering fields in the Data Edit View\" box under \"Tools > General Options > Features Available\". When they are shown in the data edit view, they show up in section numbering style, regardless of the output format." +}, +"format": "HEAD_PARA", +"uid": "f1700710a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Regular Expression Type", +"Text": "This type allows arbitrary format strings to be matched that restrict the data to a particular format. It uses Python regular expression syntax. Entries in the data editor which do not match the format string expression will cause a blue triangle to show in the upper left corner of the edit box, and the output for that field will be replaced with \"#####\"." +}, +"format": "HEAD_PARA", +"uid": "f170081ea25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f1700a26a25a11e7b7c67054d2175f18", +"f1700b34a25a11e7b7c67054d2175f18", +"f1700c38a25a11e7b7c67054d2175f18", +"f1700d3ca25a11e7b7c67054d2175f18", +"f1700e40a25a11e7b7c67054d2175f18", +"bbbf735ac24711e8b560d66a6ab671cb" +], +"data": { +"Name": "Output Formatting Details" +}, +"format": "HEADINGS", +"uid": "f1700936a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Output Format Examples", +"Text": "Here is an example of output formatting for a book list:
\n
\n\"{*Title*}\"
\n(c) {*Copyright*}, Rating: {*Rating*}
\n{*PlotDescription*}
\n
\nEach of the field names in enclosed in {* *}, curly brackets and asterisks. For more examples, see the sample files that can be opened using the \"File > Open Sample\" command." +}, +"format": "HEAD_PARA", +"uid": "f1700a26a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Title Formats", +"Text": "When a node in the tree is renamed, the program attempts to match the title formatting pattern to set the appropriate fields (the same title matching occurs when editing lines in the \"Title List\" view). If the title formatting is too complex, it may not correctly guess the intent. Things like adjacent fields with no characters separating them should be avoided unless you do not wish to rename nodes from the tree.
\n
\nIf the text data used for a tree view title has multiple lines, only the first line will be used as the title." +}, +"format": "HEAD_PARA", +"uid": "f1700b34a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Skipped Output Lines", +"Text": "If all of the fields in an output format line are empty for a given node, then the line is skipped. No blank line or embedded text will be output for that line. Note that this does not apply to a line without any fields (only embedded text). Also, when a line ending with a <br/> or an <hr/> tag is skipped, the ending tag is retained." +}, +"format": "HEAD_PARA", +"uid": "f1700c38a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "HTML Tags", +"Text": "By default, the \"Allow HTML rich text in formats\" option is unchecked in the \"Type Config\" tab of the Configure Types Dialog. So any HTML tags are treated as plain text. If the option is enabled, simple HTML formatting tags can be used in node output formats.
\n
\nCommonly used tags include \"<b>bold</b>\", \"<u>underline</u>\", \"line break<br/>\", \"horizontal line<hr/>\", and various font tags. Complex block tags should generally be avoided." +}, +"format": "HEAD_PARA", +"uid": "f1700d3ca25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Other Field References", +"Text": "References to fields that are not contained within the node can be added to the output. Pushing the \"Show Advanced\" button on the \"Output\" tab of the configure dialog makes a reference level selection become visible.
\n
\nIf the reference level is changed to \"File Info Reference\", fields containing file meta-data can be added to the output. These include the file name, path, size, modified date and modified time. These special fields are shown as \"{*!field_name*}\" in the title and output format editors.
\n
\nThere are field references to various ancestor nodes (parents, grandparents, etc.). These require the data type of the reference to be specified. This selection determines the field names that are available, but the data from any type with a matching field name will be shown in the output. References to fields from parent and grandparent nodes are shown as \"{**field_name*}\" and \"{***field_name*}\", respectively. There are also general ancestor references, shown as \"{*?field_name*}\", that take data from the closest ancestor with a matching field.
\n
\nReferences to child nodes can also be added, shown as \"{*&field_name*}\". These also require that the child data type be specified. The child data becomes embedded in the parent output. The child data is delimited with a separator string defined on the \"Type Config\" tab (with show advanced active). The separator defaults to a comma and a space, but can be set to <br/> or anything else.
\n
\nFinally, a \"Child Count\" reference can be added. This field will show the number of children (\"Level1\" field) or grandchildren (\"Level2\" field) of a node. These are shown as {*#Level1*} in the format editors.
\n
\nFor examples of these fields, see the \"sample_other_fields\" file (by using the \"File > Open Sample\" command)." +}, +"format": "HEAD_PARA", +"uid": "f1700e40a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f1701156a25a11e7b7c67054d2175f18", +"f170125aa25a11e7b7c67054d2175f18", +"f1701368a25a11e7b7c67054d2175f18", +"aa6f6ca4bb0711e7bbf43417ebd53aeb" +], +"data": { +"Name": "Type Format Details" +}, +"format": "HEADINGS", +"uid": "f1700f58a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Generic and Derived Types", +"Text": "Data types can be set to derive their field settings from a generic type. This allows types with different output formatting to always use the same set of fields. Any changes to the generic's list of fields and field types are automatically reflected in the fields of all derived types. This does not apply to a field's output formatting, which can still be set independently.
\n
\nThere are two methods for creating derived types. First, a derived option can be selected when copying a type on the \"Type List\" tab of the \"Data > Configure Types Dialog\". Alternately, a generic type can be specified from the derived type's \"Type Config\" tab of the dialog if the advanced functions are shown." +}, +"format": "HEAD_PARA", +"uid": "f1701156a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Conditional Types", +"Text": "Conditional expressions can be used to automatically assign a data type based on each node's content. Conditions can be assigned only to a generic type and its associated derived types. This allows the automatic assignment of different output formatting or different icons depending on each node's field data.
\n
\nThe conditional dialog box is accessed from a button on the \"Type Config\" tab of the \"Data->Configure Types Dialog\" if the advanced functions are shown. Each line of the condition includes a field, an operator and a comparison value. The operators include equality, greater than, less than, starts with, ends with, and contains. There are also True and False operators that will toggle the type of all nodes simultaneously.
\n
\nFor special field types such as dates, times, and booleans, the comparison value should be entered in the same format that is used in the Data Editor window. In general, the starts with, ends with, and contains operators should not be used for these special fields, since the comparison is done using an internal data representation. Dates and times also support a special comparison value of \"now\", which is always interpreted as the current date and time.
\n
\nThe \"Add New Rule\" button is used to add additional condition lines. The lines can be joined with \"and\" or \"or\" operators. The \"Remove Rule\" button deletes the last condition line. If only a single line is present, the \"Remove Rule\" button completely removes the condition.
\n
\nConditions do not have to be set for all types in a family. If no conditions are true for a node, the program will select a blank condition rather than a false one.
\n
\nFor an example, see the \"sample_conditional_todo\" file (by using the \"File > Open Sample\" command)." +}, +"format": "HEAD_PARA", +"uid": "f170125aa25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Copying Formats", +"Text": "Another method for changing data type formatting is to copy the formats from another TreeLine file. This is done with the \"Data > Copy Types from File\" command. All types from the chosen file are copied. Any types in the current file with matching names are overwritten, but types with unique names are retained." +}, +"format": "HEAD_PARA", +"uid": "f1701368a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f170155ca25a11e7b7c67054d2175f18", +"f1701660a25a11e7b7c67054d2175f18", +"f1701764a25a11e7b7c67054d2175f18", +"f1701868a25a11e7b7c67054d2175f18", +"f170198aa25a11e7b7c67054d2175f18", +"f1701a8ea25a11e7b7c67054d2175f18" +], +"data": { +"Name": "Tree Data Operations" +}, +"format": "HEADINGS", +"uid": "f170146ca25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Sorting", +"Text": "The \"Data > Sort Nodes\" command can sort nodes based on either node titles or key fields predefined in the node type configuration. The predefined sort fields can be changed in the \"Field Config\" tab of the Configure Types Dialog. The \"Sort Keys\" button brings up a list of fields that define a sort key sequence. The fields higher in the sequence have a higher priority. The direction for each key field can be flipped." +}, +"format": "HEAD_PARA", +"uid": "f170155ca25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Numbering", +"Text": "The \"Data > Update Numbering\" command updates the contents of fields with a special numbering field type. The field's output format defines how the numbers are displayed in the output, including whether individual numbers, outline numbers or a section numbering scheme are shown. See the Numbering Field Type for more information.
\n
\nNote that numbering fields are not shown in the data edit view unless \"Show numbering fields in the Data Edit View\" is checked under \"Tools > General Options > Features Available\". When they are shown in the data edit view, they show up in section numbering style, regardless of the output format." +}, +"format": "HEAD_PARA", +"uid": "f1701660a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Find and Replace", +"Text": "The \"Tools > Find and Replace\" command can be used to change the text in several nodes. The search text and replacement text are entered. Searching can be based on any match, full words only, or a Python regular expression. The operation can optionally be restricted to a particular node type and to a particular node field.
\n
\nReplacement using regular expressions is quite powerful. Searching for \".*\" will match all of the text in the field. The replacement string can contain back references that consist of a backslash and a number. The back references get replaced with the corresponding parenthesized group from the match. For example, \"\\2\" will be replaced with the text that matched the second group of parenthesis. The \"\\g<0>\" back-reference can be used to substitute the entire matching string." +}, +"format": "HEAD_PARA", +"uid": "f1701764a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Spell Check", +"Text": "There is a spell check command in the \"Tools\" menu. Use of this command requires an external program to be installed (either aspell, ispell or hunspell- see the System Requirements section). If there are any misspelled words in the selected branch, a dialog will allow the word to be ignored, added to the dictionary, replaced with a suggestion or edited. This will spell check the text in all data fields of each node." +}, +"format": "HEAD_PARA", +"uid": "f1701868a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Cloned Nodes", +"Text": "Cloned nodes are used to duplicate sections of the tree. Editing their data or child structure in one location changes all locations. When a cloned node is selected, multiple lines are shown in the Breadcrumb view, showing ancestor nodes that have multiple parents. The selected node will appear black (not a link), but its clones can be clicked to select their location in the tree view.
\n
\nCloned nodes are created by copying nodes or branches and then using the special paste clone commands found in the \"Edit\" menu to paste them elsewhere. The clone link can be removed by deleting the nodes or by using the \"Data > Detach Clones\" command to convert them back to regular nodes.
\n
\nCloned nodes can also be created automatically by using the \"Data > Clone All Matched Nodes\" command. This will convert all identical nodes in the tree structure into cloned nodes.
\n
\nFor an example of cloned nodes with multiple parents, see the \"sample_genealogy\" file (by using the \"File > Open Sample\" command)." +}, +"format": "HEAD_PARA", +"uid": "f170198aa25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Category-Based Arrangement", +"Text": "The \"Data\" menu contains commands for arranging and flattening the data by category. These methods are used to automatically add and remove levels of nodes below the current node in the tree.
\n
\nThe \"Add Category Level\" command allows you to select one or more of the fields that the child nodes have in common. These fields are used to create new parent nodes for the children, grouping them by common categories. For example, in a list of books, picking the \"author_first_name\" and \"author_last_name\" fields will result in a tree with the books under new nodes for each unique author.
\n
\nThe \"Flatten by Category\" command is almost the opposite of \"Add Category Level\". It eliminates any descendant nodes with children, transferring their data fields to their children. It will rename fields instead of overwriting data with the same field names, but this command is most useful when the children and parents are different types with unique field names.
\n
\nThere is also a \"Swap Category Levels\" command that will swap the child and grandchild nodes beneath a selected node. A child node with multiple nodes under it will become cloned under each one. Any existing grandchild clones will become individual nodes with multiple children." +}, +"format": "HEAD_PARA", +"uid": "f1701a8ea25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f1701d90a25a11e7b7c67054d2175f18", +"f1701e8aa25a11e7b7c67054d2175f18", +"f1701f84a25a11e7b7c67054d2175f18" +], +"data": { +"Name": "Printing" +}, +"format": "HEADINGS", +"uid": "f1701caaa25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Print Setup", +"Text": "The dialog for print setup contains four tabs. The first, General Options, includes settings for what part of the tree to print, whether to include lines to child nodes and whether to allow page breaks between a parent an its first child node. It also has place to set the printer queue, to make pagination and print previews more accurate. The second tab, Page Setup, includes paper size, orientation, margins and columns. The third tab selects the font for printing. The last tab, Header/Footer, defines text and file data meta-fields for inclusion in headers and footers." +}, +"format": "HEAD_PARA", +"uid": "f1701d90a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Print Preview", +"Text": "The Print Preview window shows how the printout will look with current print settings. It can be dragged larger to show more detail, or the zoom settings can be changed. It also includes buttons for the Print Setup dialog and for printing." +}, +"format": "HEAD_PARA", +"uid": "f1701e8aa25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Print to PDF", +"Text": "This command prompts for a path and file name to export a PDF file. It uses the current printer settings." +}, +"format": "HEAD_PARA", +"uid": "f1701f84a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f170216ea25a11e7b7c67054d2175f18", +"f1702272a25a11e7b7c67054d2175f18", +"f170236ca25a11e7b7c67054d2175f18", +"f1702470a25a11e7b7c67054d2175f18" +], +"data": { +"Name": "File Handling" +}, +"format": "HEADINGS", +"uid": "f170207ea25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "File Compression", +"Text": "A TreeLine file is in a JSON text format. There are options to compress the files (gzip format) to save storage space. Individual files can be set to compressed mode from either \"File > Properties\" or from the file type pull-down in the save-as dialog." +}, +"format": "HEAD_PARA", +"uid": "f170216ea25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "File Encryption", +"Text": "There is a file encryption option to password protect TreeLine files. Individual files can be set to encrypted mode from either \"File > Properties\" or from the file type pull-down in the save-as dialog. The encryption uses an SHA hash function as a stream cipher - it should be fairly secure." +}, +"format": "HEAD_PARA", +"uid": "f1702272a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Auto-Save", +"Text": "An auto-save feature can store unsaved files with a \"~\" appended to the file name. The backup files are automatically removed when the file is saved or TreeLine exits cleanly. The auto-save time interval is set in the general options. Setting the interval to zero disables this feature." +}, +"format": "HEAD_PARA", +"uid": "f170236ca25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Saved Tree States", +"Text": "When opening a recently used file, TreeLine will restore the states of open and selected nodes. This information is stored in the user's TreeLine configuration files. If desired, this feature can be disabled with a general option." +}, +"format": "HEAD_PARA", +"uid": "f1702470a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f170265aa25a11e7b7c67054d2175f18", +"f170275ea25a11e7b7c67054d2175f18", +"f1702b46a25a11e7b7c67054d2175f18", +"95547c38b97411e7a42a3417ebd53aeb", +"f1702858a25a11e7b7c67054d2175f18", +"f1702952a25a11e7b7c67054d2175f18", +"f1702a42a25a11e7b7c67054d2175f18" +], +"data": { +"Name": "File Import" +}, +"format": "HEADINGS", +"uid": "f170256aa25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "General Information", +"Text": "A TreeLine file is in a specific JSON text format. Other types of files can be imported using the \"File > Import\" command, which will show a dialog box where the type of import can be selected. Alternatively, using the \"File > Open\" command with a non-TreeLine file will also show this dialog." +}, +"format": "HEAD_PARA", +"uid": "f170265aa25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Text Import", +"Text": "Several different text formats can be selected for import. Tab indented text creates a node title from each line in the file, structured based on the number of tabs before each line.
\n
\nThe comma-delimited (CSV) and the tab delimited text tables use the first line as a header row to create field names, then each additional row becomes a node with field data taken from each column. The CSV import with level numbers creates a tree structure from level numbers in the first column that are incremented to show child relationships.
\n
\nThe plain text, one node per line import creates a flat tree of node titles.
\n
\nFinally, the plain text paragraph import creates long text nodes from text separated by blank lines." +}, +"format": "HEAD_PARA", +"uid": "f170275ea25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Treepad Import", +"Text": "Files from the Treepad shareware program can be imported. Only Treepad text nodes are supported." +}, +"format": "HEAD_PARA", +"uid": "f1702858a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "XML Import", +"Text": "TreeLine will import and export generic XML files. These routines do not have much intelligence - each XML element becomes a node and each XML attribute becomes a field. XML text content become fields named \"Element_Data\". This lets TreeLine function as a crude XML editor." +}, +"format": "HEAD_PARA", +"uid": "f1702952a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "ODF Import", +"Text": "TreeLine will import Open Document Format (ODF) text documents, from applications such as Apache OpenOffice and LibreOffice. The node structure is formed based on the heading styles assigned in the document. Any text under each heading is assigned to that heading's node. The import filter is intended for simple text outlines only. No formatting is maintained, and objects such as tables and pictures are not imported." +}, +"format": "HEAD_PARA", +"uid": "f1702a42a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Bookmarks Import", +"Text": "TreeLine will import bookmark files in both the Mozilla HTML format (Firefox browser) and the XBEL format (Konqueror, Galeon and Elinks browsers). Each bookmark becomes a node with a name and a link field. Some information in the files, such as visited dates and icon references, is not imported. For an example, see the \"sample_bookmarks\" file (by using the \"File > Open Sample\" command).
\n" +}, +"format": "HEAD_PARA", +"uid": "f1702b46a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f1702d4ea25a11e7b7c67054d2175f18", +"f1702e52a25a11e7b7c67054d2175f18", +"f1702f60a25a11e7b7c67054d2175f18", +"65289b36b97611e783eb3417ebd53aeb", +"f1703064a25a11e7b7c67054d2175f18", +"f170315ea25a11e7b7c67054d2175f18", +"f1703262a25a11e7b7c67054d2175f18" +], +"data": { +"Name": "File Export" +}, +"format": "HEADINGS", +"uid": "f1702c4aa25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "General Information", +"Text": "Files are exported using the \"File > Export\" command. This will show a dialog box of available export types and options." +}, +"format": "HEAD_PARA", +"uid": "f1702d4ea25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "HTML Export", +"Text": "The HTML export subtypes can export to a single page or to multiple pages. The single page export contains all of the indented output. A navigation pane on the left with links to anchors at node positions is optional.
\n
\nThe multiple HTML pages export has one web page per node. It includes a navigation pane on the left with links to the pages with sibling, parent and aunt/uncle nodes.
\n
\nMultiple HTML data tables export creates a table in each HTML file that contains the data for a set of siblings, as well as links to the parent and child pages.
\n
\nThe Live Tree HTML export uses Javascript and CSS to create an interactive view. Nodes in the tree can be expanded and collapsed, and a separate pane shows the output for all descendants of the selected node. The first form of this export creates separate files that are linked to the existing TreeLine file. These files are intended for use on a web server (browser security usually prevents local access). The HTML file stores the relative path from the TreeLine file to the initial export directory, so this relationship needs to be maintained on the web server. Only the TreeLine file needs to be replaced to update the data (re-export is not required).
\n
\nThe second form of Live Tree export creates a single file (with embedded data) that can be accessed from a local web browser. It appears the same as the first form on the browser." +}, +"format": "HEAD_PARA", +"uid": "f1702e52a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Text Export", +"Text": "Data can be exported to tabbed title text, comma-delimited (CSV) tables and tab-delimited tables. These formats are the same as the corresponding import formats.
\n
\nThere is also an unformatted text export the dumps all of the output into a text file without preserving the tree structure." +}, +"format": "HEAD_PARA", +"uid": "f1702f60a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "XML Export", +"Text": "TreeLine will import and export generic XML files. These routines do not have much intelligence - each node becomes an XML element and each field becomes an XML attribute, except for fields named \"Element_Data\" that become the element's text. This lets TreeLine function as a crude XML editor." +}, +"format": "HEAD_PARA", +"uid": "f1703064a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "ODF Export", +"Text": "TreeLine will export an outline to an Open Document Format (ODF) text document, compatible with Apache OpenOffice and LibreOffice. The title of each node is assigned a heading style at the appropriate level. Any other text in the output of each node becomes normal text under the heading. The export filter is intended for simple text outlines only. Any HTML formatting is stripped, and objects such as tables and pictures are not supported." +}, +"format": "HEAD_PARA", +"uid": "f170315ea25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Bookmarks Export", +"Text": "TreeLine will export bookmark files in both the Mozilla HTML format (Firefox browser) and the XBEL format (Konqueror, Galeon and Elinks browsers). TreeLine will look for a link field in each node that becomes the target of the bookmark." +}, +"format": "HEAD_PARA", +"uid": "f1703262a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f1703442a25a11e7b7c67054d2175f18", +"f1703546a25a11e7b7c67054d2175f18", +"f170364aa25a11e7b7c67054d2175f18", +"f1703744a25a11e7b7c67054d2175f18", +"f1703848a25a11e7b7c67054d2175f18", +"7e9b3b88c24511e896c7d66a6ab671cb" +], +"data": { +"Name": "Customizations" +}, +"format": "HEADINGS", +"uid": "f170335ca25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Options", +"Text": "TreeLine's behavior can be modified with several settings available in \"Tools > General Options\". Most of these options are covered elsewhere in this document." +}, +"format": "HEAD_PARA", +"uid": "f1703442a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Keyboard Shortcuts", +"Text": "Keyboard shortcuts can be customized by using the \"Tools > Set Keyboard Shortcuts\" command. Simply type the new key sequence with the appropriate field selected." +}, +"format": "HEAD_PARA", +"uid": "f1703546a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Toolbars", +"Text": "An editor to customize the toolbars is available from \"Tools > Customize Toolbars\". The number of toolbars can be set, and the buttons on each can be defined." +}, +"format": "HEAD_PARA", +"uid": "f170364aa25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Fonts", +"Text": "The default font used in the application can be set using the \"Tools > Customize Fonts\" menu. There are also options to specify fonts used in the tree views, the output view and the editor views." +}, +"format": "HEAD_PARA", +"uid": "f1703744a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Tree Icons", +"Text": "There is an icons directory in the user configuration directory (\"~/.treeline-x.x/icons\" on Linux, \"Users\\<user>\\AppData\\roaming\\bellz\\treeline-x.x\\icons\" on Windows). Image files (PNG or BMP) placed into this directory are available for use as tree icons." +}, +"format": "HEAD_PARA", +"uid": "f1703848a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"67e95a5bbbf611e891f7a44cc8e97404", +"e8e10d33a14311e88e24a44cc8e97404", +"4bc47248786711e8a8bfa44cc8e97404", +"95a6e90a333f11e89efed66a6ab671cb", +"6df9a0c5ba3b11e78e7e3417ebd53aeb", +"f1703e7ea25a11e7b7c67054d2175f18", +"f1704752a25a11e7b7c67054d2175f18", +"f170527ea25a11e7b7c67054d2175f18", +"f170652aa25a11e7b7c67054d2175f18", +"f1706be2a25a11e7b7c67054d2175f18", +"f170748ea25a11e7b7c67054d2175f18", +"f1708032a25a11e7b7c67054d2175f18", +"f1708ae6a25a11e7b7c67054d2175f18", +"f170ab34a25a11e7b7c67054d2175f18", +"f170c1aaa25a11e7b7c67054d2175f18", +"f170e900a25a11e7b7c67054d2175f18", +"f170fddca25a11e7b7c67054d2175f18", +"f1710e58a25a11e7b7c67054d2175f18", +"f1711f24a25a11e7b7c67054d2175f18" +], +"data": { +"Name": "Revision History" +}, +"format": "HEADINGS", +"uid": "f1703d8ea25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f1703f8ca25a11e7b7c67054d2175f18", +"f170427aa25a11e7b7c67054d2175f18" +], +"data": { +"Name": "March 26, 2017 - Release 2.1.2 (unstable development snapshot)" +}, +"format": "HEADINGS", +"uid": "f1703e7ea25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f170407ca25a11e7b7c67054d2175f18", +"f1704176a25a11e7b7c67054d2175f18" +], +"data": { +"Name": "Notes" +}, +"format": "BULLET_HEADING", +"uid": "f1703f8ca25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Unstable snapshot", +"Text": "This is an unstable development snapshot of TreeLine. It may contains bugs. Testing and bug reports are appreciated, but the stable release (TreeLine 2.0.x) should probably be used for critical work." +}, +"format": "BULLETS", +"uid": "f170407ca25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Translations", +"Text": "The GUI and documentation translations have not yet been updated." +}, +"format": "BULLETS", +"uid": "f1704176a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f1704360a25a11e7b7c67054d2175f18", +"f170445aa25a11e7b7c67054d2175f18", +"f1704554a25a11e7b7c67054d2175f18", +"f170464ea25a11e7b7c67054d2175f18" +], +"data": { +"Name": "Bug Fixes" +}, +"format": "BULLET_HEADING", +"uid": "f170427aa25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "DateTime output", +"Text": "Fix problems formatting DateTime fields in output views." +}, +"format": "BULLETS", +"uid": "f1704360a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Internal links", +"Text": "Fix dialog issues with clicking on targets when creating inline internal links." +}, +"format": "BULLETS", +"uid": "f170445aa25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Data view tabbing", +"Text": "Fix a regression that prevented using the tab key to cycle between data edit view items." +}, +"format": "BULLETS", +"uid": "f1704554a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Autosave restore", +"Text": "Avoid opening two windows when restoring an auto-saved backup file." +}, +"format": "BULLETS", +"uid": "f170464ea25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f1704842a25a11e7b7c67054d2175f18", +"f1704b30a25a11e7b7c67054d2175f18", +"f1704d1aa25a11e7b7c67054d2175f18" +], +"data": { +"Name": "March 12, 2017 - Release 2.1.1 (unstable development snapshot)" +}, +"format": "HEADINGS", +"uid": "f1704752a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f1704928a25a11e7b7c67054d2175f18", +"f1704a2ca25a11e7b7c67054d2175f18" +], +"data": { +"Name": "Notes" +}, +"format": "BULLET_HEADING", +"uid": "f1704842a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Unstable snapshot", +"Text": "This is an unstable development snapshot of TreeLine. It may contains bugs. Testing and bug reports are appreciated, but the stable release (TreeLine 2.0.x) should probably be used for critical work." +}, +"format": "BULLETS", +"uid": "f1704928a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Translations", +"Text": "The GUI and documentation translations have not yet been updated." +}, +"format": "BULLETS", +"uid": "f1704a2ca25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f1704c20a25a11e7b7c67054d2175f18" +], +"data": { +"Name": "Updates" +}, +"format": "BULLET_HEADING", +"uid": "f1704b30a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Windows binary", +"Text": "The Windows binary is now built using Python 3.6 and PyQt 5.8." +}, +"format": "BULLETS", +"uid": "f1704c20a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f1704f54a25a11e7b7c67054d2175f18", +"f170506ca25a11e7b7c67054d2175f18", +"f1705166a25a11e7b7c67054d2175f18" +], +"data": { +"Name": "Bug Fixes" +}, +"format": "BULLET_HEADING", +"uid": "f1704d1aa25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Set icon crash", +"Text": "Fix a crash when changing the icon on the Type Config page of the Configure Data Types dialog box." +}, +"format": "BULLETS", +"uid": "f1704f54a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Type list issues", +"Text": "Fix inconsistent type selections on the Type List page of the Configure Data Types dialog box." +}, +"format": "BULLETS", +"uid": "f170506ca25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Window placement", +"Text": "Fix issues with restoring the placement of the TreeLine window in some multiple monitor setups." +}, +"format": "BULLETS", +"uid": "f1705166a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f170536ea25a11e7b7c67054d2175f18", +"f170565ca25a11e7b7c67054d2175f18", +"f170603ea25a11e7b7c67054d2175f18", +"f1706228a25a11e7b7c67054d2175f18" +], +"data": { +"Name": "February 20, 2017 - Release 2.1.0 (unstable development snapshot)" +}, +"format": "HEADINGS", +"uid": "f170527ea25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f170545ea25a11e7b7c67054d2175f18", +"f1705558a25a11e7b7c67054d2175f18" +], +"data": { +"Name": "Notes" +}, +"format": "BULLET_HEADING", +"uid": "f170536ea25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Unstable snapshot", +"Text": "This is an unstable development snapshot of TreeLine. It probably contains bugs. Testing and bug reports are appreciated, but the stable release (TreeLine 2.0.x) should probably be used for critical work." +}, +"format": "BULLETS", +"uid": "f170545ea25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Translations", +"Text": "The GUI and documentation translations have not yet been updated." +}, +"format": "BULLETS", +"uid": "f1705558a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f170574ca25a11e7b7c67054d2175f18", +"f1705850a25a11e7b7c67054d2175f18", +"f1705954a25a11e7b7c67054d2175f18", +"f1705a4ea25a11e7b7c67054d2175f18", +"f1705b48a25a11e7b7c67054d2175f18", +"f1705c4ca25a11e7b7c67054d2175f18", +"f1705d46a25a11e7b7c67054d2175f18", +"f1705e40a25a11e7b7c67054d2175f18", +"f1705f3aa25a11e7b7c67054d2175f18" +], +"data": { +"Name": "New Features" +}, +"format": "BULLET_HEADING", +"uid": "f170565ca25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Port to Qt5", +"Text": "TreeLine has been ported from the Qt4 library to the Qt5 library. The system requirements have been updated to Python 3.4 or higher, PyQt 5.4 or higher and Qt 5.4 or higher." +}, +"format": "BULLETS", +"uid": "f170574ca25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Cloned nodes", +"Text": "Cloned nodes that can duplicate sections of the tree have been added. Editing their data or child structure in one location changes all locations. They are created by copying nodes and then using the \"Edit->Paste Cloned Node\" command to paste them elsewhere. The clone link can be removed by deleting the nodes or by cutting them and pasting them back using the regular paste command." +}, +"format": "BULLETS", +"uid": "f1705850a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "DateTime field", +"Text": "Added a new DateTime field that combines dates and times into a single field that is useful for timestamps. The editor format is a combination of the date and time formats from the general options, separated by a space." +}, +"format": "BULLETS", +"uid": "f1705954a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "HTML in choice fields", +"Text": "Added an option to allow HTML tags in Choice, AutoChoice, Combination, AutoCombination and RegularExpression fields. The control is in the Field Config tab of the Configure Data Types dialog. This option is disabled by default. If enabled, special characters (angled brackets and quotation marks) will need to be escaped to show up in text." +}, +"format": "BULLETS", +"uid": "f1705a4ea25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Saved conditional rules", +"Text": "Saved rules for Conditional Find and Conditional Filter commands are listed directly in the dialog boxes, making them easier to load and save." +}, +"format": "BULLETS", +"uid": "f1705b48a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "CSV import/export", +"Text": "Added comma-delimited (CSV) table import and export, similar to existing tab-delimited table import and export. The export only handles data from a single level of child nodes." +}, +"format": "BULLETS", +"uid": "f1705c4ca25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Incremental title search", +"Text": "Added a quick, incremental search of node titles. By default, it's bound to ctrl+/. Then, matching titles are found as the search string is typed. The F3 and shift+F3 keys can be used to go to the next or previous matches, respectively." +}, +"format": "BULLETS", +"uid": "f1705d46a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Multiple field selection", +"Text": "Multiple fields can now be selected in the Output page of the Configuration dialog, so several fields can be added to the formats simultaneously." +}, +"format": "BULLETS", +"uid": "f1705e40a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Print settings", +"Text": "A pull-down selector for printers has been added to the Print Setup dialog. This enables TreeLine to verify that the page size and margin settings are supported by the current printer." +}, +"format": "BULLETS", +"uid": "f1705f3aa25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f170612ea25a11e7b7c67054d2175f18" +], +"data": { +"Name": "Updates" +}, +"format": "BULLET_HEADING", +"uid": "f170603ea25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "SpacedText Field special characters", +"Text": "The SpacedText Field now allows special HTML characters (angled brackets and quotation marks) without requiring them to be manually escaped." +}, +"format": "BULLETS", +"uid": "f170612ea25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f170630ea25a11e7b7c67054d2175f18", +"f1706426a25a11e7b7c67054d2175f18" +], +"data": { +"Name": "Bug Fixes" +}, +"format": "BULLET_HEADING", +"uid": "f1706228a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Printed page splits", +"Text": "The code that splits content into pages when printing has been improved. It now handles very long nodes better." +}, +"format": "BULLETS", +"uid": "f170630ea25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Formatted field title edits", +"Text": "When formatted fields (dates, times, etc.) are used in node titles, editing the titles in the tree or in the Title List pane will now work if the edit and output formats are similar." +}, +"format": "BULLETS", +"uid": "f1706426a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f1706610a25a11e7b7c67054d2175f18", +"f1706804a25a11e7b7c67054d2175f18" +], +"data": { +"Name": "October 3, 2015 - Release 2.0.2 (new stable release)" +}, +"format": "HEADINGS", +"uid": "f170652aa25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f1706700a25a11e7b7c67054d2175f18" +], +"data": { +"Name": "Bug Fixes" +}, +"format": "BULLET_HEADING", +"uid": "f1706610a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Data edit regression", +"Text": "Fixed a major regression in 2.0.1 that broke data editors for most specialized field types (number, math, boolean, choice, etc.)" +}, +"format": "BULLETS", +"uid": "f1706700a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f17068f4a25a11e7b7c67054d2175f18", +"f17069e4a25a11e7b7c67054d2175f18", +"f1706ae8a25a11e7b7c67054d2175f18" +], +"data": { +"Name": "Compatibility Notes" +}, +"format": "BULLET_HEADING", +"uid": "f1706804a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "File format", +"Text": "There are some file format changes between TreeLine 1.4.x and this version of TreeLine." +}, +"format": "BULLETS", +"uid": "f17068f4a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "File conversion", +"Text": "Older files opened in this version are automatically converted when saved." +}, +"format": "BULLETS", +"uid": "f17069e4a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "File compatibility", +"Text": "Files saved in this version may not be fully compatible with TreeLine 1.4.x." +}, +"format": "BULLETS", +"uid": "f1706ae8a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f1706cd2a25a11e7b7c67054d2175f18", +"f1706fb6a25a11e7b7c67054d2175f18" +], +"data": { +"Name": "September 26, 2015 - Release 2.0.1 (new stable release)" +}, +"format": "HEADINGS", +"uid": "f1706be2a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f1706db8a25a11e7b7c67054d2175f18", +"f1706ebca25a11e7b7c67054d2175f18" +], +"data": { +"Name": "Updates" +}, +"format": "BULLET_HEADING", +"uid": "f1706cd2a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Plugin options", +"Text": "Added methods to the plugin interface that allow general program options to be queried and changed." +}, +"format": "BULLETS", +"uid": "f1706db8a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Table import errors", +"Text": "Improve text table import error messages by including the line number where the problem is found." +}, +"format": "BULLETS", +"uid": "f1706ebca25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f170709ca25a11e7b7c67054d2175f18", +"f1707196a25a11e7b7c67054d2175f18", +"f1707290a25a11e7b7c67054d2175f18", +"f1707380a25a11e7b7c67054d2175f18" +], +"data": { +"Name": "Bug Fixes" +}, +"format": "BULLET_HEADING", +"uid": "f1706fb6a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Data edit undo", +"Text": "Reduce the amount of work that a single undo command removes from editors in the data edit view." +}, +"format": "BULLETS", +"uid": "f170709ca25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Configuration changes", +"Text": "Fixed a bug that prevented setting the unique ID reference field on a newly created data type." +}, +"format": "BULLETS", +"uid": "f1707196a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Legacy newline convert", +"Text": "Preserve hard newlines in text fields when converting TreeLine 1.4.x files to this version." +}, +"format": "BULLETS", +"uid": "f1707290a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Indent expand status", +"Text": "Fix problems preserving expand/collapse node states when indenting and unindenting nodes." +}, +"format": "BULLETS", +"uid": "f1707380a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f1707592a25a11e7b7c67054d2175f18", +"f1707c5ea25a11e7b7c67054d2175f18" +], +"data": { +"Name": "May 17, 2015 - Release 2.0.0 (new stable release)" +}, +"format": "HEADINGS", +"uid": "f170748ea25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f1707682a25a11e7b7c67054d2175f18", +"f170777ca25a11e7b7c67054d2175f18", +"f1707876a25a11e7b7c67054d2175f18", +"f1707966a25a11e7b7c67054d2175f18", +"f1707a60a25a11e7b7c67054d2175f18", +"f1707b5aa25a11e7b7c67054d2175f18" +], +"data": { +"Name": "Updates" +}, +"format": "BULLET_HEADING", +"uid": "f1707592a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Treepad import update", +"Text": "Modified the Treepad file import to use SpacedText fields to more closely match Treepad formatting." +}, +"format": "BULLETS", +"uid": "f1707682a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Treepad export", +"Text": "An optional plugin was written that can export files to the Treepad text file format." +}, +"format": "BULLETS", +"uid": "f170777ca25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "New icon", +"Text": "The TreeLine icon was replaced with a new one. Thanks to David Reimer for contributing the artwork." +}, +"format": "BULLETS", +"uid": "f1707876a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Translation updates", +"Text": "The German and Portuguese GUI translations were updated." +}, +"format": "BULLETS", +"uid": "f1707966a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Sample file updates", +"Text": "Updated the long text sample file to include the SpacedText field type, and added a conditional equation to the math sample file." +}, +"format": "BULLETS", +"uid": "f1707a60a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Documentation update", +"Text": "Updated the Math Field section of the documentation." +}, +"format": "BULLETS", +"uid": "f1707b5aa25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f1707d44a25a11e7b7c67054d2175f18", +"f1707e3ea25a11e7b7c67054d2175f18", +"f1707f38a25a11e7b7c67054d2175f18" +], +"data": { +"Name": "Compatibility Notes" +}, +"format": "BULLET_HEADING", +"uid": "f1707c5ea25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "File format", +"Text": "There are some file format changes between TreeLine 1.4.x and this version of TreeLine." +}, +"format": "BULLETS", +"uid": "f1707d44a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "File conversion", +"Text": "Older files opened in this version are automatically converted when saved." +}, +"format": "BULLETS", +"uid": "f1707e3ea25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "File compatibility", +"Text": "Files saved in this version may not be fully compatible with TreeLine 1.4.x." +}, +"format": "BULLETS", +"uid": "f1707f38a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f1708118a25a11e7b7c67054d2175f18", +"f17083fca25a11e7b7c67054d2175f18", +"f1708802a25a11e7b7c67054d2175f18" +], +"data": { +"Name": "March 29, 2015 - Release 1.9.7 (unstable development snapshot)" +}, +"format": "HEADINGS", +"uid": "f1708032a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f1708208a25a11e7b7c67054d2175f18", +"f1708302a25a11e7b7c67054d2175f18" +], +"data": { +"Name": "New Features" +}, +"format": "BULLET_HEADING", +"uid": "f1708118a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Math field conditions", +"Text": "Added comparison operators and conditional if statements to math field equations. The operators can be used with a new boolean result type, or as a part of numeric, date, time or text expressions." +}, +"format": "BULLETS", +"uid": "f1708208a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Math field text operations", +"Text": "Text operators were made available in math field equations, and the result type can be set to text. This allows math fields to combine text from other fields, replace sub-strings and change capitalization." +}, +"format": "BULLETS", +"uid": "f1708302a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f17084eca25a11e7b7c67054d2175f18", +"f17085f0a25a11e7b7c67054d2175f18", +"f17086fea25a11e7b7c67054d2175f18" +], +"data": { +"Name": "Updates" +}, +"format": "BULLET_HEADING", +"uid": "f17083fca25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Saved status bar message", +"Text": "Added a \"file saved\" status bar message." +}, +"format": "BULLETS", +"uid": "f17084eca25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "German translation", +"Text": "Added a German GUI translation." +}, +"format": "BULLETS", +"uid": "f17085f0a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Portuguese translation", +"Text": "Updated the Portuguese GUI translation." +}, +"format": "BULLETS", +"uid": "f17086fea25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f17088e8a25a11e7b7c67054d2175f18", +"f17089e2a25a11e7b7c67054d2175f18" +], +"data": { +"Name": "Bug Fixes" +}, +"format": "BULLET_HEADING", +"uid": "f1708802a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Combination editor", +"Text": "Fixed a focus problem that made Combination and AutoCombination field editor pull-downs unusable." +}, +"format": "BULLETS", +"uid": "f17088e8a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Choice editor", +"Text": "Fixed a focus problem that made the pull-downs in choice and boolean field editors unusable on Linux." +}, +"format": "BULLETS", +"uid": "f17089e2a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f1708bcca25a11e7b7c67054d2175f18", +"f17097aca25a11e7b7c67054d2175f18", +"f170a2eca25a11e7b7c67054d2175f18" +], +"data": { +"Name": "March 10, 2015 - Release 1.9.6 (unstable development snapshot)" +}, +"format": "HEADINGS", +"uid": "f1708ae6a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f1708cbca25a11e7b7c67054d2175f18", +"f1708db6a25a11e7b7c67054d2175f18", +"f1708ea6a25a11e7b7c67054d2175f18", +"f1708faaa25a11e7b7c67054d2175f18", +"f17090aea25a11e7b7c67054d2175f18", +"f17091e4a25a11e7b7c67054d2175f18", +"f17092dea25a11e7b7c67054d2175f18", +"f1709644a25a11e7b7c67054d2175f18" +], +"data": { +"Name": "New Features" +}, +"format": "BULLET_HEADING", +"uid": "f1708bcca25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "OneLineText field", +"Text": "Added a new OneLineText field type that restricts the text length to a single line." +}, +"format": "BULLETS", +"uid": "f1708cbca25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "All Types conditional test", +"Text": "Added an All Types option to the conditional find and filter commands. This allows multiple node types to be found or filtered at the same time. The fields from every type are available for use in conditions. The conditions give a false result for all node types that do not contain that field name." +}, +"format": "BULLETS", +"uid": "f1708db6a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Math field dates and times", +"Text": "Added support for date and time calculations using math fields. Math equation result types can be set to numeric, date or time output. Date fields can be subtracted to give the number of days elapsed, and numbers of days can be added to or subtracted from dates, resulting in new dates. Time fields can be subtracted to give the number of seconds elapsed, and numbers of seconds can be added to or subtracted from times, resulting in new times." +}, +"format": "BULLETS", +"uid": "f1708ea6a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Math field root references", +"Text": "In math fields, added an equation reference level to reference fields in the root node. This provides a place for \"constant\" field values that can be referenced from any node but only need to be changed in one location." +}, +"format": "BULLETS", +"uid": "f1708faaa25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Plugin interface", +"Text": "Support was added for plugin extension modules. Most of the interface methods from TreeLine 1.4.x were duplicated to ease porting of old plugins. Of course, old plugins must be ported from Python 2.x to Python 3.x, and there are multi-window implementation differences. New interfaces allow the creation of new field types and the execution of any menu command. Sample plugins are available on the TreeLine download page." +}, +"format": "BULLETS", +"uid": "f17090aea25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Automatic HTML tags", +"Text": "Added text formatting and link commands for HTML fields that add tags to the HTML content." +}, +"format": "BULLETS", +"uid": "f17091e4a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Open in folder", +"Text": "An open in folder command was added to external link field editors to open the directory in a file manager." +}, +"format": "BULLETS", +"uid": "f17092dea25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Select All command", +"Text": "Added the Select All command to global menus with a different default shortcut key (ctrl-L) to avoid conflicts with the add child shortcut." +}, +"format": "BULLETS", +"uid": "f1709644a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f170989ca25a11e7b7c67054d2175f18", +"f170998ca25a11e7b7c67054d2175f18", +"f1709a7ca25a11e7b7c67054d2175f18", +"f1709b8aa25a11e7b7c67054d2175f18", +"f1709c7aa25a11e7b7c67054d2175f18", +"f1709d6aa25a11e7b7c67054d2175f18", +"f1709e50a25a11e7b7c67054d2175f18", +"f1709f40a25a11e7b7c67054d2175f18", +"f170a026a25a11e7b7c67054d2175f18", +"f170a116a25a11e7b7c67054d2175f18", +"f170a206a25a11e7b7c67054d2175f18" +], +"data": { +"Name": "Updates" +}, +"format": "BULLET_HEADING", +"uid": "f17097aca25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Data edit view focus", +"Text": "Improved the focus handling for data edit view edit boxes. This eliminates the blue outline for boxes in inactive data edit views. It also makes tab-to-focus more predictable, including fully selecting single-line field types when they receive tab focus." +}, +"format": "BULLETS", +"uid": "f170989ca25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Data editor resize", +"Text": "Data edit boxes are now automatically resized when editing is complete and the focus moves to another row." +}, +"format": "BULLETS", +"uid": "f170998ca25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Drag external links", +"Text": "Allow files to be drag & dropped on data edit boxes to create external links." +}, +"format": "BULLETS", +"uid": "f1709a7ca25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Stay-on-top dialogs", +"Text": "Small, non-modal dialogs, such as those for sorting, numbering, finding and filtering, have been set to stay on top, so they won't be obscured by TreeLine windows." +}, +"format": "BULLETS", +"uid": "f1709b8aa25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Opening external links", +"Text": "Made opening associated programs from external file links more consistent, especially in Linux." +}, +"format": "BULLETS", +"uid": "f1709c7aa25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Copy types from file", +"Text": "The copy types from file command now supports encrypted and compressed files." +}, +"format": "BULLETS", +"uid": "f1709d6aa25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Wait cursors", +"Text": "Added wait cursors to TreeLine operations that could be time consuming." +}, +"format": "BULLETS", +"uid": "f1709e50a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Context menus", +"Text": "Improved the consistency of context menus and shortcut commands used in edit boxes." +}, +"format": "BULLETS", +"uid": "f1709f40a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Portuguese translation", +"Text": "Added a nearly complete Portuguese GUI translation." +}, +"format": "BULLETS", +"uid": "f170a026a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Sample file languages", +"Text": "Added support for sample TreeLine files to be provided in alternate languages." +}, +"format": "BULLETS", +"uid": "f170a116a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Documentation translation", +"Text": "Prepared the help file for translation into other languages," +}, +"format": "BULLETS", +"uid": "f170a206a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f170a3d2a25a11e7b7c67054d2175f18", +"f170a4b8a25a11e7b7c67054d2175f18", +"f170a5a8a25a11e7b7c67054d2175f18", +"f170a698a25a11e7b7c67054d2175f18", +"f170a77ea25a11e7b7c67054d2175f18", +"f170a86ea25a11e7b7c67054d2175f18", +"f170a95ea25a11e7b7c67054d2175f18", +"f170aa44a25a11e7b7c67054d2175f18" +], +"data": { +"Name": "Bug Fixes" +}, +"format": "BULLET_HEADING", +"uid": "f170a2eca25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Duplicate unique IDs", +"Text": "Corrupted TreeLine files with the same unique ID assigned to multiple nodes no longer fail to open. The user is warned that unique IDs have been updated, which could break some internal links." +}, +"format": "BULLETS", +"uid": "f170a3d2a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Conditional field list", +"Text": "Fixed missing fields in the pull-down list in the conditional find and filter dialog boxes when a rule was re-used after a node type change. " +}, +"format": "BULLETS", +"uid": "f170a4b8a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Math field missing references", +"Text": "Fixed problems with math fields that reference non-existing fields in parent or child nodes." +}, +"format": "BULLETS", +"uid": "f170a5a8a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Math field type updates", +"Text": "Made math fields update properly after node type changes." +}, +"format": "BULLETS", +"uid": "f170a698a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Rename bullet/table fields", +"Text": "Fixed inconsistent updates after renaming fields used with bulleted or tabled output." +}, +"format": "BULLETS", +"uid": "f170a77ea25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Copying descendants", +"Text": "Made node copy-paste and drag-and-drop work when the initial selection includes both parent and child nodes. " +}, +"format": "BULLETS", +"uid": "f170a86ea25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Tabbed text import", +"Text": "When importing a tabbed text file with multiple top-level nodes, create a single higher-level node to prevent failure." +}, +"format": "BULLETS", +"uid": "f170a95ea25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Custom toolbar dialog", +"Text": "Fixed the availability of the move up button in the customize toolbar dialog when the second command in the list is selected." +}, +"format": "BULLETS", +"uid": "f170aa44a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f170ac38a25a11e7b7c67054d2175f18", +"f170afeea25a11e7b7c67054d2175f18", +"f170b836a25a11e7b7c67054d2175f18" +], +"data": { +"Name": "December 31, 2014 - Release 1.9.5 (unstable development snapshot)" +}, +"format": "HEADINGS", +"uid": "f170ab34a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f170ad1ea25a11e7b7c67054d2175f18", +"f170ae0ea25a11e7b7c67054d2175f18", +"f170aefea25a11e7b7c67054d2175f18" +], +"data": { +"Name": "New Features" +}, +"format": "BULLET_HEADING", +"uid": "f170ac38a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Math field type", +"Text": "Added a math field type that is configured by defining an equation. The field value is automatically calculated based on references to numerical values in other nodes. See the \"sample_math_fields\" file for a usage example." +}, +"format": "BULLETS", +"uid": "f170ad1ea25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Clear formatting", +"Text": "Added a Clear Formatting command that removes font changes and links from data editor text." +}, +"format": "BULLETS", +"uid": "f170ae0ea25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "GUI Translations", +"Text": "Made source code changes necessary to support user interface translations into other languages. The actual translation work remains to be done." +}, +"format": "BULLETS", +"uid": "f170aefea25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f170b0caa25a11e7b7c67054d2175f18", +"f170b1baa25a11e7b7c67054d2175f18", +"f170b2aaa25a11e7b7c67054d2175f18", +"f170b390a25a11e7b7c67054d2175f18", +"f170b480a25a11e7b7c67054d2175f18", +"f170b570a25a11e7b7c67054d2175f18", +"f170b656a25a11e7b7c67054d2175f18", +"f170b746a25a11e7b7c67054d2175f18" +], +"data": { +"Name": "Updates" +}, +"format": "BULLET_HEADING", +"uid": "f170afeea25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Combination field scroll bars", +"Text": "Add scroll bars to the pull-down editors for combination fields with many entries." +}, +"format": "BULLETS", +"uid": "f170b0caa25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "External link truncation", +"Text": "Reduce the truncation of external link URLs when generating default display names." +}, +"format": "BULLETS", +"uid": "f170b1baa25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "File newlines", +"Text": "Use Unix-style newlines for saved TreeLine files to keep files consistent across platforms." +}, +"format": "BULLETS", +"uid": "f170b2aaa25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "HTML panes", +"Text": "Update the CSS code in exported HTML with navigation panes to improve the appearance in some browsers." +}, +"format": "BULLETS", +"uid": "f170b390a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Undo optimization", +"Text": "Optimize some undo information to reduce the amount of data in memory." +}, +"format": "BULLETS", +"uid": "f170b480a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Move sample files", +"Text": "Move sample files into a separate directory to avoid future translation conflicts." +}, +"format": "BULLETS", +"uid": "f170b570a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Installer clarification", +"Text": "Clarify a Linux installer error message when checking for the Python 3 version of PyQt." +}, +"format": "BULLETS", +"uid": "f170b656a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Include MSVC files", +"Text": "Include MSVCR DLL files in the Windows installer." +}, +"format": "BULLETS", +"uid": "f170b746a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f170b91ca25a11e7b7c67054d2175f18", +"f170ba0ca25a11e7b7c67054d2175f18", +"f170baf2a25a11e7b7c67054d2175f18", +"f170bbeca25a11e7b7c67054d2175f18", +"f170bcf0a25a11e7b7c67054d2175f18", +"f170bde0a25a11e7b7c67054d2175f18", +"f170bed0a25a11e7b7c67054d2175f18", +"f170bfc0a25a11e7b7c67054d2175f18", +"f170c0a6a25a11e7b7c67054d2175f18" +], +"data": { +"Name": "Bug Fixes" +}, +"format": "BULLET_HEADING", +"uid": "f170b836a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Applying config dialog settings", +"Text": "Fix problems applying multiple configuration changes while the Configure Data Types dialog box remains open." +}, +"format": "BULLETS", +"uid": "f170b91ca25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Title list line split", +"Text": "In the Title List editor, splitting a title into two lines now creates a new node without losing the children and parameters of the original node." +}, +"format": "BULLETS", +"uid": "f170ba0ca25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Title list undo", +"Text": "Fix the undo command in the Title List View so that deleted lines/nodes are properly restored. " +}, +"format": "BULLETS", +"uid": "f170baf2a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Sorting by fields", +"Text": "When sorting nodes by fields, properly handle a reverse direction." +}, +"format": "BULLETS", +"uid": "f170bbeca25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Find and replace", +"Text": "Fix problems with the find and replace command when a particular node type is specified." +}, +"format": "BULLETS", +"uid": "f170bcf0a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Conditional types", +"Text": "Fix problems defining conditional types from the Configure Data Types dialog box." +}, +"format": "BULLETS", +"uid": "f170bde0a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Apply font sizes", +"Text": "Correctly apply font size formatting to selections with mixed font sizes." +}, +"format": "BULLETS", +"uid": "f170bed0a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Editor height", +"Text": "Fix the height of long text field editors with customized data editor fonts." +}, +"format": "BULLETS", +"uid": "f170bfc0a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Base 64 image export", +"Text": "Fix HTML export of Base 64 images." +}, +"format": "BULLETS", +"uid": "f170c0a6a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f170c286a25a11e7b7c67054d2175f18", +"f170e068a25a11e7b7c67054d2175f18", +"f170e46ea25a11e7b7c67054d2175f18" +], +"data": { +"Name": "March 8, 2014 - Release 1.9.4 (unstable development snapshot)" +}, +"format": "HEADINGS", +"uid": "f170c1aaa25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f170d8d4a25a11e7b7c67054d2175f18", +"f170d9d8a25a11e7b7c67054d2175f18", +"f170dac8a25a11e7b7c67054d2175f18", +"f170dbb8a25a11e7b7c67054d2175f18", +"f170dca8a25a11e7b7c67054d2175f18", +"f170dd98a25a11e7b7c67054d2175f18", +"f170de88a25a11e7b7c67054d2175f18", +"f170df6ea25a11e7b7c67054d2175f18" +], +"data": { +"Name": "New Features" +}, +"format": "BULLET_HEADING", +"uid": "f170c286a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Documentation", +"Text": "Created new documentation, including a TreeLine file with details and a text file with basic usage instructions. Both are accessible from the help menu." +}, +"format": "BULLETS", +"uid": "f170d8d4a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Font settings", +"Text": "Added customizing of default fonts used in the tree, output and editor views." +}, +"format": "BULLETS", +"uid": "f170d9d8a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Spaced text field", +"Text": "Added a new SpacedText field type that holds plain text and preserves all spacing." +}, +"format": "BULLETS", +"uid": "f170dac8a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Combination editor", +"Text": "Combination and auto combination field types now use a simpler checkbox style pull-down editor." +}, +"format": "BULLETS", +"uid": "f170dbb8a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Autosave option", +"Text": "An autosave option was added." +}, +"format": "BULLETS", +"uid": "f170dca8a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Unique ID output", +"Text": "A unique ID reference field was added to the file info fields to allow node unique IDs to be included in output formats." +}, +"format": "BULLETS", +"uid": "f170dd98a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Paste plain text", +"Text": "A plain text paste command was added to paste non-formatted text to data editors." +}, +"format": "BULLETS", +"uid": "f170de88a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Indent option", +"Text": "Added an option to set the child indent offset amount." +}, +"format": "BULLETS", +"uid": "f170df6ea25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f170e162a25a11e7b7c67054d2175f18", +"f170e252a25a11e7b7c67054d2175f18", +"f170e36aa25a11e7b7c67054d2175f18" +], +"data": { +"Name": "Updates" +}, +"format": "BULLET_HEADING", +"uid": "f170e068a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Print preview size", +"Text": "The last size and position of the print preview window are remembered and restored." +}, +"format": "BULLETS", +"uid": "f170e162a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Page breaks in nodes", +"Text": "When printing, nodes with long text content are split between pages." +}, +"format": "BULLETS", +"uid": "f170e252a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Export relative links", +"Text": "In multiple page HTML exports, relative links in the content are adjusted based on the directory depth." +}, +"format": "BULLETS", +"uid": "f170e36aa25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f170e54aa25a11e7b7c67054d2175f18", +"f170e63aa25a11e7b7c67054d2175f18", +"f170e720a25a11e7b7c67054d2175f18", +"f170e810a25a11e7b7c67054d2175f18" +], +"data": { +"Name": "Bug Fixes" +}, +"format": "BULLET_HEADING", +"uid": "f170e46ea25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Blank nodes", +"Text": "Fixed problems outputting completely blank nodes." +}, +"format": "BULLETS", +"uid": "f170e54aa25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Conditional rules", +"Text": "Problems with the contains and true/false conditional rules were fixed." +}, +"format": "BULLETS", +"uid": "f170e63aa25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Link dialogs", +"Text": "Fixed issues displaying several editor link dialogs in quick succession." +}, +"format": "BULLETS", +"uid": "f170e720a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Output config cursor", +"Text": "In the configure data types dialog, the output format cursor no longer moves when switching to other field references." +}, +"format": "BULLETS", +"uid": "f170e810a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f170e9dca25a11e7b7c67054d2175f18", +"f170f12aa25a11e7b7c67054d2175f18", +"f170f846a25a11e7b7c67054d2175f18" +], +"data": { +"Name": "January 19, 2014 - Release 1.9.3 (unstable development snapshot)" +}, +"format": "HEADINGS", +"uid": "f170e900a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f170eac2a25a11e7b7c67054d2175f18", +"f170eba8a25a11e7b7c67054d2175f18", +"f170ec8ea25a11e7b7c67054d2175f18", +"f170ed74a25a11e7b7c67054d2175f18", +"f170ee5aa25a11e7b7c67054d2175f18", +"f170ef4aa25a11e7b7c67054d2175f18", +"f170f030a25a11e7b7c67054d2175f18" +], +"data": { +"Name": "New Features" +}, +"format": "BULLET_HEADING", +"uid": "f170e9dca25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Keyboard shortcuts", +"Text": "Added controls in the Tools menu for customizing TreeLine's keyboard shortcuts." +}, +"format": "BULLETS", +"uid": "f170eac2a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Toolbars", +"Text": "Controls for customizing TreeLine's toolbar buttons were added to the Tools menu." +}, +"format": "BULLETS", +"uid": "f170eba8a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Conditional search", +"Text": "New dialogs were created for conditional finding and filtering of nodes. Specific conditions can be applied to individual types and fields, and the conditions can be saved." +}, +"format": "BULLETS", +"uid": "f170ec8ea25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Find and replace", +"Text": "Find and replace functionality was added to search and change nodes' text data. The search can be limited to specific types and fields." +}, +"format": "BULLETS", +"uid": "f170ed74a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Table output", +"Text": "A new option for output data in tables was added to the Type Config pane of the Configure Data Types dialog. Each line of the output format becomes a column. Any text at the start of the format line that is followed by a colon becomes a table heading." +}, +"format": "BULLETS", +"uid": "f170ee5aa25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Bullet output", +"Text": "An option to add bullets to the output of child nodes was added to the Type Config pane of the Configure Data Types dialog." +}, +"format": "BULLETS", +"uid": "f170ef4aa25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Toggle selections", +"Text": "Added View > Previous Selection and Next Selection commands to step through the node selection history." +}, +"format": "BULLETS", +"uid": "f170f030a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f170f210a25a11e7b7c67054d2175f18", +"f170f2f6a25a11e7b7c67054d2175f18", +"f170f418a25a11e7b7c67054d2175f18", +"f170f4fea25a11e7b7c67054d2175f18", +"f170f5e4a25a11e7b7c67054d2175f18", +"f170f6d4a25a11e7b7c67054d2175f18" +], +"data": { +"Name": "Updates" +}, +"format": "BULLET_HEADING", +"uid": "f170f12aa25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Drop to reorder", +"Text": "Nodes can now be reordered by dragging and dropping them between sibling nodes." +}, +"format": "BULLETS", +"uid": "f170f210a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Date entry formats", +"Text": "The keyboard entry of dates and times into fields was made more flexible by allowing entries such as 4-digit years that don't exactly match the entry format." +}, +"format": "BULLETS", +"uid": "f170f2f6a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Default link names", +"Text": "Any text selection is now used as the default name for links inserted into text fields." +}, +"format": "BULLETS", +"uid": "f170f418a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Anchor links", +"Text": "Links to local named anchors in a node's HTML text content now work if they don't conflict with any node unique IDs." +}, +"format": "BULLETS", +"uid": "f170f4fea25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Line spacing", +"Text": "Line spacing in output views was made more consistent." +}, +"format": "BULLETS", +"uid": "f170f5e4a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Saved states", +"Text": "Improved the efficiency of restoring node open/close states when opening files." +}, +"format": "BULLETS", +"uid": "f170f6d4a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f170f936a25a11e7b7c67054d2175f18", +"f170fa1ca25a11e7b7c67054d2175f18", +"f170fb0ca25a11e7b7c67054d2175f18", +"f170fbf2a25a11e7b7c67054d2175f18", +"f170fce2a25a11e7b7c67054d2175f18" +], +"data": { +"Name": "Bug Fixes" +}, +"format": "BULLET_HEADING", +"uid": "f170f846a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Paste font sizes", +"Text": "Errors when pasting text with varying font sizes into data editors were fixed." +}, +"format": "BULLETS", +"uid": "f170f936a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Conditional data types", +"Text": "Various issues with conditional data types were fixed, including problems with pasting conditional nodes and prompt updating when the types change." +}, +"format": "BULLETS", +"uid": "f170fa1ca25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Drag undo", +"Text": "Problems with undoing the dragging and dropping of a node were fixed." +}, +"format": "BULLETS", +"uid": "f170fb0ca25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Expand branches", +"Text": "Fix the extremely slow operation of the View > Expand Full Branch and Collapse Full Branch commands." +}, +"format": "BULLETS", +"uid": "f170fbf2a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Printed lines", +"Text": "Fix improperly placed branch lines in printed output." +}, +"format": "BULLETS", +"uid": "f170fce2a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f170fec2a25a11e7b7c67054d2175f18", +"f171053ea25a11e7b7c67054d2175f18", +"f17108e0a25a11e7b7c67054d2175f18" +], +"data": { +"Name": "October 22, 2013 - Release 1.9.2 (unstable development snapshot)" +}, +"format": "HEADINGS", +"uid": "f170fddca25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f170ffa8a25a11e7b7c67054d2175f18", +"f171008ea25a11e7b7c67054d2175f18", +"f171017ea25a11e7b7c67054d2175f18", +"f1710264a25a11e7b7c67054d2175f18", +"f171034aa25a11e7b7c67054d2175f18", +"f1710430a25a11e7b7c67054d2175f18" +], +"data": { +"Name": "New Features" +}, +"format": "BULLET_HEADING", +"uid": "f170fec2a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Spell check", +"Text": "Added a spell check tool. This requires either aspell, ispell or hunspell to be installed." +}, +"format": "BULLETS", +"uid": "f170ffa8a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Numbering field type", +"Text": "Included a node numbering field type with several formatting options. An update numbering command fills in the sequence." +}, +"format": "BULLETS", +"uid": "f171008ea25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Add category level", +"Text": "Added a command to add a category level based on a subset of data fields." +}, +"format": "BULLETS", +"uid": "f171017ea25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Flatten by category", +"Text": "Added a flatten by category command to combine parent fields into child nodes." +}, +"format": "BULLETS", +"uid": "f1710264a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Flatten by link", +"Text": "A new flatten by link command flattens the structure and provides internal links to the former parent nodes." +}, +"format": "BULLETS", +"uid": "f171034aa25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Arrange by link", +"Text": "An arrange by link command restores the structure based on parent internal links." +}, +"format": "BULLETS", +"uid": "f1710430a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f171061aa25a11e7b7c67054d2175f18", +"f171070aa25a11e7b7c67054d2175f18", +"f17107f0a25a11e7b7c67054d2175f18" +], +"data": { +"Name": "Updates" +}, +"format": "BULLET_HEADING", +"uid": "f171053ea25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "URL drag-and-drop", +"Text": "Allow file URL drag-and-drop on active external link data edit widgets." +}, +"format": "BULLETS", +"uid": "f171061aa25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Share directory", +"Text": "Change the Linux installer to use the 'share' directory in place of 'lib' for python files." +}, +"format": "BULLETS", +"uid": "f171070aa25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Windows binary", +"Text": "Update the Windows binary to use version 3.3 of Python." +}, +"format": "BULLETS", +"uid": "f17107f0a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f17109bca25a11e7b7c67054d2175f18", +"f1710aa2a25a11e7b7c67054d2175f18", +"f1710b88a25a11e7b7c67054d2175f18", +"f1710c78a25a11e7b7c67054d2175f18", +"f1710d68a25a11e7b7c67054d2175f18" +], +"data": { +"Name": "Bug Fixes" +}, +"format": "BULLET_HEADING", +"uid": "f17108e0a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Output line sequence", +"Text": "Fix out of sequence output lines when output formats are longer than ten lines." +}, +"format": "BULLETS", +"uid": "f17109bca25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Node indenting", +"Text": "Fix problems with unique IDs and internal links when indenting and unindenting nodes." +}, +"format": "BULLETS", +"uid": "f1710aa2a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Widget focus problems", +"Text": "Avoid widget focus problems when editing data on conditional types." +}, +"format": "BULLETS", +"uid": "f1710b88a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Format cursor position", +"Text": "Maintain the output format cursor position when changing fields in the configure dialog." +}, +"format": "BULLETS", +"uid": "f1710c78a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "XML import", +"Text": "Fix importing of generic XML documents that have nodes with no data." +}, +"format": "BULLETS", +"uid": "f1710d68a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f1710f48a25a11e7b7c67054d2175f18", +"f17118a8a25a11e7b7c67054d2175f18" +], +"data": { +"Name": "May 2, 2013 - Release 1.9.1 (unstable development snapshot)" +}, +"format": "HEADINGS", +"uid": "f1710e58a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f171102ea25a11e7b7c67054d2175f18", +"f171111ea25a11e7b7c67054d2175f18", +"f171120ea25a11e7b7c67054d2175f18", +"f17112fea25a11e7b7c67054d2175f18", +"f17113eea25a11e7b7c67054d2175f18", +"f17114dea25a11e7b7c67054d2175f18", +"f17115d8a25a11e7b7c67054d2175f18", +"f17116c8a25a11e7b7c67054d2175f18", +"f17117b8a25a11e7b7c67054d2175f18" +], +"data": { +"Name": "Updates" +}, +"format": "BULLET_HEADING", +"uid": "f1710f48a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Compression and encryption", +"Text": "Added TreeLine file compression and file encryption, controlled from a File > Properties dialog box." +}, +"format": "BULLETS", +"uid": "f171102ea25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Restore tree states", +"Text": "Tree node open/close states are restored for recent files." +}, +"format": "BULLETS", +"uid": "f171111ea25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Derived data types", +"Text": "Added derived data types that keep the field list of their generic type." +}, +"format": "BULLETS", +"uid": "f171120ea25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Conditional type setting", +"Text": "Added conditional type setting that changes icons or output format based on field contents." +}, +"format": "BULLETS", +"uid": "f17112fea25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Other field references", +"Text": "Other field references (file info, ancestors, children) can be used in node output formats and in print headers & footers." +}, +"format": "BULLETS", +"uid": "f17113eea25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "HTML file export", +"Text": "Added an HTML file export to a single file with a navigation pane on the side." +}, +"format": "BULLETS", +"uid": "f17114dea25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Embedded blank lines", +"Text": "Allow embedded blank lines in non-HTML node output formats." +}, +"format": "BULLETS", +"uid": "f17115d8a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Separator config option", +"Text": "Added an output separator config option for combination fields and child references." +}, +"format": "BULLETS", +"uid": "f17116c8a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "First day of week option", +"Text": "Add an option to set the first day of the week for calendar widgets." +}, +"format": "BULLETS", +"uid": "f17117b8a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f1711998a25a11e7b7c67054d2175f18", +"f1711a88a25a11e7b7c67054d2175f18", +"f1711b6ea25a11e7b7c67054d2175f18", +"f1711c54a25a11e7b7c67054d2175f18", +"f1711d44a25a11e7b7c67054d2175f18", +"f1711e2aa25a11e7b7c67054d2175f18" +], +"data": { +"Name": "Bug Fixes" +}, +"format": "BULLET_HEADING", +"uid": "f17118a8a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Drag multiple nodes", +"Text": "Fixed problems with pasting or dragging multiple nodes." +}, +"format": "BULLETS", +"uid": "f1711998a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Remove fields from multi-line output formats", +"Text": "Made removing fields from multi-line output formats work properly." +}, +"format": "BULLETS", +"uid": "f1711a88a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Prevent duplicate unique IDs", +"Text": "Prevent duplicate unique IDs from being created after undoing the deletion of a branch." +}, +"format": "BULLETS", +"uid": "f1711b6ea25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Fixed non-text nodes sorting", +"Text": "Fixed node sorting of non-text nodes (numbers, dates, times, etc.)" +}, +"format": "BULLETS", +"uid": "f1711c54a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Calendar widgets", +"Text": "Avoid placing calendar widgets partially off screen if near the bottom." +}, +"format": "BULLETS", +"uid": "f1711d44a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Automatic cell height", +"Text": "Limit the automatic height increases for text edit cells to avoid confusing double scroll bars." +}, +"format": "BULLETS", +"uid": "f1711e2aa25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f171200aa25a11e7b7c67054d2175f18" +], +"data": { +"Name": "February 6, 2013 - Release 1.9.0 (unstable development snapshot)" +}, +"format": "HEADINGS", +"uid": "f1711f24a25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f17120e6a25a11e7b7c67054d2175f18", +"f17121d6a25a11e7b7c67054d2175f18", +"f17122c6a25a11e7b7c67054d2175f18", +"f17123aca25a11e7b7c67054d2175f18", +"f171249ca25a11e7b7c67054d2175f18", +"f171258ca25a11e7b7c67054d2175f18", +"f1712690a25a11e7b7c67054d2175f18", +"f1712776a25a11e7b7c67054d2175f18", +"f1712866a25a11e7b7c67054d2175f18", +"f1712960a25a11e7b7c67054d2175f18", +"f1712a46a25a11e7b7c67054d2175f18", +"f1712b36a25a11e7b7c67054d2175f18", +"f1712c1ca25a11e7b7c67054d2175f18", +"f1712d02a25a11e7b7c67054d2175f18", +"f1712df2a25a11e7b7c67054d2175f18", +"f1712ed8a25a11e7b7c67054d2175f18", +"f1712fbea25a11e7b7c67054d2175f18", +"f1713194a25a11e7b7c67054d2175f18", +"f1713284a25a11e7b7c67054d2175f18", +"f171336aa25a11e7b7c67054d2175f18" +], +"data": { +"Name": "New Features" +}, +"format": "BULLET_HEADING", +"uid": "f171200aa25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Full Python 3 rewrite", +"Text": "TreeLine has been fully rewritten using Python 3." +}, +"format": "BULLETS", +"uid": "f17120e6a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Direct model-view and ElementTree", +"Text": "Improved performance due to direct use of model-view classes for views and ElementTree for input/output." +}, +"format": "BULLETS", +"uid": "f17121d6a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Table based data editor pane", +"Text": "A table based data editor pane (much faster)." +}, +"format": "BULLETS", +"uid": "f17122c6a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "WYSIWYG data editor view.", +"Text": "WYSIWYG formatting in the data editor view." +}, +"format": "BULLETS", +"uid": "f17123aca25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Real-time window updates ", +"Text": "Real-time updates of the same file shown in multiple windows." +}, +"format": "BULLETS", +"uid": "f171249ca25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Improved printing", +"Text": "Improved printing and print preview performance." +}, +"format": "BULLETS", +"uid": "f171258ca25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Saves print options", +"Text": "Saves print options with the TreeLine file." +}, +"format": "BULLETS", +"uid": "f1712690a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Built-in PDF", +"Text": "Built-in print to PDF function." +}, +"format": "BULLETS", +"uid": "f1712776a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Searching options.", +"Text": "More searching options." +}, +"format": "BULLETS", +"uid": "f1712866a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Filtering command", +"Text": "A filtering command shows matches in a simple list." +}, +"format": "BULLETS", +"uid": "f1712960a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Saved sorting parameters", +"Text": "Sorting parameters can be saved with each data type." +}, +"format": "BULLETS", +"uid": "f1712a46a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Basic text field type", +"Text": "The basic text field type allows formatting, preserves line breaks and allows HTML restricted characters." +}, +"format": "BULLETS", +"uid": "f1712b36a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Calendar widget", +"Text": "A calendar widget can be used for editing date fields." +}, +"format": "BULLETS", +"uid": "f1712c1ca25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Easy internal links", +"Text": "Internal link fields and inline internal links are easier to use." +}, +"format": "BULLETS", +"uid": "f1712d02a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Unified external links", +"Text": "An external link type supports http, https, file and mailto protocols." +}, +"format": "BULLETS", +"uid": "f1712df2a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Support relative paths", +"Text": "Relative paths are supported for external links and pictures." +}, +"format": "BULLETS", +"uid": "f1712ed8a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Regular expression field type", +"Text": "A regular expression field type can match patterns (phone numbers, email addresses, etc.)" +}, +"format": "BULLETS", +"uid": "f1712fbea25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Node IDs automatically generated", +"Text": "Unique node IDs are automatically generated and updated." +}, +"format": "BULLETS", +"uid": "f1713194a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "New windows installer", +"Text": "A new windows installer allows a single-user, non-administrator install." +}, +"format": "BULLETS", +"uid": "f1713284a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Portable installs", +"Text": "Includes better support for portable installs." +}, +"format": "BULLETS", +"uid": "f171336aa25a11e7b7c67054d2175f18" +}, +{ +"children": [ +"f171354aa25a11e7b7c67054d2175f18", +"f1713644a25a11e7b7c67054d2175f18", +"f171372aa25a11e7b7c67054d2175f18" +], +"data": { +"Name": "Contacts" +}, +"format": "HEADINGS", +"uid": "f1713464a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Mailing list", +"Text": "There is a low-volume mailing list for users to discuss anything and everything about TreeLine. This is the place for development discussions (from roadmaps to feature suggestions to beta testing), release announcements, bug reports, and general user discussions (from new uses to tips & tricks to configuration samples).
\n
\nTo subscribe, go to lists.sourceforge.net/lists/listinfo/treeline-users
\n" +}, +"format": "HEAD_PARA", +"uid": "f171354aa25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Email", +"Text": "If you do not wish to subscribe to the mailing list, I can be contacted by email at: doug101 AT bellz DOT org " +}, +"format": "HEAD_PARA", +"uid": "f1713644a25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Updates", +"Text": "I welcome any feedback, including reports of any bugs you find. Also, you can periodically check back to treeline.bellz.org for any updates." +}, +"format": "HEAD_PARA", +"uid": "f171372aa25a11e7b7c67054d2175f18" +}, +{ +"children": [], +"data": { +"Name": "Error handling", +"Text": "Error handling has been improved. For most errors, a dialog box is shown with debugging information that can be copied into an email." +}, +"format": "BULLETS", +"uid": "f5983b38bb0411e795e13417ebd53aeb" +}, +{ +"children": [], +"data": { +"Name": "XML export", +"Text": "Fixed problems with generic XML export from multiple root nodes." +}, +"format": "BULLETS", +"uid": "f817e5b6334a11e89349d66a6ab671cb" +}, +{ +"children": [], +"data": { +"Name": "Spanish translation", +"Text": "Added a Spanish GUI translation (thanks to Diego)." +}, +"format": "BULLETS", +"uid": "f966db0cc23f11e8b80fd66a6ab671cb" +} +], +"properties": { +"tlversion": "3.0.1", +"topnodes": [ +"f16f7d90a25a11e7b7c67054d2175f18" +] +} +} \ No newline at end of file diff -Nru treeline-2.0.2/i18n/translations/documentation_xx.trl treeline-3.0.1/i18n/translations/documentation_xx.trl --- treeline-2.0.2/i18n/translations/documentation_xx.trl 2018-04-08 20:12:57.000000000 +0000 +++ treeline-3.0.1/i18n/translations/documentation_xx.trl 1970-01-01 00:00:00.000000000 +0000 @@ -1,792 +0,0 @@ - - -TreeLine Documentation - - -Introduction - -Version -This document covers TreeLine, Version 2.0.2, released October 3, 2015 by Doug Bell. - - - - -Why TreeLine? -Do you have lots of sticky notes lying around with various useful information jotted down? Or many lists of books, movies, links, website logins, personal contacts, or things to do? Can you find them when you need them? Well, I often couldn't. So here's my answer. - - -Outliner vs. database -Treeline is both an outliner and a small database. It stores almost any kind of information. A tree structure makes it easy to keep things organized. And each node in the tree can contain several fields, forming a database. The output format for each node can be defined, and the output can be shown on the screen, printed, or exported to HTML or text. - - -License -Since I'm not in the software business, I'm making this program free for anyone to use, distribute and modify, as long as it stays non-proprietary. TreeLine is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either Version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY. See the LICENSE file provided with this program for more information. - - - -Features - -General - - -Store information -Stores almost any type of information, including plain text, rich text, HTML, numbers, dates, times, booleans, URLs, etc. - - - - -Tree structure -The tree structure helps keep things organized. - - -Fields -Each node can have several fields that form a mini-database. - - -Node types -Several node types, with different sets of fields, can be included in one file. - - -Output format -The node format, including fields, output lines, formatting and tree-view icon, can be defined for each node type. - - - -Views - -Tree view -The left-hand view shows an indented list of titles - - -Right-hand views -The right-hand view can show one of three views - for showing output, editing node data and editing node titles. - - -Show parent and children -The right-hand view is normally split to show data from the parent node and its children. - - -Show multiple selection -If multiple nodes are selected, the right-hand view shows all of their data. - - -Show descendant output -The output view can be set to show indented output from all descendant nodes. - - - -Navigation - -Find commands -Find commands can search node data for text or for more specific rules. - - -Filtering -Filtering commands show only matching nodes in a flat left-hand view. - - -Toggle selections -Previous and next selection commands toggle selections to quickly move between parts of the tree. - - - -Formatting - -Configuration dialog -The dialog for data type configuration has several tabs to easily set all type, field and output parameters. - - -Format copies -Formatting information can be copied from another TreeLine file. - - - -File Handling - -Undo/redo -Undo and redo commands are available for all modifying operations. - - -File formats -TreeLine files are XML by default, with options for automatically compressing or encrypting the files. - - -Document templates -Document templates for new files are preformatted to cover basic needs. - - -Printing -The formatted output can be printed with parent/child lines, headers and footers. - - - -File Import and Export - -HTML export -The data can be exported to single or multiple HTML files with optional navigation panes. - - -Text import/export -Plain text, tab-indented text and tab-delimited table files can be imported and exported. - - -Bookmark import/export -Mozilla and XBEL format bookmark files can be imported and exported. - - -Generic XML import/export -Generic XML files can be imported and exported, allowing TreeLine to function as a crude XML editor. - - -ODF import/export -ODF text documents can be imported and exported as outlines. - - - -Linking - -Internal links -Internal link fields toggle the selection to a linked node when clicked. - - -External links -External link fields can be used to open URLs in web browsers. - - -Embedded links -Both internal and external links can be embedded into text fields. - - - -Data Manipulation - -Sorting -Nodes can be sorted by title or by predefined node type criteria. - - -Math Fields -Math fields can be defined that automatically calculate their contents based on numerical values in other nodes. - - -Numbering -Numbering fields can be defined and automatically updated. - - -Conditional types -A node's icon and output format can be changed conditionally based on its data. - - -Spell check -Text data can be spell checked (requires an external program - see the <a href="#system_requirements">System Requirements</a> section). - - -Arranging data -Data can be automatically arranged using either parent references or categories from data fields. - - - -Customization - -Customization -There are many options for customizing both general and file-based attributes. - - -Shortcuts and toolbars -There are editors for keyboard shortcuts and toolbar commands. - - -Fonts -Fonts used for editing and output can be customized. - - -Languages -The user interface is available in English, German and Portuguese. - - -Plugins -Plugin extension modules can be loaded to further customize TreeLine. - - - - -System Requirements - -Linux - -Python -Python (Version 3.2 or higher) - - -Qt -QT (Version 4.6, 4.7 or 4.8) - - -PyQt -PyQt (Version 4.8.x, 4.9.x, 4.10.x or 4.11.x) - - -Spell check -If spell checking is desired, either aspell, ispell or hunspell are required - - - -Windows - -Binary -Should run on any computer running Windows XP, Vista, 7 or 8. - - -Spell check -If spell checking is desired, an external program is required. Either aspell, ispell or hunspell must be installed. - - - - -Installation - -Linux - -Basic installation -Extract the source files from the treeline tar file, then change to the TreeLine directory in a terminal. For a basic installation, simply execute the following command as root: "python install.py". - - -Python 3 -If your distribution defaults to Python 2.x, you may need to substitute "python3", "python3.2" or "python3.3" for "python" in these commands. - - -Install options -To see all install options, use: "python install.py -h". To install TreeLine with a different prefix (the default is /usr/local), use: "python install.py -p /prefix/path". - - - -Windows - -All users -To install for all users, execute the "TreeLine-x.x.x-install-all.exe" file. Administrator permissions are required. - - -Single user -To install for a single user (administrator rights are not required), execute the "TreeLine-x.x.x-install-user.exe" file. - - -Portable install -For a portable install, execute the "TreeLine-x.x.x-install-user.exe" file. The file association, shortcuts and uninstaller tasks should be unchecked. When TreeLine starts and prompts for the config file location, choose the program directory option. - - - - -Basic Usage - -Views - -Tree View -The left-hand view shows a tree of node titles. Parent nodes can be opened and closed to display or hide their indented descendant nodes. Clicking on an already selected node allows the title to be edited. Right-click context menus are available for commonly used functions. - - - - -Right-hand Views -The right pane is tabbed to show one of three different views of the data. The "Data Output" view shows the formatted text, the "Data Edit" view shows a text edit boxes, and the "Title List" view shows an editable list of node titles.<br /> -<br /> -When a parent node is selected in the tree, the right view will default to showing information about the selected node in an upper pane and information about the selected node's children in a lower pane. The "View &gt; Show Child Pane" command will toggle the display of the child nodes. If the selected node has no children, the view will show a single pane with information about the selected node only.<br /> -<br /> -When multiple nodes are selected in the tree (by holding down the shift or Ctrl keys while clicking), the right view will not display any child node information. It will instead show information about every selected node. - - -Data Output View -The "Data Output" view shows formatted output text. It cannot be edited from this view.<br /> -<br /> -When the "View &gt; Show Output Descendants" command is toggled, the "Data Output" view will show an indented list with information about every descendant of a single selected node. - - -Data Edit View -The "Data Edit" view shows a text edit box for each data field within a node. It also shows the node types and the node titles. The types of edit boxes vary based on the field type. Some are just text editors, while others (such as choice fields, date fields, links, etc.) have pull-down menus or dialogs. - - -Title List View -The "Title List" view shows a list of node titles that can be modified using typical text editor methods. If a new line is typed, a new node is created with that title. If a line is deleted, the corresponding node is removed from the tree. - - - -Editing - -Node Menu -The commands in the "Node" menu operate on the selected nodes in the left tree view. There are commands to add or insert nodes, rename node titles and delete nodes. There are also commands to rearrange the tree by changing indent levels or moving nodes up or down. For many of the commands, the descendants of the selected nodes are also affected. - - -Edit Menu -The edit menu includes undo and redo commands that can fix problems. Cut, copy and paste commands can operate either on text in the right-hand view (if selected or active) or to tree nodes. It also has text formatting commands that are active when using edit boxes in the Data Edit view. - - -Shortcuts -There are several shortcuts for use in tree editing. Drag and drop will move (or copy if the Ctrl button is held) nodes. Clicking on a selected node will rename it. Pressing the delete key will remove the selected nodes. If desired, these shortcuts can be disabled in "Tools &gt; General Options". - - - -Files - -Templates -When starting a new file, a dialog box offers a choice of templates. The default has only a single text field for each node that contains the title. The Long Text template adds a second long text field for more output text. Other templates have various fields for contacts, book lists and to-do lists. - - -Sample Files -Various TreeLine sample files can be opened by using the "File &gt; Open Sample" command. These have more detail and example content than the new file templates. - - - -Data Types - -Node Types -Multiple node data types can be defined in a TreeLine file. Each can contain different data fields and have different output formats. See the template and sample files for examples. Nodes can be set to a specific type using the "Data &gt; Set Node Type" command. - - -Type Config -The "Data &gt; Configure Data Types" command is used to modify node data types, fields and output formatting. Refer to the Detailed Usage section of the full documentation for details. - - - - -Detailed Usage - -Tree Navigation and Search - -Keyboard Shortcuts -There are several keyboard commands that can be used for tree navigation. The up and down arrow keys move the selection. The left and right arrows open and close the current node. Holding the CTRL and SHIFT keys with "J" or "K" moves between siblings, skipping children. The "Home", "End", "Page Up" and "Page Down" keys can be used to move quickly through the tree. The CTRL and SHIFT keys can be held with the "Page Up" and "Page Down" keys to move the tree view even when a different pane is focused.<br /> -<br /> -Another way to move through the tree is to type the first letter of a visible node title. Hitting the letter again moves to the next possibility. - - -Selection -Multiple nodes can be selected by holding down the CTRL or the SHIFT key when changing the active node. Individual nodes are added or removed from the selection when the CTRL key is held. The selection of all nodes between the old and new active nodes are toggled when SHIFT is held. The active node can be changed by using the mouse or by using any of the keyboard navigation methods.<br /> -<br /> -Note that multiple nodes selected with the CTRL key are output in the order selected.<br /> -<br /> -The "View &gt; Previous Selection" and "View &gt; Next Selection" commands can be used to toggle through a history of selections, allowing faster navigation through the tree. - - -Searching -The "Tools &gt; Find Text" command will search for text within the tree structure. The dialog box has options for searching all of the node data or only the node titles. There are also options for how to interpret the search text. Key words will match nodes with the search words found anywhere in the node. Key full words will only match complete words anywhere in the node. Full phrase will only match the complete phrase in the proper sequence. Finally, the regular expression option will search using Python regular expressions.<br /> -<br /> -The "Tools &gt; Conditional Find" command will search in particular node types and node fields. Various comparison operators can be selected to exactly match, to match a greater or lesser value, or part of the value. And the True/False operators give the same result regardless of the values. In general, the value is interpreted using the edit format for special field types. Multiple rules can be added, connected with logical "and" or "or" operators. The "All Types" option makes fields from every type are available, so that multiple node types to be part of the same search. The condition will be false for node types that do not contain that field name. - - -Filtering -There are two filtering commands, "Tools &gt; Text Filter" and "Tools &gt; Conditional Filter". They work like the corresponding search commands above, except that they show all of the matching nodes in a flat list that replaces the tree view. The nodes can be selected and edited from this view. Use the "End Filter" button to restore the full tree view. - - - -Defining Node Types - -Type List -The "Type List" is the first tab of the "Data &gt; Configure Types Dialog". The list of data types can be modified by the buttons on the right. New types can be added, and existing types can be copied, renamed or deleted. - - -Type Config -"Type Config" is the second tab of the Configure Types Dialog. It contains a selection for the default child type. If set, this will be the initial type used for new children with this type of parent. If set to "[None]", children will default to either the type of their siblings or their parent.<br /> -<br /> -The "Change Icon" button allows the selection of a custom tree icon for this data type. The "Clear Select" button on the icon dialog can be used to set the icon to "None", so that no icon will be displayed for this type. To avoid showing any tree icons, the "Show icons in the tree view" general option can be unset.<br /> -<br /> -There are also options here for adding blanks lines between nodes, allowing HTML tags in the common format text, and changing the output to add bullets or tables. - - -Field List -The "Field List" is the third tab of the Configure Types Dialog. The list of fields within a data type can be modified by using the buttons on the right. New fields can be added, and existing fields can be moved, renamed or deleted. - - -Field Config -"Field Config" is the fourth tab of the Configure Types Dialog. The field type and its output format string can be set, if applicable to the field. Extra prefix and suffix text to be output with the field can also be set, and a default field value for new nodes can be entered. Finally, the number of lines displayed in the editor for the field can be specified. - - -Output -"Output" is the last tab of the Configure Types Dialog. The left half of the dialog shows the fields. The right half shows the formatting for the title (used for the node text in the tree view) and the node output. The formatting consists of text lines with embedded fields. The fields are shown as "{*field_name*}". The field that is selected in the list can be added to a format at the cursor position with the "&gt;&gt;" keys. The field reference at the cursor can be removed with the "&lt;&lt;" keys. - - - -Field Types - -Field Options -The field type and options are set in the "Field Config" tab of the "Data &gt; Configure Types Dialog". The many different field types are described in the paragraphs below.<br /> -<br /> -Several of the field types use a formatting string to define their output. For a list of available formatting characters, use the "Format Help" button. Entries in the data editor which do not match the format will cause a blue triangle to show in the upper left corner of the edit box, and the output for that field will be replaced with "#####". - - -Text Type -The default field type is a text field. It is the most commonly used field. These fields are edited using edit boxes in the data editor view. There are several commands in the Edit menu (and also in the context menu) for setting the font style and adding external or internal links. The edit box height expands when re-displayed after adding several lines of text. The minimum edit box height can also be set explicitly in the "Field Config" tab. - - -HTML Text Type -This type allows simple HTML tags to be used in the text. Commonly used tags include "&lt;b&gt;bold&lt;/b&gt;", "&lt;u&gt;underline&lt;/u&gt;", "line break&lt;br/&gt;", "horizontal line&lt;hr/&gt;", and various font tags. Complex block tags should generally be avoided. Carriage returns are ignored and non-escaped "&lt;", "&gt;" and "&amp;" symbols do not display. - - -One Line Text Type -This type restricts the text length to a single line. It does not allow carriage returns, but does not restrict line wrapping of a single long line. - - -Spaced Text Type -This type holds plain text and preserves all spacing. Other formatting of the text is not permitted. It could be useful to use the "Tools &gt; Customize Fonts" command to set the editor font to a mono-spaced font when using this field type. - - -Number Type -In the number type, special characters in the format define the display of the numbers. The format uses a string of "#" (optional digit) and "0" (required digit) characters to define the output formatting. For example, pi formatted with "#.#" is "3.1" and formatted with "00.00" is "03.14". Regardless of the formatting, digits to the left of the decimal point are not truncated, since that would display an incorrect result. But use care to show enough decimal places (either optional or required) to avoid problems with round-off error.<br /> -<br /> -The radix character can be specified as either "." or "," to handle internationalization. For use as a thousands separator, use "\," or "\.". For example, a large number may be formatted as "#\,###\,###.##" or as "#\.###\.###,##". Press the "Format Help" button from the field format dialog for more formatting details. <br /> -<br /> -Unlike most other formats, the number type also uses the output format for display in the Data Editor. Of course, any new entry with a reasonable format is correctly interpreted (but the correct radix character must be used). - - -Boolean Type -This type gives two choices corresponding to true/false values. The format help menu includes typical values such as "yes/no", "true/false" and "1/0", but users can also enter their own word pair. The data editor boxes will accept either the currently set format or any of the typical values. - - -Date Type -In the date field type, special characters in the format are replaced by elements of the data, similar to number fields. Press the "Format Help" button from the field format dialog for formatting details. <br /> -<br /> -There is also an edit format under "Tools &gt; General Options &gt; Data Editor Formats". This controls how date fields are displayed in the Data Editor view. Generally, entries in the data editor with various formats will be correctly interpreted regardless of this setting, but dates must use the correct day-month-year sequence. Also note that the date editor format does not support days of the week.<br /> -<br /> -A default initial field value of "Now" can be used to enter the date of node creation. - - -Time Type -In the time field type, special characters in the format are replaced by elements of the data, similar to number fields. Press the "Format Help" button from the field format dialog for formatting details. <br /> -<br /> -There is also an edit format under "Tools &gt; General Options &gt; Data Editor Formats". This controls how time fields are displayed in the Data Editor view. Generally, entries in the data editor with various formats will be correctly interpreted regardless of this setting.<br /> -<br /> -A default initial field value of "Now" can be used to enter the time of node creation. - - -Choice Type -The choice field type allows for the selection of text items from a pull-down edit list. The formatting strings for these types list the items separated with the "/" character (use "//" to get a literal "/" in an item). Entries in the data editor which do not match the choices will cause a blue triangle to show in the upper left corner of the edit box, and the output for that field will be replaced with "#####". - - -Auto Choice Type -This field type is similar to the choice type, but without a format string. The entries in the pull-down menu are automatically generated from all previously used entries. Any entries that are typed will be available in the pull-down menu for future use. - - -Combination Type -This is basically the equivalent of the choice type with multiple selection. The formatting string entries are separated by the "/" character. The pull-down menu shows check-boxes that are checked for currently selected nodes. By default, the selected entries are output separated by a comma and a space. This can be changed in the "Type Config" tab of the "Data &gt; Configure Types Dialog". Click the "Show Advanced" button to see the separator setting. - - -Auto Combination Type -This field type is similar to the combination type, but without a format string. The entries in the pull-down menu are automatically generated from all previously used entries. Any entries that are typed will be available in the pull-down menu for future use. - - -External Link Type -This type can support various link protocols, including http and https for web pages, mailto for email addresses and file for local files. A pull down dialog in the data editor allows the selection of the protocol type and the entry of an address and a display name. In the edit boxes, the display name shows up in [brackets], and it is used as the text of the link in the output view. The "file" protocol also provides a button to browse for a path and buttons to choose either absolute or relative path names.<br /> -<br /> -Clicking on the link in the output view or choosing "Open Link" from the right-click context menu in the edit box will open the link in a web browser. In Linux, setting the "BROWSER" environment variable to a string like "mozilla %s" will result in the desired browser being used. - - -Internal Link Type -These links create shortcuts to select nodes elsewhere in the tree structure. The unique ID of the referenced node is entered along with a display name. The pull down dialog in the data editor has an "Enable click on target" button. Once clicked, the target node can be selected and the appropriate unique node ID will be entered. Like external links, this link can be clicked in the output view, or "Open Link" can be chosen from the right-click context menu. - - -Picture Type -These links add referenced pictures to the output view. A pull down dialog in the data editor has a button to browse for picture files to be linked. It also allows absolute or relative paths to be used and has a small image preview. - - -Math Type -Math field types are configured by defining equations. The equations can reference number fields, date fields, time fields, boolean fields, text fields and/or other math fields. The resulting values of math fields are automatically calculated for each node.<br /> -<br /> -To define a math field equation, press the "Define Equation" button in the "Field Config" tab of the "Data &gt; Configure Types Dialog". This brings up a dialog with fields to reference on the left and math operators on the right. The "Reference Level" pull-down determines whether the reference is from the same node, the node's parent, the root node, or the node's children. The "Result Type" pull-down allows numeric, date, time, boolean or text results to be chosen. The "Operator Type" pull-down allows numeric, comparison or text operators to be shown in the operator list. The down-arrow buttons below the references and operators add the selected item to the equation text below. Portions of equations can also be typed directly in the equation text line editor.<br /> -<br /> -References to child nodes must be enclosed in a grouping function, such as sum, max, min, mean or join. A math field can contain a parent or child reference to itself, but not a same-level reference to itself (a circular reference). The references only contain the field name, so they will reference a parent or child field with that name even if it is a different node type.<br /> -<br /> -In equations, date fields are represented by the number of days since January 1, 1970, and time fields are the number of seconds since midnight. So date fields can be subtracted to give the number of days elapsed, and numbers of days can be added to or subtracted from dates to result in new dates. Time fields can be subtracted to give the number of seconds elapsed, and numbers of seconds can be added to or subtracted from times to result in new times.<br /> -<br /> -The "if" comparison operator can be used to make the result depend on the value of another field. The expression is written as "true_value if condition else false_value". Of course, the "true_value", "condition" and "false_value" strings must be replaced with valid fields or expressions. If this operator is inserted from the operator list (under comparisons), it will include parenthesis as placeholders. These parenthesis are optional in the equations.<br /> -<br /> -The "join" text function is used to combine text from several other fields (or from child nodes). The first argument to the function is a separator string that is placed between each piece of text. The remaining argument(s) are the text to be joined.<br /> -<br /> -By default, math fields are shown in the data edit view, but they are read-only. To hide them, uncheck the "Show math fields in the Data Edit View" box under "Tools &gt; General Options &gt; Features Available".<br /> -<br /> -There is also an option under "File &gt; Properties" that toggles whether blank fields are treated as zeros. If checked (the default), a blank field that is referenced by a math field has a value of zero (for numeric operations) or blank (for text operations). If unchecked, any blank references also cause equations that reference them to be blank.<br /> - - - -Numbering Type -This type (not to be confused with the number type above) provides fields that are automatically filled in with the "Data &gt; Update Numbering" command. The "Format Help" button in the field format dialog shows the output format options. A single format level will result in a simple sequential numbering scheme. Use of the "/" level separator will result in an outline-type numbering with different sequences at different levels. Use of the "." section separator will result in a "2.3.5" type numbering scheme.<br /> -<br /> -Since numbering fields are automatically populated, by default they are not shown in the data edit view. To show them, check the "Show numbering fields in the Data Edit View" box under "Tools &gt; General Options &gt; Features Available". When they are shown in the data edit view, they show up in section numbering style, regardless of the output format. - - -Regular Expression Type -This type allows arbitrary format strings to be matched that restrict the data to a particular format. It uses Python regular expression syntax. Entries in the data editor which do not match the format string expression will cause a blue triangle to show in the upper left corner of the edit box, and the output for that field will be replaced with "#####". - - - -Output Formatting Details - -Output Format Examples -Here is an example of output formatting for a book list:<br /> -<br /> -"{*Title*}"<br /> -(c) {*Copyright*}, Rating: {*Rating*}<br /> -{*PlotDescription*}<br /> -<br /> -Each of the field names in enclosed in {* *}, curly brackets and asterisks. For more examples, see the sample files that can be opened using the "File &gt; Open Sample" command. - - -Title Formats -When a node in the tree is renamed, the program attempts to match the title formatting pattern to set the appropriate fields (the same title matching occurs when editing lines in the "Title List" view). If the title formatting is too complex, it may not correctly guess the intent. Things like adjacent fields with no characters separating them should be avoided unless you do not wish to rename nodes from the tree.<br /> -<br /> -If the text data used for a tree view title has multiple lines, only the first line will be used as the title. - - -Skipped Output Lines -If a line in the output formatting contains one or more fields and all of those fields for a given node are empty, the line is skipped. No blank line or embedded text will be output for that line. Note that this does not apply to a line without any fields (only embedded text). Also, when a line ending with a &lt;br/&gt; or an &lt;hr/&gt; tag is skipped, the ending tag is retained. - - -HTML Tags -By default, the "Allow HTML rich text in formats" option is unchecked in the "Type Config" tab of the Configure Types Dialog. So any HTML tags are treated as plain text. If the option is enabled, simple HTML formatting tags can be used in node output formats.<br /> -<br /> -Commonly used tags include "&lt;b&gt;bold&lt;/b&gt;", "&lt;u&gt;underline&lt;/u&gt;", "line break&lt;br/&gt;", "horizontal line&lt;hr/&gt;", and various font tags. Complex block tags should generally be avoided. - - -Other Field References -References to fields that are not contained within the node can be added to the output. Pushing the "Show Advanced" button on the "Output" tab of the configure dialog makes a reference level selection become visible.<br /> -<br /> -If the reference level is changed to "File Info Reference", fields containing file meta-data can be added to the output. These include the file name, path, size, modified day and modified time. A field referencing the node's unique ID is also included here. These special fields are shown as "{*!field_name*}" in the title and output format editors.<br /> -<br /> -There are field references to various ancestor nodes (parents, grandparents, etc.). These require the data type of the reference to be specified. This selection determines the field names that are available, but the data from any type with a matching field name will be shown in the output. References to fields from parent and grandparent nodes are shown as "{**field_name*}" and "{***field_name*}", respectively. There are also general ancestor references, shown as "{*?field_name*}", that take data from the closest ancestor with a matching field.<br /> -<br /> -References to child nodes can also be added. These also require that the child data type be specified. The child data becomes embedded in the parent output. The child data is delimited with a separator string defined on the "Type Config" tab (with show advanced active). The separator defaults to a comma and a space, but can be set to &lt;br/&gt; or anything else.<br /> -<br /> -Finally, a "Child Count" reference can be added. This field will show the number of children ("Level1" field) or grandchildren ("Level2" field) of a node. These are shown as {*#Level1*} in the format editors.<br /> -<br /> -For examples of these fields, see the "sample_other_fields" file (by using the "File &gt; Open Sample" command). - - - -Type Format Details - -Unique IDs -Every node has a Unique ID string associated with it. The IDs are used for addresses in internal links and in other places where TreeLine needs to identify a particular node.By default, the text in the first field is used to generate the ID. This can be changed in the "Type Config" tab of the "Data &gt; Configure Types Dialog". Click the "Show Advanced" button to see the "Unique ID Reference Field" setting. <br /> -<br /> -To create the Unique ID, text in the reference field is modified by removing spaces and other special characters, shortening long entries, and adding extra characters if required to be unique in the file. If the content of the reference field changes, the Unique ID is updated, along with any links that reference it.<br /> -<br /> -The Unique IDs can be shown in the data edit view by checking the "Show node unique IDs in the Data Edit View" box under "Tools &gt; General Options &gt; Features Available". - - -Generic and Derived Types -Data types can be set to derive their field settings from a generic type. This allows types with different output formatting to always use the same set of fields. Any changes to the generic's list of fields and field types are automatically reflected in the fields of all derived types. This does not apply to a field's output formatting, which can still be set independently.<br /> -<br /> -There are two methods for creating derived types. First, a derived option can be selected when copying a type on the "Type List" tab of the "Data &gt; Configure Types Dialog". Alternately, a generic type can be specified from the derived type's "Type Config" tab of the dialog if the advanced functions are shown. - - -Conditional Types -Conditional expressions can be used to automatically assign a data type based on each node's content. Conditions can be assigned only to a generic type and its associated derived types. This allows the automatic assignment of different output formatting or different icons depending on each node's field data.<br /> -<br /> -The conditional dialog box is accessed from a button on the "Type Config" tab of the "Data-&gt;Configure Types Dialog" if the advanced functions are shown. Each line of the condition includes a field, an operator and a comparison value. The operators include equality, greater than, less than, starts with, ends with, and contains. There are also True and False operators that will toggle the type of all nodes simultaneously.<br /> -<br /> -For special field types such as dates, times, and booleans, the comparison value should be entered in the same format that is used in the Data Editor window. In general, the starts with, ends with, and contains operators should not be used for these special fields, since the comparison is done using an internal data representation. Dates and times also support a special comparison value of "now", which is always interpreted as the current date and time.<br /> -<br /> -The "Add New Rule" button is used to add additional condition lines. The lines can be joined with "and" or "or" operators. The "Remove Rule" button deletes the last condition line. If only a single line is present, the "Remove Rule" button completely removes the condition.<br /> -<br /> -Conditions do not have to be set for all types in a family. If no conditions are true for a node, the program will select a blank condition over a false one.<br /> -<br /> -For an example, see the "sample_conditional_todo" file (by using the "File &gt; Open Sample" command). - - -Copying Formats -Another method for changing data type formatting is to copy the formats from another TreeLine file. This is done with the "Data &gt; Copy Types from File" command. All types from the chosen file are copied. Any types in the current file with matching names are overwritten, but types with unique names are retained. - - - -Tree Data Operations - -Sorting -The "Data &gt; Sort Nodes" command can sort nodes based on either node titles or key fields predefined in the node type configuration. The predefined sort fields can be changed in the "Field Config" tab of the Configure Types Dialog. The "Sort Keys" button brings up a list of fields that define a sort key sequence. The fields higher in the sequence have a higher priority. The direction for each key field can be flipped. - - -Numbering -The "Data &gt; Update Numbering" command updates the contents of fields with a special numbering field type. The field's output format defines how the numbers are displayed in the output, including whether individual numbers, outline numbers or a section numbering scheme are shown. See the <a href="#numbering_type">Numbering Field Type</a> for more information.<br /> -<br /> -Note that numbering fields are not shown in the data edit view unless "Show numbering fields in the Data Edit View" is checked under "Tools &gt; General Options &gt; Features Available". When they are shown in the data edit view, they show up in section numbering style, regardless of the output format. - - -Find and Replace -The "Tools &gt; Find and Replace" command can be used to change the text in several nodes. The search text and replacement text are entered. Searching can be based on any match, full words only, or a Python regular expression. The operation can optionally be restricted to a particular node type and to a particular node field.<br /> -<br /> -Replacement using regular expressions is quite powerful. Searching for ".*" will match all of the text in the field. The replacement string can contain back references that consist of a backslash and a number. The back references get replaced with the corresponding parenthesized group from the match. For example, "\2" will be replaced with the text that matched the second group of parenthesis. The "\g&lt;0&gt;" back-reference can be used to substitute the entire matching string. - - -Spell Check -There is a spell check command in the "Tools" menu. Use of this command requires an external program to be installed (either aspell, ispell or hunspell- see the <a href="#system_requirements">System Requirements</a> section). If there are any misspelled words in the selected branch, a dialog will allow the word to be ignored, added to the dictionary, replaced with a suggestion or edited. This will spell check the text in all data fields of each node. - - -Category-Based Arrangement -The "Data" menu contains commands for arranging and flattening the data by category and by internal links. These methods are used to automatically add and remove levels of nodes below the current node in the tree.<br /> -<br /> -The "Add Category Level" command allows you to select one or more of the fields that the child nodes have in common. These fields are used to create new parent nodes for the children, grouping them by common categories. For example, in a list of books, picking the "author_first_name" and "author_last_name" fields will result in a tree with the books under new nodes for each unique author.<br /> -<br /> -The "Flatten by Category" command is almost the opposite of "Add Category Level". It eliminates any descendant nodes with children, transferring their data fields to their children. It will rename fields instead of overwriting data with the same field names, but this command is most useful when the children and parents are different types with unique field names. - - -Link-Based Arrangement -The "Data &gt; Flatten by Link" command will move all descendant nodes directly under a root node in a flat structure. It will add an internal link field to each node that points to the node's former parent.<br /> -<br /> -The "Data &gt; Arrange by Link" command does the opposite, placing each node under a parent node that is referenced by an internal link field. - - - -Printing - -Print Setup -The dialog for print setup contains four tabs. The first, General Options, includes settings for what part of the tree to print, whether to include lines to child nodes and whether to allow page breaks between a parent an its first child node. The second tab, Page Setup, includes paper size, orientation, margins and columns. The third tab selects the font for printing. The last tab, Header/Footer, defines text and file data meta-fields for inclusion in headers and footers. - - -Print Preview -The Print Preview window shows how the printout will look with current print settings. It can be dragged larger to show more detail, or the zoom settings can be changed. It also includes buttons for the Print Setup dialog and for printing. - - -Print to PDF -This command prompts for a path and file name to export a PDF file. It uses the current printer settings. - - - -File Handling - -File Compression -A TreeLine file is in an XML text format. There are also options to work with compressed files (gzip format) to save storage space. Individual files can be set to compressed mode from either "File &gt; Properties" or from the file type pull-down in the save-as dialog. - - -File Encryption -There is a file encryption option to password protect TreeLine files. Individual files can be set to encrypted mode from either "File &gt; Properties" or from the file type pull-down in the save-as dialog. The encryption uses an SHA hash function as a stream cipher - it should be fairly secure. - - -Auto-Save -An auto-save feature can store unsaved files with a "~" appended to the file name. The backup files are automatically removed when the file is saved or TreeLine exits cleanly. The auto-save time interval is set in the general options. Setting the interval to zero disables this feature. - - -Saved Tree States -When opening a recently used file, TreeLine will restore the states of open and selected nodes. This information is stored in the user's TreeLine configuration files. If desired, this feature can be disabled with a general option. - - - -File Import - -General Information -A TreeLine file is in a specific XML text format. Other types of files can be imported using the "File &gt; Import" command, which will show a dialog box where the type of import can be selected. Alternatively, using the "File &gt; Open" command with a non-TreeLine file will also show this dialog. - - -Text Import -There are four different text formats that can be selected for import. Tab indented text creates a node title from each line in the file, structured based on the number of tabs before each line. A tab delimited text table uses the first line as a header row to create field names, then each additional row becomes a node with field data taken from each column. The plain text, one node per line import creates a flat tree of node titles. Finally, the plain text paragraph import creates long text nodes from text separated by blank lines. - - -Treepad Import -Files from the Treepad shareware program can be imported. Only Treepad text nodes are supported. - - -XML Import -TreeLine will import and export generic XML files. These routines do not have much intelligence - each XML element becomes a node and each XML attribute becomes a field. XML text content become fields named "Element_Data". This lets TreeLine function as a crude XML editor. - - -ODF Import -TreeLine will import Open Document Format (ODF) text documents, from applications such as Apache OpenOffice and LibreOffice. The node structure is formed based on the heading styles assigned in the document. Any text under each heading is assigned to that heading's node. The import filter is intended for simple text outlines only. No formatting is maintained, and objects such as tables and pictures are not imported. - - -Bookmarks Import -TreeLine will import bookmark files in both the Mozilla HTML format (Firefox browser) and the XBEL format (Konqueror, Galeon and Elinks browsers). Each bookmark becomes a node with a name and a link field. Some information in the files, such as visited dates and icon references, is not imported. For an example, see the "sample_bookmarks" file (by using the "File &gt; Open Sample" command).<br /> - - - - -File Export - -General Information -Files are exported using the "File &gt; Export" command. This will show a dialog box of available export types and options. - - -HTML Export -The HMTL export subtypes can export to a single page or to multiple pages. The single page export contains all of the indented output. A navigation pane on the left with links to anchors at node positions is optional.<br /> -<br /> -The multiple HTML pages export has one web page per node. It includes a navigation pane on the left with links to the pages with sibling, parent and aunt/uncle nodes.<br /> -<br /> -Multiple HTML data tables export creates a table in each HTML file that contains the data for a set of siblings, as well as links to the parent and child pages. - - -Text Export -Data can be exported to tabbed title text and tab-delimited tables. These formats are the same as the corresponding import formats. When exporting to a table, only the first level of children is used, so you may wish to flatten the data before exporting.<br /> -<br /> -There is also an unformatted text export the dumps all of the output into a text file without preserving the tree structure. - - -XML Export -TreeLine will import and export generic XML files. These routines do not have much intelligence - each node becomes an XML element and each field becomes an XML attribute, except for fields named "Element_Data" that become the element's text. This lets TreeLine function as a crude XML editor. - - -ODF Export -TreeLine will export an outline to an Open Document Format (ODF) text document, compatible with Apache OpenOffice and LibreOffice. The title of each node is assigned a heading style at the appropriate level. Any other text in the output of each node becomes normal text under the heading. The export filter is intended for simple text outlines only. Any HTML formatting is stripped, and objects such as tables and pictures are not supported. - - -Bookmarks Export -TreeLine will export bookmark files in both the Mozilla HTML format (Firefox browser) and the XBEL format (Konqueror, Galeon and Elinks browsers). TreeLine will look for a link field in each node that becomes the target of the bookmark. - - - -Customizations - -Options -TreeLine's behavior can be modified with several settings available in "Tools &gt; General Options". Most of these options are covered elsewhere in this document. - - -Keyboard Shortcuts -Keyboard shortcuts can be customized by using the "Tools &gt; Set Keyboard Shortcuts" command. Simply type the new key sequence with the appropriate field selected. - - -Toolbars -An editor to customize the toolbars is available from "Tools &gt; Customize Toolbars". The number of toolbars can be set, and the buttons on each can be defined. - - -Fonts -Fonts used in the tree views, the output view and the editor views can be set in the "Tools &gt; Customize Fonts" menu. - - -Tree Icons -There is an icons directory in the user configuration directory ("~/.treeline-x.x/icons" on Linux, "Users\&lt;user&gt;\AppData\roaming\bellz\treeline-x.x\icons" on Windows). Image files (PNG or BMP) placed into this directory are available for use as tree icons. - - - -Plugins - -Description -TreeLine has an interface for plugin extension modules. This allows extensions to be written by various programmers to provide features needed by a few users without adding bloat to the main application. The plugins can add new menu items, access and modify TreeLine data, and read and write TreeLine Files.<br /> -<br /> -Currently available plugins are listed on the "Downloads" page of the TreeLine web site. - - -Installation -The plugins are installed by copying their Python file (*.py) to a plugins directory. This can be a directory in the TreeLine installation ("&lt;prefix&gt;/share/treeline/plugins/" on Linux or "TreeLine-2\plugins\" under the Program Files or Application Data folders on Windows) or in the user configuration directory ("~/.treeline-2.x/plugins/" on Linux, "Documents and Settings\&lt;user&gt;\Application Data\bellz\treeline-2.x\plugins\" on Windows). A list of loaded plugins can be found using the "Help &gt;About Plugins" command. - - -Programming Interface -Information needed to write new plugins can be found in the "plugininterface.py" file in the TreeLine source code. - - - - -Revision History - -See English version -For the full revision history, see the website or the English version of the documentation. - - - -Contacts - -Mailing list -There is a low-volume mailing list for users to discuss anything and everything about TreeLine. This is the place for development discussions (from roadmaps to feature suggestions to beta testing), release announcements, bug reports, and general user discussions (from new uses to tips &amp; tricks to configuration samples).<br /> -<br /> -To subscribe, go to <a href="https://lists.sourceforge.net/lists/listinfo/treeline-users">lists.sourceforge.net/lists/listinfo/treeline-users</a><br /> - - - -Email -If you do not wish to subscribe to the mailing list, I can be contacted by email at: doug101 AT bellz DOT org - - -Updates -I welcome any feedback, including reports of any bugs you find. Also, you can periodically check back to <a href="http://treeline.bellz.org">treeline.bellz.org</a> for any updates. - - - diff -Nru treeline-2.0.2/i18n/translations/qt_de.ts treeline-3.0.1/i18n/translations/qt_de.ts --- treeline-2.0.2/i18n/translations/qt_de.ts 2018-04-08 20:12:57.000000000 +0000 +++ treeline-3.0.1/i18n/translations/qt_de.ts 1970-01-01 00:00:00.000000000 +0000 @@ -1,6204 +0,0 @@ - - - - @default - - - OK - OK - - - - Cancel - Abbrechen - - - - PPDOptionsModel - - - Name - Name - - - - Value - - - - - Q3Accel - - - %1, %2 not defined - %1, %2 nicht definiert - - - - Ambiguous %1 not handled - Mehrdeutige %1 nicht gehandhabt - - - - Q3DataTable - - - True - Wahr - - - - False - Falsch - - - - Insert - Einfügen - - - - Update - Aktualisieren - - - - Delete - Löschen - - - - Q3FileDialog - - - Copy or Move a File - Datei kopieren oder verschieben - - - - Read: %1 - Lesen: %1 - - - - Write: %1 - Schreiben: %1 - - - - Cancel - Abbrechen - - - - All Files (*) - Alle Dateien (*) - - - - Name - Name - - - - Size - Größe - - - - Type - Typ - - - - Date - Datum - - - - Attributes - Attribute - - - - &OK - &OK - - - - Look &in: - Su&chen in: - - - - File &name: - Datei&name: - - - - File &type: - Datei&typ: - - - - Back - Zurück - - - - One directory up - Ein Verzeichnis zurück - - - - Create New Folder - Neuen Ordner erstellen - - - - List View - Liste - - - - Detail View - Ausführlich - - - - Preview File Info - Voransicht der Datei-Info - - - - Preview File Contents - Voransicht des Datei-Inhalts - - - - Read-write - Lesen/Schreiben - - - - Read-only - Nur Lesen - - - - Write-only - Nur Schreiben - - - - Inaccessible - Gesperrt - - - - Symlink to File - Verknüpfung mit Datei - - - - Symlink to Directory - Verknüpfung mit Verzeichnies - - - - Symlink to Special - Verknüpfung mit Spezialdatei - - - - File - Datei - - - - Dir - Verzeichnis - - - - Special - Spezialattribut - - - - Open - Öffnen - - - - Save As - Speichern unter - - - - &Open - &Öffnen - - - - &Save - S&peichern - - - - &Rename - &Umbenennen - - - - &Delete - &Löschen - - - - R&eload - Erne&ut laden - - - - Sort by &Name - Nach &Name sortieren - - - - Sort by &Size - Nach &Größe sortieren - - - - Sort by &Date - Nach &Datum sortieren - - - - &Unsorted - &Unsortiert - - - - Sort - Sortieren - - - - Show &hidden files - &Versteckte Dateien anzeigen - - - - the file - die Datei - - - - the directory - das Verzeichnis - - - - the symlink - die Verknüpfung - - - - Delete %1 - %1 löschen - - - - <qt>Are you sure you wish to delete %1 "%2"?</qt> - <qt>Sind Sie sicher, dass Sie %1 "%2" löschen möchten?</qt> - - - - &Yes - &Ja - - - - &No - N&ein - - - - New Folder 1 - Neues Verzeichnis 1 - - - - New Folder - Neues Verzeichnis - - - - New Folder %1 - Neues Verzeichnis %1 - - - - Find Directory - Verzeichnis suchen - - - - Directories - Verzeichnisse - - - - Directory: - Verzeichnis: - - - - Error - Fehler - - - - %1 -File not found. -Check path and filename. - %1 -Datei konnte nicht gefunden werden. -Überprüfen Sie Pfad und Dateinamen. - - - - All Files (*.*) - Alle Dateien (*.*) - - - - Open - Öffnen - - - - Select a Directory - Wählen Sie ein Verzeichnis - - - - Q3Ftp - - - Not connected - Keine Verbindung - - - - Host %1 not found - Rechner %1 nicht gefunden - - - - Connection refused to host %1 - Verbindung mit %1 verweigert - - - - Connected to host %1 - Verbunden mit Rechner %1 - - - - Connection refused for data connection - Verbindung für die Daten Verbindung verweigert - - - - Unknown error - Unbekannter Fehler - - - - Connecting to host failed: -%1 - Verbindung mit Rechner schlug fehl: -%1 - - - - Login failed: -%1 - Anmeldung schlug fehl: -%1 - - - - Listing directory failed: -%1 - Inhalt des Verzeichnises kann nicht angezeigt werden: -%1 - - - - Changing directory failed: -%1 - Ändern des Verzeichnises schlug fehl: -%1 - - - - Downloading file failed: -%1 - Herunterladen der Datei schlug fehl: -%1 - - - - Uploading file failed: -%1 - Hochladen der Datei schlug fehl: -%1 - - - - Removing file failed: -%1 - Löschen der Datei schlug fehl: -%1 - - - - Creating directory failed: -%1 - Erstellen des Verzeichnises schlug fehl: -%1 - - - - Removing directory failed: -%1 - Löschen des Verzeichnises schlug fehl: -%1 - - - - Connection closed - Verbindung beendet - - - - Host %1 found - Rechner %1 gefunden - - - - Connection to %1 closed - Verbindung mit %1 beendet - - - - Host found - Rechner gefunden - - - - Connected to host - Verbindung mit Rechner besteht - - - - Q3Http - - - Unknown error - Unbekannter Fehler - - - - Request aborted - Anfrage wurde abgebrochen - - - - No server set to connect to - Kein Rechner gesetzt für die Verbindung - - - - Wrong content length - Falsche Content-Length - - - - Server closed connection unexpectedly - Server hat die Verbindung unerwartet geschlossen - - - - Connection refused - Verbindung verweigert - - - - Host %1 not found - Rechner %1 nicht gefunden - - - - HTTP request failed - HTTP Anfrage schlug fehl - - - - Invalid HTTP response header - Ungültiger HTTP Antwort-Header - - - - Invalid HTTP chunked body - Ungültiger HTTP Chunked-Body - - - - Host %1 found - Rechner %1 gefunden - - - - Connected to host %1 - Verbunden mit Rechner %1 - - - - Connection to %1 closed - Verbindung mit %1 beendet - - - - Host found - Rechner gefunden - - - - Connected to host - Verbindung mit Rechner besteht - - - - Connection closed - Verbindung beendet - - - - Q3LocalFs - - - Could not read directory -%1 - Konnte Verzeichnis nicht lesen -%1 - - - - Could not create directory -%1 - Konnte Verzeichnis nicht erstellen -%1 - - - - Could not remove file or directory -%1 - Konnte Datei oder Verzeichnis nicht löschen -%1 - - - - Could not rename -%1 -to -%2 - Konnte nicht umbenannt werden: -%1 -nach -%2 - - - - Could not open -%1 - Konnte nicht geöffnet werden: -%1 - - - - Could not write -%1 - Konnte nicht geschrieben werden: -%1 - - - - Q3MainWindow - - - Line up - Ausrichten - - - - Customize... - Anpassen... - - - - Q3NetworkProtocol - - - Operation stopped by the user - Operation von Benutzer angehalten - - - - Q3ProgressDialog - - - Cancel - Abbrechen - - - - Q3TabDialog - - - OK - OK - - - - Apply - Anwenden - - - - Help - Hilfe - - - - Defaults - Defaults - - - - Cancel - Abbrechen - - - - Q3TextEdit - - - &Undo - &Rückgängig - - - - &Redo - Wieder&herstellen - - - - Cu&t - &Ausschneiden - - - - &Copy - &Kopieren - - - - &Paste - Einf&ügen - - - - Clear - Löschen - - - - Select All - Alles auswählen - - - - Q3TitleBar - - - System - - - - - Restore up - - - - - Minimize - Minimieren - - - - Restore down - - - - - Maximize - Maximieren - - - - Close - Schließen - - - - Contains commands to manipulate the window - - - - - Puts a minimized back to normal - - - - - Moves the window out of the way - - - - - Puts a maximized window back to normal - - - - - Makes the window full screen - - - - - Closes the window - - - - - Displays the name of the window and contains controls to manipulate it - - - - - Q3ToolBar - - - More... - Mehr... - - - - Q3UrlOperator - - - The protocol `%1' is not supported - Das Protokoll `%1' wird nicht unterstützt - - - - The protocol `%1' does not support listing directories - Das Protokoll `%1' unterstützt nicht das Auflisten von Verzeichnissen - - - - The protocol `%1' does not support creating new directories - Das Protokoll `%1' unterstützt nicht das Anlegen neuer Verzeichnisse - - - - The protocol `%1' does not support removing files or directories - Das Protokoll `%1' unterstützt nicht das Löschen von Dateien oder Verzeichnissen - - - - The protocol `%1' does not support renaming files or directories - Das Protokoll `%1' unterstützt nicht das Umbenennen von Dateien oder Verzeichnissen - - - - The protocol `%1' does not support getting files - Das Protokoll `%1' unterstützt nicht das Laden von Files - - - - The protocol `%1' does not support putting files - Das Protokoll `%1' unterstützt nicht das Speichern von Files - - - - The protocol `%1' does not support copying or moving files or directories - Das Protokoll `%1' unterstützt nicht das Kopieren oder Verschieben von Dateien oder Verzeichnissen - - - - (unknown) - (unbekannt) - - - - Q3Wizard - - - &Cancel - &Abbrechen - - - - < &Back - < &Zurück - - - - &Next > - &Weiter > - - - - &Finish - Ab&schließen - - - - &Help - &Hilfe - - - - QAbstractSocket - - - Host not found - Rechner konnte nicht gefunden werden - - - - Connection refused - Verbindung verweigert - - - - Socket operation timed out - Das Zeitlimit für die Operation wurde überschritten - - - - Socket is not connected - - - - - QAbstractSpinBox - - - &Step up - &Inkrementieren - - - - Step &down - &Dekrementieren - - - - QAccel - - - Space - Leertaste - - - - Esc - Esc - - - - Tab - Tab - - - - Backtab - Rück-Tab - - - - Backspace - Rücktaste - - - - Return - Return - - - - Enter - Enter - - - - Ins - Einfg - - - - Del - Entf - - - - Pause - Pause - - - - Print - Druck - - - - SysReq - SysReq - - - - Home - Pos1 - - - - End - Ende - - - - Left - Links - - - - Up - Hoch - - - - Right - Rechts - - - - Down - Runter - - - - PgUp - Bild aufwärts - - - - PgDown - Bild abwärts - - - - CapsLock - Feststelltaste - - - - NumLock - Zahlen-Feststelltaste - - - - ScrollLock - Rollen-Feststelltaste - - - - Ctrl - Strg - - - - + - + - - - - Alt - Alt - - - - Shift - Umschalt - - - - F%1 - F%1 - - - - Menu - Menü - - - - Help - Hilfe - - - - Back - Zurück - - - - Forward - Vorwärts - - - - Stop - Abbrechen - - - - Refresh - Aktualisieren - - - - Volume Down - Lautstärke - - - - - Volume Mute - Ton aus - - - - Volume Up - Lautstärke + - - - - Bass Boost - Bass Boost - - - - Bass Up - Bass + - - - - Bass Down - Bass - - - - - Treble Up - Höhen + - - - - Treble Down - Höhen - - - - - Media Play - Wiedergabe - - - - Media Stop - Stopp - - - - Media Previous - Vorheriger - - - - Media Next - Nächster - - - - Media Record - Aufzeichnen - - - - Favorites - Favoriten - - - - Search - Suchen - - - - Standby - Standby - - - - Open URL - Öffne URL - - - - Launch Mail - Start Mail - - - - Launch Media - Start Media Player - - - - Launch (0) - Start (0) - - - - Launch (1) - Start (1) - - - - Launch (2) - Start (2) - - - - Launch (3) - Start (3) - - - - Launch (4) - Start (4) - - - - Launch (5) - Start (5) - - - - Launch (6) - Start (6) - - - - Launch (7) - Start (7) - - - - Launch (8) - Start (8) - - - - Launch (9) - Start (9) - - - - Launch (A) - Start (A) - - - - Launch (B) - Start (B) - - - - Launch (C) - Start (C) - - - - Launch (D) - Start (D) - - - - Launch (E) - Start (E) - - - - Launch (F) - Start (F) - - - - Meta - Meta - - - - QApplication - - - QT_LAYOUT_DIRECTION - Translate this string to the string 'LTR' in left-to-right languages or to 'RTL' in right-to-left languages (such as Hebrew and Arabic) to get proper widget layout. - LTR - - - - Executable '%1' requires Qt %2, found Qt %3. - Applikation '%1' benötigt Qt %2, aber Qt %3 gefunden. - - - - Incompatible Qt Library Error - Qt Bibliothek ist inkompatibel - - - - Activate - Aktivieren - - - - Activates the program's main window - Aktiviert das Programmhauptfenster - - - - QAquaStyle - - - OK - OK - - - - Cancel - Abbrechen - - - - QAxSelect - - - Select ActiveX Control - - - - - OK - OK - - - - &Cancel - &Abbrechen - - - - COM &Object: - - - - - QAxServerBase - - - &Help - &Hilfe - - - - QCheckBox - - - Uncheck - - - - - Check - - - - - Toggle - - - - - QColorDialog - - - Hu&e: - Farb&ton: - - - - &Sat: - &Sat: - - - - &Val: - &Val: - - - - &Red: - &Rot: - - - - &Green: - &Grün: - - - - Bl&ue: - Bla&u: - - - - A&lpha channel: - A&lphakanal: - - - - &Basic colors - Grundfar&ben - - - - &Custom colors - &Benutzerdefinierte Farben - - - - &Define Custom Colors >> - Eigene Farben &definieren >> - - - - OK - OK - - - - Cancel - Abbrechen - - - - &Add to Custom Colors - Zu benutzerdefinierten Farben &hinzufügen - - - - Select color - Farbauswahl - - - - QComboBox - - - Open - Öffnen - - - - QDB2Driver - - - Unable to connect - - - - - Unable to commit transaction - - - - - Unable to rollback transaction - - - - - Unable to set autocommit - - - - - QDB2Result - - - Unable to execute statement - - - - - Unable to prepare statement - - - - - Unable to bind variable - - - - - Unable to fetch record %1 - - - - - Unable to fetch next - - - - - Unable to fetch first - - - - - QDataManager - - - Delete this record? - Diesen Datensatz löschen? - - - - Yes - Ja - - - - No - Nein - - - - Save edits? - Änderungen speichern? - - - - Cancel - Abbrechen - - - - Confirm - Bestätigen - - - - Cancel your edits? - Änderungen verwerfen? - - - - QDataTable - - - True - Wahr - - - - False - Falsch - - - - Insert - Einfügen - - - - Update - Aktualisieren - - - - Delete - Löschen - - - - QDateTimeEdit - - - AM - AM - - - - am - am - - - - PM - PM - - - - pm - pm - - - - QDialog - - - What's This? - Direkthilfe - - - - Help - Hilfe - - - - QDialogButtonBox - - - OK - OK - - - - Save - Speichern - - - - Open - Öffnen - - - - Cancel - Abbrechen - - - - Close - Schließen - - - - Apply - Anwenden - - - - Reset - - - - - Help - Hilfe - - - - Don't Save - - - - - Discard - - - - - &Yes - &Ja - - - - Yes to &All - Ja zu &allem - - - - &No - N&ein - - - - N&o to All - N&ein zu allem - - - - Save All - - - - - Abort - Abbrechen - - - - Retry - Wiederholen - - - - Ignore - Ignorieren - - - - Restore Defaults - - - - - Close without Saving - - - - - QDialogButtons - - - Yes to All - Ja zu allem - - - - OK to All - OK zu allem - - - - No to All - Nein zu allem - - - - Cancel All - Alle abbrechen - - - - Yes - Ja - - - - OK - OK - - - - No - Nein - - - - Cancel - Abbrechen - - - - Apply - Anwenden - - - - Ignore - Ignorieren - - - - Retry - Wiederholen - - - - Abort - Abbrechen - - - - Help - Hilfe - - - - %1 to All - %1 zu allem - - - - QDirModel - - - Name - Name - - - - Size - Größe - - - - Type - Typ - - - - Modified - Geändert - - - - Kind - Match OS X Finder - - - - - Type - All other platforms - Typ - - - - Date Modified - - - - - QErrorMessage - - - &Show this message again - Diese Meldungen noch einmal an&zeigen - - - - &OK - &OK - - - - Debug Message: - Debug Ausgabe: - - - - Warning: - Warnung: - - - - Fatal Error: - Fehler: - - - - QFileDialog - - - Copy or Move a File - Datei kopieren oder verschieben - - - - Read: %1 - Lesen: %1 - - - - Write: %1 - Schreiben: %1 - - - - Cancel - Abbrechen - - - - All Files (*) - Alle Dateien (*) - - - - Name - Name - - - - Size - Größe - - - - Type - Typ - - - - Date - Datum - - - - Attributes - Attribute - - - - OK - OK - - - - Look &in: - Su&chen in: - - - - File &name: - Datei&name: - - - - File &type: - Datei&typ: - - - - Back - Zurück - - - - One directory up - Ein Verzeichnis zurück - - - - Create New Folder - Neuen Ordner erstellen - - - - List View - Liste - - - - Detail View - Details - - - - Preview File Info - Voransicht der Datei-Info - - - - Preview File Contents - Voransicht des Datei-Inhalts - - - - Read-write - Lesen/Schreiben - - - - Read-only - Nur Lesen - - - - Write-only - Nur Schreiben - - - - Inaccessible - Gesperrt - - - - Symlink to File - Link auf Datei - - - - Symlink to Directory - Link auf Verzeichnies - - - - Symlink to Special - Link auf Spezialdatei - - - - File - Datei - - - - Dir - Verzeichnis - - - - Special - Spezialattribut - - - - Open - Öffnen - - - - Save As - Speichern unter - - - - &Open - &Öffnen - - - - &Save - S&peichern - - - - &Rename - &Umbenennen - - - - &Delete - &Löschen - - - - R&eload - Erne&ut laden - - - - Sort by &Name - Nach &Name sortieren - - - - Sort by &Size - Nach &Größe sortieren - - - - Sort by &Date - Nach &Datum sortieren - - - - &Unsorted - &Unsortiert - - - - Sort - Sortieren - - - - Show &hidden files - &Versteckte Dateien anzeigen - - - - the file - die Datei - - - - the directory - das Verzeichnis - - - - the symlink - den symbolischen Link - - - - Delete %1 - %1 löschen - - - - <qt>Are you sure you wish to delete %1 "%2"?</qt> - <qt>Sind Sie sicher, dass Sie %1 "%2" löschen möchten?</qt> - - - - &Yes - &Ja - - - - &No - N&ein - - - - New Folder 1 - Neues Verzeichnis 1 - - - - New Folder - Neues Verzeichnis - - - - New Folder %1 - Neues Verzeichnis %1 - - - - Find Directory - Verzeichnis suchen - - - - Directories - Verzeichnisse - - - - Save - Speichern - - - - Error - Fehler - - - - %1 -File not found. -Check path and filename. - %1 -Datei wurde nicht gefunden. -Überprüfen Sie Pfad und Dateinamen. - - - - All Files (*.*) - Alle Dateien (*.*) - - - - Open - Öffnen - - - - Select a Directory - Wählen Sie ein Verzeichnis - - - - Directory: - Verzeichnis: - - - - %1 already exists. -Do you want to replace it? - Die Datei %1 existiert bereits. -Soll sie überschreiben werden? - - - - %1 -File not found. -Please verify the correct file name was given. - %1 -Die Datei konnte nicht gefunden werden. -Stellen Sie sicher, dass der Dateiname richtig ist. - - - - My Computer - Mein Computer - - - - &Reload - Erne&ut laden - - - - Parent Directory - Elternverzeichnis - - - - Look in: - Suchen in: - - - - File name: - Dateiname: - - - - Files of type: - Dateien des Typs: - - - - -File not found. -Please verify the correct file name was given - - - - - %1 -Directory not found. -Please verify the correct directory name was given. - %1 -Das Verzeichnis konnte nicht gefunden werden. -Stellen Sie sicher, dass der Verzeichnisname richtig ist. - - - - '%1' is write protected. -Do you want to delete it anyway? - - - - - Are sure you want to delete '%1'? - - - - - Could not delete directory. - - - - - Drive - Laufwerk - - - - Unknown - Unbekannt - - - - QFileDialogPrivate - - - Sort - Sortieren - - - - &Open - &Öffnen - - - - &Rename - &Umbenennen - - - - &Delete - &Löschen - - - - Sort by &Name - Nach &Name sortieren - - - - Sort by &Size - Nach &Größe sortieren - - - - Sort by &Date - Nach &Datum sortieren - - - - &Unsorted - &Unsortiert - - - - Show &hidden files - &Versteckte Dateien anzeigen - - - - Back - Zurück - - - - Create New Folder - Neuen Ordner erstellen - - - - List View - Liste - - - - Detail View - Ausführlich - - - - Open - Öffnen - - - - Cancel - Abbrechen - - - - %1 -Directory not found. -Please verify the correct directory name was given. - %1 -Das Verzeichnis konnte nicht gefunden werden. -Stellen Sie sicher, dass der Verzeichnisname richtig ist. - - - - QFont - - - Latin - Latein - - - - Greek - Griechisch - - - - Cyrillic - Kyrillisch - - - - Armenian - Armenisch - - - - Georgian - Georgisch - - - - Runic - Runen - - - - Ogham - Ogham - - - - Hebrew - Hebräisch - - - - Arabic - Arabisch - - - - Syriac - Syrisch - - - - Thaana - Thaana - - - - Devanagari - Devanagari - - - - Bengali - Bengalisch - - - - Gurmukhi - Gurmukhi - - - - Gujarati - Gujarati - - - - Oriya - Oriya - - - - Tamil - Tamilisch - - - - Telugu - Telugu - - - - Kannada - Kannada - - - - Malayalam - Malayalam - - - - Sinhala - Sinhala - - - - Thai - Thailändisch - - - - Lao - Lao - - - - Tibetan - Tibetisch - - - - Myanmar - Myanmar - - - - Khmer - Khmer - - - - Han - Han - - - - Hiragana - Hiragana - - - - Katakana - Katakana - - - - Hangul - Hangul - - - - Bopomofo - Bopomofo - - - - Yi - Yi - - - - Ethiopic - Äthiopisch - - - - Cherokee - Skripte der Cherokee Indianer - - - - Canadian Aboriginal - Skripte der kanadischen Ureinwohner - - - - Mongolian - Mongolisch - - - - Currency Symbols - Währungssymbole - - - - Letterlike Symbols - Buchstabenartige Symbole - - - - Number Forms - Ziffernformen - - - - Mathematical Operators - Mathematische Operatoren - - - - Technical Symbols - Technische Symbole - - - - Geometric Symbols - Geometrische Symbole - - - - Miscellaneous Symbols - Verschiedene Symbole - - - - Enclosed and Square - Eingeschlossene Symbole - - - - Braille - Blindenschrift - - - - Unicode - Unicode - - - - Reverse Oblique - Umgekehrtes Schräg - - - - Reverse Italic - Umgekehrtes Kursiv - - - - Other - Sonstiges - - - - Oblique - Schräg - - - - Italic - Kursiv - - - - Normal - Normal - - - - QFontDialog - - - &Font - &Schriftart - - - - Font st&yle - Schrifts&til - - - - &Size - &Größe - - - - Effects - Effekte - - - - Stri&keout - Durch&gestrichen - - - - &Underline - &Unterstrichen - - - - &Color - &Farbe - - - - Sample - Beispiel - - - - Scr&ipt - Schr&iftsystem - - - - OK - OK - - - - Apply - Anwenden - - - - Cancel - Abbrechen - - - - Close - Schließen - - - - Select Font - Schriftart auswählen - - - - Wr&iting System - &Schriftsystem - - - - QFtp - - - Host %1 found - Rechner %1 gefunden - - - - Host found - Rechner gefunden - - - - Connected to host %1 - Verbunden mit Rechner %1 - - - - Connected to host - Verbindung mit Rechner besteht - - - - Connection to %1 closed - Verbindung mit %1 beendet - - - - Connection closed - Verbindung beendet - - - - Login Incorrect - Ungültige Anmeldung - - - - Host %1 not found - Rechner %1 konnte nicht gefunden werden - - - - Connection refused to host %1 - Verbindung mit %1 verweigert - - - - Unknown error - Unbekannter Fehler - - - - Connecting to host failed: -%1 - Verbindung mit Rechner schlug fehl: -%1 - - - - Login failed: -%1 - Anmeldung schlug fehl: -%1 - - - - Listing directory failed: -%1 - Inhalt des Verzeichnises kann nicht angezeigt werden: -%1 - - - - Changing directory failed: -%1 - Ändern des Verzeichnises schlug fehl: -%1 - - - - Downloading file failed: -%1 - Herunterladen der Datei schlug fehl: -%1 - - - - Uploading file failed: -%1 - Hochladen der Datei schlug fehl: -%1 - - - - Removing file failed: -%1 - Löschen der Datei schlug fehl: -%1 - - - - Creating directory failed: -%1 - Erstellen des Verzeichnises schlug fehl: -%1 - - - - Removing directory failed: -%1 - Löschen des Verzeichnises schlug fehl: -%1 - - - - Not connected - Keine Verbindung - - - - Connection refused for data connection - Verbindung für die Daten Verbindung verweigert - - - - QFtpPI - - - Host %1 not found - Rechner %1 nicht gefunden - - - - Connection refused to host %1 - Verbindung mit %1 verweigert - - - - Connected to host %1 - Verbunden mit Rechner %1 - - - - QHeader - - - %1 - %1 - - - - QHostInfo - - - Unknown error - Unbekannter Fehler - - - - QHostInfoAgent - - - Host not found - Rechner konnte nicht gefunden werden - - - - Unknown address type - Unbekannter Adresstyp - - - - Unknown error - Unbekannter Fehler - - - - QHttp - - - Connection refused - Verbindung verweigert - - - - Host %1 not found - Rechner %1 konnte nicht gefunden werden - - - - Connection closed by %1 - %1 hat Verbindung beendet - - - - Invalid HTTP reply header - Ungültiger HTTP Reply-Header - - - - Wrong content length - Falsche Content-Length - - - - HTTP request failed - HTTP Anfrage schlug fehl - - - - Host %1 found - Rechner %1 gefunden - - - - Host found - Rechner gefunden - - - - Connected to host %1 - Verbunden mit Rechner %1 - - - - Connected to host - Verbindung mit Rechner besteht - - - - Connection to %1 closed - Verbindung mit %1 beendet - - - - Connection closed - Verbindung beendet - - - - Unknown error - Unbekannter Fehler - - - - Request aborted - Anfrage wurde abgebrochen - - - - No server set to connect to - Kein Rechner gesetzt für die Verbindung - - - - Server closed connection unexpectedly - Server hat die Verbindung unerwartet geschlossen - - - - Invalid HTTP response header - Ungültiger HTTP Antwort-Header - - - - Invalid HTTP chunked body - Ungültiger HTTP Chunked-Body - - - - QIBaseDriver - - - Error opening database - - - - - Could not start transaction - - - - - Unable to commit transaction - - - - - Unable to rollback transaction - - - - - QIBaseResult - - - Unable to create BLOB - - - - - Unable to write BLOB - - - - - Unable to open BLOB - - - - - Unable to read BLOB - - - - - Could not find array - - - - - Could not get array data - - - - - Could not get query info - - - - - Could not start transaction - - - - - Unable to commit transaction - - - - - Could not allocate statement - - - - - Could not prepare statement - - - - - Could not describe input statement - - - - - Could not describe statement - - - - - Unable to close statement - - - - - Unable to execute query - - - - - Could not fetch next item - - - - - Could not get statement info - - - - - QIODevice - - - Permission denied - Zugriff verweigert - - - - Too many open files - Zu viele Dateien geöffnet - - - - No such file or directory - Die Datei oder das Verzeichnis konnte nicht gefunden werden - - - - No space left on device - Kein freier Speicherplatz auf dem Gerät vorhanden - - - - Unknown error - Unbekannter Fehler - - - - QInputContext - - - XIM - XIM - - - - XIM input method - XIM-Eingabemethode - - - - Windows input method - Windows-Eingabemethode - - - - Mac OS X input method - Mac OS X-Eingabemethode - - - - QInputDialog - - - OK - OK - - - - Cancel - Abbrechen - - - - QLibrary - - - QLibrary::load_sys: Cannot load %1 (%2) - - - - - QLibrary::unload_sys: Cannot unload %1 (%2) - - - - - QLibrary::resolve_sys: Symbol "%1" undefined in %2 (%3) - - - - - Could not mmap '%1': %2 - - - - - Plugin verification data mismatch in '%1' - - - - - Could not unmap '%1': %2 - - - - - The plugin '%1' uses incompatible Qt library. (%2.%3.%4) [%5] - - - - - The plugin '%1' uses incompatible Qt library. Expected build key "%2", got "%3" - - - - - Unknown error - Unbekannter Fehler - - - - QLineEdit - - - Clear - Löschen - - - - Select All - Alles auswählen - - - - &Undo - &Rückgängig - - - - &Redo - Wieder&herstellen - - - - Cu&t - &Ausschneiden - - - - &Copy - &Kopieren - - - - &Paste - Einf&ügen - - - - Delete - Löschen - - - - QLineEditPrivate - - - &Undo - &Rückgängig - - - - &Redo - Wieder&herstellen - - - - Cu&t - &Ausschneiden - - - - &Copy - &Kopieren - - - - &Paste - Einf&ügen - - - - Delete - Löschen - - - - Select All - Alles auswählen - - - - QLocalFs - - - Could not rename -%1 -to -%2 - Konnte nicht umbenannt werden: -%1 -nach -%2 - - - - Could not open -%1 - Konnte nicht geöffnet werden: -%1 - - - - Could not write -%1 - Konnte nicht geschrieben werden: -%1 - - - - Could not read directory -%1 - Konnte Verzeichnis nicht lesen -%1 - - - - Could not create directory -%1 - Konnte Verzeichnis nicht erstellen -%1 - - - - Could not remove file or directory -%1 - Konnte Datei oder Verzeichnis nicht löschen -%1 - - - - QMYSQLDriver - - - Unable to open database ' - - - - - Unable to connect - - - - - Unable to begin transaction - - - - - Unable to commit transaction - - - - - Unable to rollback transaction - - - - - QMYSQLResult - - - Unable to fetch data - - - - - Unable to execute query - - - - - Unable to store result - - - - - Unable to prepare statement - - - - - Unable to reset statement - - - - - Unable to bind value - - - - - Unable to execute statement - - - - - Unable to bind outvalues - - - - - Unable to store statement results - - - - - QMainWindow - - - Line up - Ausrichten - - - - Customize... - Anpassen... - - - - QMenu - - - Close - Schließen - - - - Open - Öffnen - - - - Execute - - - - - QMenuBar - - - About - Info - - - - Config - Konfiguration - - - - Preference - Einstellungen - - - - Options - Optionen - - - - Setting - Einstellungen - - - - Setup - Einrichten - - - - Quit - Beenden - - - - Exit - Beenden - - - - About %1 - - - - - About Qt - Über Qt - - - - Preferences - - - - - Quit %1 - - - - - QMessageBox - - - OK - OK - - - - Cancel - Abbrechen - - - - &Yes - &Ja - - - - &No - N&ein - - - - &Abort - &Abbrechen - - - - &Retry - &Wiederholen - - - - &Ignore - &Ignorieren - - - - <h3>About Qt</h3><p>This application was created with Qt version %1.</p> - <h3>Über Qt</h3><p>Dieses Programm wurde mit der Qt Version %1 erstellt.</p> - - - - <p>This program uses Qt version %1.</p><p>Qt is a C++ toolkit for cross-platform GUI &amp; application development.</p><p>Qt provides single-source portability across Windows 95/98/NT4/ME/2000, Mac&nbsp;OS&nbsp;X, Linux, Solaris, HP-UX and many other versions of Unix with X11.<br>Qt is also available for embedded devices.</p><p>Qt is a Trolltech product. See <tt>http://www.trolltech.com/qt/</tt> for more information.</p> - <p>Dieses Programm verwendet Qt Version %1</p><p>Qt ist ein multi-platform Framework zum Erstellen von GUI-Programmen in C++.</p><p>Qt bietet Portierungsmöglichkeiten mit nur einer Quellcode-Basis auf Windows 95/98/NT/ME/2000, Mac&nbsp;OS&nbsp;X, Linux, Solaris, HP-UX und vielen anderen Unix-Versionen, die X11 verwenden.<br>Qt ist auch für eingebettete Systeme erhältlich.</p><p>Qt ist ein Produkt von Trolltech. Weitere Informationen finden Sie unter <tt>http://www.trolltech.com/qt/</tt>.</p> - - - - <h3>About Qt</h3><p>This program uses Qt version %1.</p><p>Qt is a C++ toolkit for cross-platform GUI &amp; application development.</p><p>Qt provides single-source portability across Windows 95/98/NT4/ME/2000, Mac&nbsp;OS&nbsp;X, Linux, Solaris, HP-UX and many other versions of Unix with X11.<br>Qt is also available for embedded devices.</p><p>Qt is a Trolltech product. See <tt>http://www.trolltech.com/qt/</tt> for more information.</p> - <h3>Über Qt</h3><p>Dieses Programm verwendet Qt Version %1</p><p>Qt ist ein multi-platform Framework zum Erstellen von GUI-Programmen in C++.</p><p>Qt bietet Portierungsmöglichkeiten mit nur einer Quellcode-Basis auf Windows 95/98/NT/ME/2000, Mac&nbsp;OS&nbsp;X, Linux, Solaris, HP-UX und vielen anderen Unix-Versionen, die X11 verwenden.<br>Qt ist auch für eingebettete Systeme erhältlich.</p><p>Qt ist ein Produkt von Trolltech. Weitere Informationen finden Sie unter <tt>http://www.trolltech.com/qt/</tt>.</p> - - - - <h3>About Qt</h3><p>This program uses Qt version %1.</p><p>Qt is a C++ toolkit for multiplatform GUI &amp; application development.</p><p>Qt provides single-source portability across MS&nbsp;Windows, Mac&nbsp;OS&nbsp;X, Linux, and all major commercial Unix variants.<br>Qt is also available for embedded devices.</p><p>Qt is a Trolltech product. See <tt>http://www.trolltech.com/qt/</tt> for more information.</p> - <h3>Über Qt</h3><p>Dieses Programm verwendet Qt Version %1</p><p>Qt ist ein multi-platform Framework zum Erstellen von GUI-Programmen in C++.</p><p>Qt bietet Portierungsmöglichkeiten mit nur einer Quellcode-Basis auf MS&nbsp;Windows, Mac&nbsp;OS&nbsp;X, Linux und allen anderen großen kommerziellen Unix-Versionen.<br>Qt ist auch für eingebettete Systeme erhältlich.</p><p>Qt ist ein Produkt von Trolltech. Weitere Informationen finden Sie unter <tt>http://www.trolltech.com/qt/</tt>.</p> - - - - Yes to &All - Ja zu &allem - - - - N&o to All - N&ein zu allem - - - - About Qt - Über Qt - - - - Help - Hilfe - - - - <h3>About Qt</h3>%1<p>Qt is a C++ toolkit for cross-platform application development.</p><p>Qt provides single-source portability across MS&nbsp;Windows, Mac&nbsp;OS&nbsp;X, Linux, and all major commercial Unix variants. Qt is also available for embedded devices as Qtopia Core.</p><p>Qt is a Trolltech product. See <tt>http://www.trolltech.com/qt/</tt> for more information.</p> - <h3>Über Qt</h3>%1<p>Qt ist ein plattformübergreifendes C++-Framework zum Erstellen von Programmen.</p><p>Qt macht es möglich mit nur einer einzigen Quellcode-Basis Programme für verschiedene Plattformen wie MS&nbsp;Windows, Mac&nbsp;OS&nbsp;X, Linux und alle wichtigen Unix-Versionen zu erstellen. Mit Qtopia Core ist Qt auch für eingebettete Systeme erhältlich.</p><p>Qt ist ein Produkt von Trolltech. Weitere Informationen finden Sie unter <tt>http://www.trolltech.com/qt/</tt>.</p> - - - - <p>This program uses Qt version %1.</p> - <p>Dieses Programm verwendet Qt Version %1.</p> - - - - <p>This program uses Qt Open Source Edition version %1.</p><p>Qt Open Source Edition is intended for the development of Open Source applications. You need a commercial Qt license for development of proprietary (closed source) applications.</p><p>Please see <tt>http://www.trolltech.com/company/model.html</tt> for an overview of Qt licensing.</p> - <p>Dieses Programm verwendet Qt Open Source Edition Version %1.</p><p>Die Qt Open Source Edition ist bestimmt für die Entwicklung von Open-Source-Programmen. Wenn Sie proprietäre (Closed Source) Software entwickeln brauchen Sie eine komerzielle Qt-Lizenz.</p><p>Um mehr über das Lizenzsystem von Qt zu erfahren besuchen Sie die Seite <tt>http://www.trolltech.com/company/model.html</tt>.</p> - - - - <h3>About Qt</h3>%1<p>Qt is a C++ toolkit for cross-platform application development.</p><p>Qt provides single-source portability across MS&nbsp;Windows, Mac&nbsp;OS&nbsp;X, Linux, and all major commercial Unix variants. Qt is also available for embedded devices as Qtopia Core.</p><p>Qt is a Trolltech product. See <a href="http://www.trolltech.com/qt/">www.trolltech.com/qt/</a> for more information.</p> - - - - - Show Details... - - - - - Hide Details... - - - - - <p>This program uses Qt Open Source Edition version %1.</p><p>Qt Open Source Edition is intended for the development of Open Source applications. You need a commercial Qt license for development of proprietary (closed source) applications.</p><p>Please see <a href="http://www.trolltech.com/company/model/">www.trolltech.com/company/model/</a> for an overview of Qt licensing.</p> - - - - - QMultiInputContext - - - Select IM - - - - - QMultiInputContextPlugin - - - Multiple input method switcher - - - - - Multiple input method switcher that uses the context menu of the text widgets - - - - - QNativeSocketEngine - - - The remote host closed the connection - Der entfernte Rechner schloss die Verbindung - - - - Network operation timed out - Das Zeitlimit für die Operation wurde überschritten - - - - Out of resources - Keine Resourcen verfügbar - - - - Unsupported socket operation - Nichtunterstütztes Socket-Kommando - - - - Protocol type not supported - Das Protokoll wird nicht unterstützt - - - - Invalid socket descriptor - Ungültiger Socket-Deskriptor - - - - Network unreachable - Das Netzwerk ist nicht erreichbar - - - - Permission denied - Zugriff verweigert - - - - Connection timed out - Das Zeitlimit für die Verbindung wurde überschritten - - - - Connection refused - Verbindung verweigert - - - - The bound address is already in use - Die angegebene Adresse ist bereits in Gebrauch - - - - The address is not available - Die Adresse ist nicht verfügbar - - - - The address is protected - Die Adresse ist geschützt - - - - Datagram was to large to send - Das Datagramm war zu groß um gesendet zu werden - - - - Unable to send a message - Die Nachricht konnte nicht gesendet werden - - - - Unable to receive a message - Die Nachricht konnte nicht empfangen werden - - - - Unable to write - Der Schreibvorgang konnte nicht ausgeführt werden - - - - Network error - Netzwerkfehler - - - - Another socket is already listening on the same port - Ein anderer Socket hört bereits auf denselben Port - - - - Unable to initialize non-blocking socket - Der nichtblockierende Socket konnte nicht initialisiert werden - - - - Unable to initialize broadcast socket - Der Broadcast-Socket konnte nicht initialisiert werden - - - - Attempt to use IPv6 socket on a platform with no IPv6 support - Es wurde versucht einen IPv6-Socket auf einem System ohne IPv6-Unterstützung zu verwenden - - - - Host unreachable - - - - - Datagram was too large to send - - - - - Operation on non-socket - - - - - Unknown error - Unbekannter Fehler - - - - QNetworkProtocol - - - Operation stopped by the user - Operation von Benutzer angehalten - - - - QOCIDriver - - - Unable to logon - - - - - Unable to initialize - QOCIDriver - - - - - QOCIResult - - - Unable to bind column for batch execute - - - - - Unable to execute batch statement - - - - - Unable to goto next - - - - - Unable to alloc statement - - - - - Unable to prepare statement - - - - - Unable to bind value - - - - - Unable to execute select statement - - - - - Unable to execute statement - - - - - QODBCDriver - - - Unable to connect - - - - - Unable to connect - Driver doesn't support all needed functionality - - - - - Unable to disable autocommit - - - - - Unable to commit transaction - - - - - Unable to rollback transaction - - - - - Unable to enable autocommit - - - - - QODBCResult - - - QODBCResult::reset: Unable to set 'SQL_CURSOR_STATIC' as statement attribute. Please check your ODBC driver configuration - - - - - Unable to execute statement - - - - - Unable to fetch next - - - - - Unable to prepare statement - - - - - Unable to bind variable - - - - - QObject - - - All Files (*) - Alle Dateien (*) - - - - All Files (*.*) - Alle Dateien (*.*) - - - - Open - Öffnen - - - - Save As - Speichern unter - - - - Open - Öffnen - - - - Select a Directory - Wählen Sie ein Verzeichnis - - - - Drive - Laufwerk - - - - File - Datei - - - - Directory - Verzeichnis - - - - Symbolic Link - Verknüpfung - - - - Unknown - Unbekannt - - - - False - Falsch - - - - True - Wahr - - - - Home - Pos1 - - - - QPSQLDriver - - - Unable to connect - - - - - Could not begin transaction - - - - - Could not commit transaction - - - - - Could not rollback transaction - - - - - QPSQLResult - - - Unable to create query - - - - - QPageSetupDialog - - - OK - OK - - - - Cancel - Abbrechen - - - - Portrait - Hochformat - - - - Landscape - Querformat - - - - QPluginLoader - - - Unknown error - Unbekannter Fehler - - - - QPrintDialog - - - locally connected - direkt verbunden - - - - Aliases: %1 - Alias: %1 - - - - unknown - unbekannt - - - - Unknown Location - Unbekannte Adresse - - - - OK - OK - - - - Cancel - Abbrechen - - - - Printer settings - Drucker-Einstellungen - - - - Print in color if available - Farbig drucken falls möglich - - - - Print in grayscale - Als Graustufen drucken - - - - Print destination - Drucker - - - - Print to printer: - Auf Drucker drucken: - - - - Printer - Drucker - - - - Host - Rechner - - - - Comment - Kommentar - - - - Print to file: - Drucken in Datei: - - - - Browse... - Durchsuchen... - - - - Options - Optionen - - - - Print all - Alles drucken - - - - Print range - Bereich drucken - - - - From page: - Von Seite: - - - - To page: - Bis Seite: - - - - Print first page first - Erste Seite zuerst drucken - - - - Print last page first - Letzte Seite zuerst drucken - - - - Number of copies: - Anzahl der Kopien: - - - - Paper format - Papierformat - - - - Portrait - Hochformat - - - - Landscape - Querformat - - - - A0 (841 x 1189 mm) - A0 (841 x 1189 mm) - - - - A1 (594 x 841 mm) - A1 (594 x 841 mm) - - - - A2 (420 x 594 mm) - A2 (420 x 594 mm) - - - - A3 (297 x 420 mm) - A3 (297 x 420 mm) - - - - A4 (210x297 mm, 8.26x11.7 inches) - A4 (210 x 297 mm, 8,26 x 11,7 Zoll) - - - - A5 (148 x 210 mm) - A5 (148 x 210 mm) - - - - A6 (105 x 148 mm) - A6 (105 x 148 mm) - - - - A7 (74 x 105 mm) - A7 (74 x 105 mm) - - - - A8 (52 x 74 mm) - A8 (52 x 74 mm) - - - - A9 (37 x 52 mm) - A9 (37 x 52 mm) - - - - B0 (1000 x 1414 mm) - B0 (1000 x 1414 mm) - - - - B1 (707 x 1000 mm) - B1 (707 x 1000 mm) - - - - B2 (500 x 707 mm) - B2 (500 x 707 mm) - - - - B3 (353 x 500 mm) - B3 (353 x 500 mm) - - - - B4 (250 x 353 mm) - B4 (250 x 353 mm) - - - - B5 (176 x 250 mm, 6.93x9.84 inches) - B5 (176 x 250 mm, 6.93 x 9.84 Zoll) - - - - B6 (125 x 176 mm) - B6 (125 x 176 mm) - - - - B7 (88 x 125 mm) - B7 (88 x 125 mm) - - - - B8 (62 x 88 mm) - B8 (62 x 88 mm) - - - - B9 (44 x 62 mm) - B9 (44 x 62 mm) - - - - B10 (31 x 44 mm) - B10 (31 x 44 mm) - - - - C5E (163 x 229 mm) - C5E (163 x 229 mm) - - - - DLE (110 x 220 mm) - DLE (110 x 220 mm) - - - - Executive (7.5x10 inches, 191x254 mm) - Executive (7,5 x 10 Zoll, 191 x 254 mm) - - - - Folio (210 x 330 mm) - Folio (210 x 330 mm) - - - - Ledger (432 x 279 mm) - Ledger (432 x 279 mm) - - - - Legal (8.5x14 inches, 216x356 mm) - Legal (8,5 x 14 Zoll, 216 x 356 mm) - - - - Letter (8.5x11 inches, 216x279 mm) - Letter (8,5 x 11 Zoll, 216 x 279 mm) - - - - Tabloid (279 x 432 mm) - Tabloid (279 x 432 mm) - - - - US Common #10 Envelope (105 x 241 mm) - US Common #10 Envelope (105 x 241 mm) - - - - Setup Printer - Drucker-Einstellungen - - - - PostScript Files (*.ps);;All Files (*) - PostScript-Dateien (*.ps);;Alle Dateien (*) - - - - A4 (210 x 297 mm, 8.26 x 11.7 inches) - A4 (210 x 297 mm) - - - - B5 (176 x 250 mm, 6.93 x 9.84 inches) - B5 (176 x 250 mm) - - - - Executive (7.5 x 10 inches, 191 x 254 mm) - Executive (7,5 x 10 Zoll, 191 x 254 mm) - - - - Legal (8.5 x 14 inches, 216 x 356 mm) - Legal (8,5 x 14 Zoll, 216 x 356 mm) - - - - Letter (8.5 x 11 inches, 216 x 279 mm) - Letter (8,5 x 11 Zoll, 216 x 279 mm) - - - - Print selection - Die Selektion Drucken - - - - Page size: - Seitengröße: - - - - Orientation: - Druckausrichtung: - - - - Paper source: - Papierquelle: - - - - Print - Druck - - - - File - Datei - - - - Print To File ... - - - - - Print dialog - - - - - Size: - - - - - Properties - - - - - Printer info: - - - - - Browse - - - - - Print to file - - - - - Pages from - - - - - to - - - - - Selection - - - - - Copies - - - - - Collate - - - - - Other - Sonstiges - - - - Double side printing - - - - - File %1 is not writable. -Please choose a different file name. - - - - - %1 already exists. -Do you want to overwrite it? - - - - - File exists - - - - - <qt>Do you want to overwrite it?</qt> - - - - - QPrintDialogPrivate - - - Printer settings - Drucker-Einstellungen - - - - Print in color if available - Farbig drucken falls möglich - - - - Print in grayscale - Als Graustufen drucken - - - - Print destination - Drucker - - - - Print to printer: - Auf Drucker drucken: - - - - Print to file: - Drucken in Datei: - - - - Browse... - Durchsuchen... - - - - Options - Optionen - - - - Print all - Alles drucken - - - - Print range - Bereich drucken - - - - From page: - Von Seite: - - - - To page: - Bis Seite: - - - - Print first page first - Erste Seite zuerst drucken - - - - Print last page first - Letzte Seite zuerst drucken - - - - Number of copies: - Anzahl der Kopien: - - - - Paper format - Papierformat - - - - Portrait - Hochformat - - - - Landscape - Querformat - - - - PostScript Files (*.ps);;All Files (*) - PostScript-Dateien (*.ps);;Alle Dateien (*) - - - - OK - OK - - - - Cancel - Abbrechen - - - - QPrintPropertiesDialog - - - PPD Properties - - - - - Save - Speichern - - - - OK - OK - - - - QProcess - - - Unknown error - Unbekannter Fehler - - - - QProgressBar - - - %1% - %1 % - - - - QProgressDialog - - - Cancel - Abbrechen - - - - QPushButton - - - Open - Öffnen - - - - QRadioButton - - - Check - - - - - QRegExp - - - no error occurred - kein Fehler - - - - disabled feature used - nicht aktivierte Eigenschaft wurde benutzt - - - - bad char class syntax - falsche Syntax für Zeichen Klasse - - - - bad lookahead syntax - falsche Syntax für Lookahead - - - - bad repetition syntax - falsche Syntax für Wiederholungen - - - - invalid octal value - ungültiger Oktal-Wert - - - - missing left delim - fehlende linke Begrenzung - - - - unexpected end - unerwartetes Ende - - - - met internal limit - internes Limit erreicht - - - - QSQLite2Driver - - - Error to open database - - - - - Unable to begin transaction - - - - - Unable to commit transaction - - - - - Unable to rollback Transaction - - - - - QSQLite2Result - - - Unable to fetch results - - - - - Unable to execute statement - - - - - QSQLiteDriver - - - Error opening database - - - - - Error closing database - - - - - Unable to begin transaction - - - - - Unable to commit transaction - - - - - Unable to roll back transaction - - - - - QSQLiteResult - - - Unable to fetch row - - - - - Unable to execute statement - - - - - Unable to reset statement - - - - - Unable to bind parameters - - - - - Parameter count mismatch - - - - - QScrollBar - - - Scroll here - Hier scrollen - - - - Left edge - Linke Seite - - - - Top - Anfang - - - - Right edge - Rechte Seite - - - - Bottom - Ende - - - - Page left - Eine Seite nach links - - - - Page up - Eine Seite nach oben - - - - Page right - Eine Seite nach rechts - - - - Page down - Eine Seite nach unten - - - - Scroll left - Nach links scrollen - - - - Scroll up - Nach oben scrollen - - - - Scroll right - Nach rechts scrollen - - - - Scroll down - Nach unten scrollen - - - - Line up - Ausrichten - - - - Position - - - - - Line down - - - - - QShortcut - - - Space - Leertaste - - - - Esc - Esc - - - - Tab - Tab - - - - Backtab - Rück-Tab - - - - Backspace - Rücktaste - - - - Return - Return - - - - Enter - Enter - - - - Ins - Einfg - - - - Del - Entf - - - - Pause - Pause - - - - Print - Druck - - - - SysReq - SysReq - - - - Home - Pos1 - - - - End - Ende - - - - Left - Links - - - - Up - Hoch - - - - Right - Rechts - - - - Down - Runter - - - - PgUp - Bild aufwärts - - - - PgDown - Bild abwärts - - - - CapsLock - Feststelltaste - - - - NumLock - Zahlen-Feststelltaste - - - - ScrollLock - Rollen-Feststelltaste - - - - Menu - Menü - - - - Help - Hilfe - - - - Back - Zurück - - - - Forward - Vorwärts - - - - Stop - Abbrechen - - - - Refresh - Aktualisieren - - - - Volume Down - Lautstärke - - - - - Volume Mute - Ton aus - - - - Volume Up - Lautstärke + - - - - Bass Boost - Bass Boost - - - - Bass Up - Bass + - - - - Bass Down - Bass - - - - - Treble Up - Höhen + - - - - Treble Down - Höhen - - - - - Media Play - Wiedergabe - - - - Media Stop - Stopp - - - - Media Previous - Vorheriger - - - - Media Next - Nächster - - - - Media Record - Aufzeichnen - - - - Favorites - Favoriten - - - - Search - Suchen - - - - Standby - Standby - - - - Open URL - Öffne URL - - - - Launch Mail - Start Mail - - - - Launch Media - Start Media Player - - - - Launch (0) - Start (0) - - - - Launch (1) - Start (1) - - - - Launch (2) - Start (2) - - - - Launch (3) - Start (3) - - - - Launch (4) - Start (4) - - - - Launch (5) - Start (5) - - - - Launch (6) - Start (6) - - - - Launch (7) - Start (7) - - - - Launch (8) - Start (8) - - - - Launch (9) - Start (9) - - - - Launch (A) - Start (A) - - - - Launch (B) - Start (B) - - - - Launch (C) - Start (C) - - - - Launch (D) - Start (D) - - - - Launch (E) - Start (E) - - - - Launch (F) - Start (F) - - - - Print Screen - Bildschirm drucken - - - - Page Up - Bild aufwärts - - - - Page Down - Bild abwärts - - - - Caps Lock - Feststelltaste - - - - Num Lock - Zahlen-Feststelltaste - - - - Number Lock - Zahlen-Feststelltaste - - - - Scroll Lock - Rollen-Feststelltaste - - - - Insert - Einfügen - - - - Delete - Löschen - - - - Escape - Escape - - - - System Request - System Request - - - - Select - Auswählen - - - - Yes - Ja - - - - No - Nein - - - - Context1 - Kontext1 - - - - Context2 - Kontext2 - - - - Context3 - Kontext3 - - - - Context4 - Kontext4 - - - - Call - Anruf - - - - Hangup - Auflegen - - - - Flip - Umdrehen - - - - Ctrl - Strg - - - - Shift - Umschalt - - - - Alt - Alt - - - - Meta - Meta - - - - + - + - - - - F%1 - F%1 - - - - Home Page - - - - - QSlider - - - Page left - Eine Seite nach links - - - - Page up - Eine Seite nach oben - - - - Position - - - - - Page right - Eine Seite nach rechts - - - - Page down - Eine Seite nach unten - - - - QSocks5SocketEngine - - - Socks5 timeout error connecting to socks server - Die Socks5-Verbindung zu einen Socks-Server hat das Zeitlimit überschritten - - - - QSpinBox - - - More - - - - - Less - - - - - QSql - - - Delete - Löschen - - - - Delete this record? - Diesen Datensatz löschen? - - - - Yes - Ja - - - - No - Nein - - - - Insert - Einfügen - - - - Update - Aktualisieren - - - - Save edits? - Änderungen speichern? - - - - Cancel - Abbrechen - - - - Confirm - Bestätigen - - - - Cancel your edits? - Änderungen verwerfen? - - - - QTDSDriver - - - Unable to open connection - - - - - Unable to use database - - - - - QTabBar - - - Scroll Left - - - - - Scroll Right - - - - - QTabDialog - - - OK - OK - - - - Apply - Anwenden - - - - Help - Hilfe - - - - Defaults - Defaults - - - - Cancel - Abbrechen - - - - QTcpServer - - - Socket operation unsupported - Nichtunterstütztes Socket-Kommando - - - - QTextControl - - - &Undo - &Rückgängig - - - - &Redo - Wieder&herstellen - - - - Cu&t - &Ausschneiden - - - - &Copy - &Kopieren - - - - Copy &Link Location - - - - - &Paste - Einf&ügen - - - - Delete - Löschen - - - - Select All - Alles auswählen - - - - QTextEdit - - - Clear - Löschen - - - - Select All - Alles auswählen - - - - &Undo - &Rückgängig - - - - &Redo - Wieder&herstellen - - - - Cu&t - &Ausschneiden - - - - &Copy - &Kopieren - - - - &Paste - Einf&ügen - - - - Delete - Löschen - - - - QTitleBar - - - System Menu - System-Menü - - - - Shade - Aufrollen - - - - Unshade - Herabrollen - - - - Normalize - Wiederherstellen - - - - Minimize - Minimieren - - - - Maximize - Maximieren - - - - Close - Schließen - - - - QToolBar - - - More... - Mehr... - - - - QToolButton - - - Press - - - - - Open - Öffnen - - - - QUdpSocket - - - This platform does not support IPv6 - Diese Plattform unterstützt kein IPv6 - - - - QUndoGroup - - - Undo - Rückgängig - - - - Redo - Wiederherstellen - - - - QUndoModel - - - <empty> - - - - - QUndoStack - - - Undo - Rückgängig - - - - Redo - Wiederherstellen - - - - QUnicodeControlCharacterMenu - - - LRM Left-to-right mark - LRM Left-to-right mark - - - - RLM Right-to-left mark - RLM Right-to-left mark - - - - ZWJ Zero width joiner - ZWJ Zero width joiner - - - - ZWNJ Zero width non-joiner - ZWNJ Zero width non-joiner - - - - ZWSP Zero width space - ZWSP Zero width space - - - - LRE Start of left-to-right embedding - LRE Start of left-to-right embedding - - - - RLE Start of right-to-left embedding - RLE Start of right-to-left embedding - - - - LRO Start of left-to-right override - LRO Start of left-to-right override - - - - RLO Start of right-to-left override - RLO Start of right-to-left override - - - - PDF Pop directional formatting - PDF Pop directional formatting - - - - Insert Unicode control character - Unicode-Kontrollzeichen einfügen - - - - QUrlOperator - - - The protocol `%1' is not supported - Das Protokoll `%1' wird nicht unterstützt - - - - The protocol `%1' does not support listing directories - Das Protokoll "%1" wird nicht unterstützt oder es "%2" unterstützt nicht die Auflistung von Verzeichnissen - - - - The protocol `%1' does not support creating new directories - Das Protokoll `%1' unterstützt nicht das Anlegen neuer Verzeichnisse - - - - The protocol `%1' does not support removing files or directories - Das Protokoll `%1' untersützt nicht das Löschen von Dateien oder Verzeichnissen - - - - The protocol `%1' does not support renaming files or directories - Das Protokoll `%1' untersützt nicht das Umbenennen von Dateien oder Verzeichnissen - - - - The protocol `%1' does not support getting files - Das Protokoll `%1' untersützt nicht das Laden von Files - - - - The protocol `%1' does not support putting files - Das Protokoll `%1' untersützt nicht das Speichern von Files - - - - The protocol `%1' does not support copying or moving files or directories - Das Protokoll `%1' untersützt nicht das Kopieren oder Verschieben von Dateien oder Verzeichnissen - - - - (unknown) - (unbekannt) - - - - QWSDecoration - - - &Restore - Wieder&herstellen - - - - &Move - Ver&schieben - - - - &Size - &Größe - - - - Mi&nimize - M&inimieren - - - - Ma&ximize - Ma&ximieren - - - - Close - Schließen - - - - Windows - Windows - - - - KDE - KDE - - - - KDE2 - KDE2 - - - - Hydro - Hydro - - - - Default - Standard - - - - BeOS - BeOS - - - - QWSManager - - - Style - Stil - - - - QWhatsThisAction - - - What's This? - Direkthilfe - - - - QWhatsThisButton - - - What's this? - Direkthilfe - - - - QWidget - - - * - * - - - - QWizard - - - Cancel - Abbrechen - - - - < Back - < Zurück - - - - Next > - Weiter > - - - - Finish - Abschließen - - - - Help - Hilfe - - - - &Cancel - &Abbrechen - - - - < &Back - < &Zurück - - - - &Next > - &Weiter > - - - - &Finish - Ab&schließen - - - - &Help - &Hilfe - - - - QWorkspace - - - &Restore - Wieder&herstellen - - - - &Move - Ver&schieben - - - - &Size - &Größe ändern - - - - Mi&nimize - M&inimieren - - - - Ma&ximize - Ma&ximieren - - - - &Close - Schl&ießen - - - - Stay on &Top - Im &Vordergrund bleiben - - - - Minimize - Minimieren - - - - Restore Down - Wiederherstellen - - - - Close - Schließen - - - - Sh&ade - &Aufrollen - - - - %1 - [%2] - %1 - [%2] - - - - &Unshade - &Herabrollen - - - - QWorkspacePrivate - - - &Restore - Wieder&herstellen - - - - &Move - Ver&schieben - - - - Mi&nimize - M&inimieren - - - - Ma&ximize - Ma&ximieren - - - - &Close - Schl&ießen - - - - Stay on &Top - Im &Vordergrund bleiben - - - - Sh&ade - &Aufrollen - - - - %1 - [%2] - %1 - [%2] - - - - Minimize - Minimieren - - - - Restore Down - Wiederherstellen - - - - Close - Schließen - - - - &Unshade - &Herabrollen - - - - QXml - - - no error occurred - kein Fehler - - - - error triggered by consumer - Konsument löste Fehler aus - - - - unexpected end of file - unerwartetes Ende der Datei - - - - more than one document type definition - mehr als eine Dokumenttypdefinition - - - - error occurred while parsing element - Fehler beim Parsen eines Elements - - - - tag mismatch - Element-Tags sind nicht richtig geschachtelt - - - - error occurred while parsing content - Fehler beim Parsen des Inhalts eines Elements - - - - unexpected character - unerwartetes Zeichen - - - - invalid name for processing instruction - ungültiger Namer für eine Processing-Instruktion - - - - version expected while reading the XML declaration - fehlende Version beim Parsen der XML-Deklaration - - - - wrong value for standalone declaration - falscher Wert für die Standalone-Deklaration - - - - error occurred while parsing document type definition - Fehler beim Parsen der Dokumenttypdefinition - - - - letter is expected - ein Buchstabe ist an dieser Stelle erforderlich - - - - error occurred while parsing comment - Fehler beim Parsen eines Kommentars - - - - error occurred while parsing reference - Fehler beim Parsen einer Referenz - - - - internal general entity reference not allowed in DTD - interne allgeimeine Entity-Referenz ist nicht erlaubt in der DTD - - - - external parsed general entity reference not allowed in attribute value - externe Entity-Referenz is nicht erlaubt in einem Attribut-Wert - - - - external parsed general entity reference not allowed in DTD - externe Entity-Referenz ist nicht erlaubt in der DTD - - - - unparsed entity reference in wrong context - nicht-analysierte Entity-Referenz im falschen Kontext verwendet - - - - recursive entities - rekursive Enitity - - - - error in the text declaration of an external entity - Fehler in der Text-Deklaration einer externen Entity - - - - encoding declaration or standalone declaration expected while reading the XML declaration - fehlende Encoding-Deklaration oder Standalone-Deklaration beim Parsen der XML-Deklaration - - - - standalone declaration expected while reading the XML declaration - fehlende Standalone-Deklaration beim Parsen der XML Deklaration - - - - QtMultiLineEdit - - - Undo - Rückgängig - - - - Redo - Wiederherstellen - - - - Cut - Ausschneiden - - - - Copy - Kopieren - - - - Paste - Einfügen - - - - Paste special... - Einfügen spezial... - - - - Clear - Löschen - - - - Select All - Alles auswählen - - - diff -Nru treeline-2.0.2/i18n/translations/qt_es.ts treeline-3.0.1/i18n/translations/qt_es.ts --- treeline-2.0.2/i18n/translations/qt_es.ts 2018-04-08 20:12:57.000000000 +0000 +++ treeline-3.0.1/i18n/translations/qt_es.ts 1970-01-01 00:00:00.000000000 +0000 @@ -1,4295 +0,0 @@ - - - - PPDOptionsModel - - - Name - Nombre - - - - Value - - - - - Q3Accel - - - %1, %2 not defined - La secuencia %1, %2 no está definida - - - - Ambiguous %1 not handled - Secuencia ambigua %1 no tratada - - - - Q3DataTable - - - True - Verdadero - - - - False - Falso - - - - Insert - Insertar - - - - Update - Actualizar - - - - Delete - Borrar - - - - Q3FileDialog - - - Copy or Move a File - Copiar o mover un fichero - - - - Read: %1 - Lectura: %1 - - - - Write: %1 - Escritura: %1 - - - - Cancel - Cancelar - - - - All Files (*) - Todos los ficheros (*) - - - - Name - Nombre - - - - Size - Tamaño - - - - Type - Tipo - - - - Date - Fecha - - - - Attributes - Atributos - - - - &OK - &Aceptar - - - - Look &in: - Buscar &en: - - - - File &name: - &Nombre de fichero: - - - - File &type: - &Tipo de fichero: - - - - Back - Precedente (histórico) - - - - One directory up - Ir al directorio superior - - - - Create New Folder - Crear una nueva carpeta - - - - List View - Vista de lista - - - - Detail View - Vista detallada - - - - Preview File Info - Información del fichero previsualizado - - - - Preview File Contents - Contenido del fichero previsualizado - - - - Read-write - Lectura-escritura - - - - Read-only - Sólo lectura - - - - Write-only - Sólo escritura - - - - Inaccessible - Inaccesible - - - - Symlink to File - Enlace simbólico a un fichero - - - - Symlink to Directory - Enlace simbólico a un directorio - - - - Symlink to Special - Enlace simbólico a un fichero especial - - - - File - Fichero - - - - Dir - Dir - - - - Special - Fichero especial - - - - Open - Abrir - - - - Save As - Guardar como - - - - &Open - &Abrir - - - - &Save - &Guardar - - - - &Rename - &Renombrar - - - - &Delete - &Borrar - - - - R&eload - R&ecargar - - - - Sort by &Name - Ordenar por &nombre - - - - Sort by &Size - Ordenar por &tamaño - - - - Sort by &Date - Ordenar por &fecha - - - - &Unsorted - &Sin ordenar - - - - Sort - Ordenar - - - - Show &hidden files - Mostrar los ficheros &ocultos - - - - the file - el fichero - - - - the directory - el directorio - - - - the symlink - el enlace simbólico - - - - Delete %1 - Borrar %1 - - - - <qt>Are you sure you wish to delete %1 "%2"?</qt> - <qt>¿Seguro que desea borrar %1 «%2»?</qt> - - - - &Yes - &Sí - - - - &No - &No - - - - New Folder 1 - Nueva carpeta 1 - - - - New Folder - Nueva carpeta - - - - New Folder %1 - Nueva carpeta %1 - - - - Find Directory - Buscar en el directorio - - - - Directories - Directorios - - - - Directory: - Directorio: - - - - Error - Error - - - - %1 -File not found. -Check path and filename. - %1 -Fichero no encontrado. -Compruebe la ruta y el nombre del fichero. - - - - All Files (*.*) - Todos los ficheros (*.*) - - - - Open - Abrir - - - - Select a Directory - Seleccionar un directorio - - - - Q3LocalFs - - - Could not read directory -%1 - No se ha podido leer el directorio -%1 - - - - Could not create directory -%1 - No se ha podido crear el directorio -%1 - - - - Could not remove file or directory -%1 - No se ha podido borrar el fichero o directorio -%1 - - - - Could not rename -%1 -to -%2 - No se ha podido renombrar -%1 -a -%2 - - - - Could not open -%1 - No se ha podido abrir -%1 - - - - Could not write -%1 - No se ha podido escribir -%1 - - - - Q3MainWindow - - - Line up - Alinear - - - - Customize... - Personalizar... - - - - Q3NetworkProtocol - - - Operation stopped by the user - Operación detenida por el usuario - - - - Q3ProgressDialog - - - Cancel - Cancelar - - - - Q3TabDialog - - - OK - Aceptar - - - - Apply - Aplicar - - - - Help - Ayuda - - - - Defaults - Valores por omisión - - - - Cancel - Cancelar - - - - Q3TextEdit - - - &Undo - &Deshacer - - - - &Redo - &Rehacer - - - - Cu&t - Cor&tar - - - - &Copy - &Copiar - - - - &Paste - &Pegar - - - - Clear - Limpiar - - - - Select All - Seleccionar todo - - - - Q3TitleBar - - - System - - - - - Restore up - - - - - Minimize - Minimizar - - - - Restore down - - - - - Maximize - - - - - Close - Cerrar - - - - Contains commands to manipulate the window - - - - - Puts a minimized back to normal - - - - - Moves the window out of the way - - - - - Puts a maximized window back to normal - - - - - Makes the window full screen - - - - - Closes the window - - - - - Displays the name of the window and contains controls to manipulate it - - - - - Q3ToolBar - - - More... - Más... - - - - Q3UrlOperator - - - The protocol `%1' is not supported - El protocolo «%1» no está contemplado - - - - The protocol `%1' does not support listing directories - El protocolo «%1» no permite listar los ficheros de un directorio - - - - The protocol `%1' does not support creating new directories - El protocolo «%1» no permite crear nuevos directorios - - - - The protocol `%1' does not support removing files or directories - El protocolo «%1» no permite borrar ficheros o directorios - - - - The protocol `%1' does not support renaming files or directories - El protocolo «%1» no permite renombrar ficheros o directorios - - - - The protocol `%1' does not support getting files - El protocolo «%1» no permite recibir ficheros - - - - The protocol `%1' does not support putting files - El protocolo «%1» no permite enviar ficheros - - - - The protocol `%1' does not support copying or moving files or directories - El protocolo «%1» no permite copiar o mover ficheros o directorios - - - - (unknown) - (desconocido) - - - - Q3Wizard - - - &Cancel - &Cancelar - - - - < &Back - < &Anterior - - - - &Next > - Siguie&nte > - - - - &Finish - &Terminar - - - - &Help - &Ayuda - - - - QAbstractSocket - - - Host not found - Equipo no encontrado - - - - Connection refused - Conexión rechazada - - - - Socket operation timed out - Operación socket expirada - - - - Socket is not connected - - - - - QAbstractSpinBox - - - &Step up - &Aumentar - - - - Step &down - Re&ducir - - - - QApplication - - - Activate - Activar - - - - Executable '%1' requires Qt %2, found Qt %3. - El ejecutable «%1» requiere Qt %2 (se encontró Qt %3). - - - - Incompatible Qt Library Error - Error: biblioteca Qt incompatible - - - - QT_LAYOUT_DIRECTION - Translate this string to the string 'LTR' in left-to-right languages or to 'RTL' in right-to-left languages (such as Hebrew and Arabic) to get proper widget layout. - LTR - - - - Activates the program's main window - Activa la ventana principal del programa - - - - QAxSelect - - - Select ActiveX Control - - - - - OK - Aceptar - - - - &Cancel - &Cancelar - - - - COM &Object: - - - - - QCheckBox - - - Uncheck - - - - - Check - - - - - Toggle - - - - - QColorDialog - - - Hu&e: - &Tono: - - - - &Sat: - &Saturación: - - - - &Val: - &Brillo: - - - - &Red: - &Rojo: - - - - &Green: - &Verde: - - - - Bl&ue: - Az&ul: - - - - A&lpha channel: - Canal a&lfa: - - - - &Basic colors - Colores &básicos - - - - &Custom colors - Colores &personalizados - - - - &Define Custom Colors >> - &Definir colores personalizados >> - - - - OK - Aceptar - - - - Cancel - Cancelar - - - - &Add to Custom Colors - &Añadir a los colores personalizados - - - - Select color - Selección de un color - - - - QComboBox - - - Open - Abrir - - - - QDB2Driver - - - Unable to connect - - - - - Unable to commit transaction - - - - - Unable to rollback transaction - - - - - Unable to set autocommit - - - - - QDB2Result - - - Unable to execute statement - - - - - Unable to prepare statement - - - - - Unable to bind variable - - - - - Unable to fetch record %1 - - - - - Unable to fetch next - - - - - Unable to fetch first - - - - - QDateTimeEdit - - - AM - AM - - - - am - am - - - - PM - PM - - - - pm - pm - - - - QDialog - - - What's This? - ¿Qué es esto? - - - - QDialogButtonBox - - - OK - Aceptar - - - - Save - Guardar - - - - Open - Abrir - - - - Cancel - Cancelar - - - - Close - Cerrar - - - - Apply - Aplicar - - - - Reset - - - - - Help - Ayuda - - - - Don't Save - - - - - Discard - - - - - &Yes - &Sí - - - - Yes to &All - Sí a &todo - - - - &No - &No - - - - N&o to All - N&o a todo - - - - Save All - - - - - Abort - Interrumpir - - - - Retry - Reintentar - - - - Ignore - Ignorar - - - - Restore Defaults - - - - - Close without Saving - - - - - QDialogButtons - - - Yes to All - Sí a todo - - - - OK to All - Aceptar a todo - - - - No to All - No a todo - - - - Cancel All - Cancelar todo - - - - Yes - - - - - OK - Aceptar - - - - No - No - - - - Cancel - Cancelar - - - - Apply - Aplicar - - - - Ignore - Ignorar - - - - Retry - Reintentar - - - - Abort - Interrumpir - - - - Help - Ayuda - - - - %1 to All - %1 a todo - - - - QDirModel - - - Name - Nombre - - - - Size - Tamaño - - - - Type - Tipo - - - - Modified - Modificado - - - - Kind - Match OS X Finder - - - - - Type - All other platforms - Tipo - - - - Date Modified - - - - - QErrorMessage - - - Debug Message: - Mensaje de depuración: - - - - Warning: - Aviso: - - - - Fatal Error: - Error fatal: - - - - &Show this message again - Mo&strar este mensaje de nuevo - - - - &OK - &Aceptar - - - - QFileDialog - - - All Files (*) - Todos los ficheros (*) - - - - Directories - Directorios - - - - &Open - &Abrir - - - - &Save - &Guardar - - - - Open - Abrir - - - - Save - Guardar - - - - %1 already exists. -Do you want to replace it? - El fichero %1 ya existe. -¿Desea reemplazarlo? - - - - %1 -File not found. -Please verify the correct file name was given. - %1 -Fichero no encontrado. -Verifique que el nombre del fichero es correcto. - - - - My Computer - Mi computadora - - - - Sort - Ordenar - - - - &Rename - &Renombrar - - - - &Delete - &Borrar - - - - &Reload - &Recargar - - - - Sort by &Name - Ordenar por &nombre - - - - Sort by &Size - Ordenar por &tamaño - - - - Sort by &Date - Ordenar por &fecha - - - - &Unsorted - &Sin ordenar - - - - Show &hidden files - Mostrar los ficheros &ocultos - - - - Back - Anterior - - - - Parent Directory - Directorio superior - - - - Create New Folder - Crear una nueva carpeta - - - - List View - Vista de lista - - - - Detail View - Vista detallada - - - - Look in: - Buscar en: - - - - File name: - Nombre de fichero: - - - - Files of type: - Ficheros de tipo: - - - - Cancel - Cancelar - - - - Directory: - Directorio: - - - - -File not found. -Please verify the correct file name was given - - - - - %1 -Directory not found. -Please verify the correct directory name was given. - %1 -Directorio no encontrado. -Verique que el nombre del directorio es correcto. - - - - '%1' is write protected. -Do you want to delete it anyway? - - - - - Are sure you want to delete '%1'? - - - - - Could not delete directory. - - - - - All Files (*.*) - Todos los ficheros (*.*) - - - - Save As - Guardar como - - - - Open - Abrir - - - - Select a Directory - Seleccionar un directorio - - - - Drive - Unidad - - - - File - Fichero - - - - Unknown - Desconocido - - - - QFileDialogPrivate - - - %1 -Directory not found. -Please verify the correct directory name was given. - %1 -Directorio no encontrado. -Verique que el nombre del directorio es correcto. - - - - QFontDialog - - - &Font - &Tipo de letra - - - - Font st&yle - &Estilo del tipo de letra - - - - &Size - &Tamaño - - - - Effects - Efectos - - - - Stri&keout - &Tachado - - - - &Underline - S&ubrayado - - - - Sample - Muestra - - - - Wr&iting System - Sistema de escr&itura - - - - OK - Aceptar - - - - Apply - Aplicar - - - - Cancel - Cancelar - - - - Close - Cerrar - - - - Select Font - Seleccionar un tipo de letra - - - - QFtp - - - Not connected - No conectado - - - - Host %1 not found - Equipo %1 no encontrado - - - - Connection refused to host %1 - Conexión rechazada al equipo %1 - - - - Connected to host %1 - Conectado al equipo %1 - - - - Connection refused for data connection - Conexión para conexión de datos rechazada - - - - Unknown error - Error desconocido - - - - Connecting to host failed: -%1 - La conexión con el equipo ha fallado: -%1 - - - - Login failed: -%1 - Identificación fallida: -%1 - - - - Listing directory failed: -%1 - El listado del directorio ha fallado: -%1 - - - - Changing directory failed: -%1 - Fallo del cambio de directorio: -%1 - - - - Downloading file failed: -%1 - Fallo de la descarga del fichero: -%1 - - - - Uploading file failed: -%1 - El envío del fichero ha fallado: -%1 - - - - Removing file failed: -%1 - Eliminación de fichero fallida: -%1 - - - - Creating directory failed: -%1 - Fallo de la creación de un directorio: -%1 - - - - Removing directory failed: -%1 - Eliminación de directorio fallida: -%1 - - - - Connection closed - Conexión cerrada - - - - Host %1 found - Equipo %1 encontrado - - - - Connection to %1 closed - Conexión a %1 cerrada - - - - Host found - Equipo encontrado - - - - Connected to host - Conectado al equipo - - - - QHostInfo - - - Unknown error - Error desconocido - - - - QHostInfoAgent - - - Host not found - Equipo no encontrado - - - - Unknown address type - Dirección de tipo desconocido - - - - Unknown error - Error desconocido - - - - QHttp - - - Unknown error - Error desconocido - - - - Request aborted - Solicitud interrumpida - - - - No server set to connect to - No se ha indicado ningún servidor al que conectarse - - - - Wrong content length - Longitud del contenido errónea - - - - Server closed connection unexpectedly - El servidor cerró la conexión inesperadamente - - - - Connection refused - Conexión rechazada - - - - Host %1 not found - Equipo %1 no encontrado - - - - HTTP request failed - Solicitud HTTP fallida - - - - Invalid HTTP response header - Cabecera de respuesta HTTP no válida - - - - Invalid HTTP chunked body - Fragmento HTTP no válido - - - - Host %1 found - Equipo %1 encontrado - - - - Connected to host %1 - Conectado al equipo %1 - - - - Connection to %1 closed - Conexión a %1 cerrada - - - - Host found - Equipo encontrado - - - - Connected to host - Conectado al equipo - - - - Connection closed - Conexión cerrada - - - - QIBaseDriver - - - Error opening database - - - - - Could not start transaction - - - - - Unable to commit transaction - - - - - Unable to rollback transaction - - - - - QIBaseResult - - - Unable to create BLOB - - - - - Unable to write BLOB - - - - - Unable to open BLOB - - - - - Unable to read BLOB - - - - - Could not find array - - - - - Could not get array data - - - - - Could not get query info - - - - - Could not start transaction - - - - - Unable to commit transaction - - - - - Could not allocate statement - - - - - Could not prepare statement - - - - - Could not describe input statement - - - - - Could not describe statement - - - - - Unable to close statement - - - - - Unable to execute query - - - - - Could not fetch next item - - - - - Could not get statement info - - - - - QIODevice - - - Permission denied - Permiso denegado - - - - Too many open files - Demasiados ficheros abiertos simultáneamente - - - - No such file or directory - No hay ningún fichero o directorio con ese nombre - - - - No space left on device - No queda espacio en el dispositivo - - - - Unknown error - Error desconocido - - - - QInputContext - - - XIM - XIM - - - - XIM input method - Método de entrada XIM - - - - Windows input method - Método de entrada Windows - - - - Mac OS X input method - Método de entrada Mac OS X - - - - QInputDialog - - - OK - Aceptar - - - - Cancel - Cancelar - - - - QLibrary - - - QLibrary::load_sys: Cannot load %1 (%2) - - - - - QLibrary::unload_sys: Cannot unload %1 (%2) - - - - - QLibrary::resolve_sys: Symbol "%1" undefined in %2 (%3) - - - - - Could not mmap '%1': %2 - - - - - Plugin verification data mismatch in '%1' - - - - - Could not unmap '%1': %2 - - - - - The plugin '%1' uses incompatible Qt library. (%2.%3.%4) [%5] - - - - - The plugin '%1' uses incompatible Qt library. Expected build key "%2", got "%3" - - - - - Unknown error - Error desconocido - - - - QLineEdit - - - &Undo - &Deshacer - - - - &Redo - &Rehacer - - - - Cu&t - Cor&tar - - - - &Copy - &Copiar - - - - &Paste - &Pegar - - - - Delete - Borrar - - - - Select All - Seleccionar todo - - - - QMYSQLDriver - - - Unable to open database ' - - - - - Unable to connect - - - - - Unable to begin transaction - - - - - Unable to commit transaction - - - - - Unable to rollback transaction - - - - - QMYSQLResult - - - Unable to fetch data - - - - - Unable to execute query - - - - - Unable to store result - - - - - Unable to prepare statement - - - - - Unable to reset statement - - - - - Unable to bind value - - - - - Unable to execute statement - - - - - Unable to bind outvalues - - - - - Unable to store statement results - - - - - QMenu - - - Close - Cerrar - - - - Open - Abrir - - - - Execute - - - - - QMenuBar - - - About - - - - - Config - - - - - Preference - - - - - Options - Opciones - - - - Setting - - - - - Setup - - - - - Quit - - - - - Exit - - - - - About %1 - - - - - About Qt - Acerca de Qt - - - - Preferences - - - - - Quit %1 - - - - - QMessageBox - - - Help - Ayuda - - - - OK - Aceptar - - - - Cancel - Cancelar - - - - &Yes - &Sí - - - - &No - &No - - - - &Abort - &Interrumpir - - - - &Retry - &Reintentar - - - - &Ignore - &Ignorar - - - - Yes to &All - Sí a &todo - - - - N&o to All - N&o a todo - - - - <h3>About Qt</h3>%1<p>Qt is a C++ toolkit for cross-platform application development.</p><p>Qt provides single-source portability across MS&nbsp;Windows, Mac&nbsp;OS&nbsp;X, Linux, and all major commercial Unix variants. Qt is also available for embedded devices as Qtopia Core.</p><p>Qt is a Trolltech product. See <tt>http://www.trolltech.com/qt/</tt> for more information.</p> - <h3>Acerca de Qt</h3>%1 -<p>Qt es una caja de herramientas en C++ para el desarrollo de aplicaciones multiplataforma.</p> -<p>Qt permite la portabilidad de un solo código fuente entre MS&nbsp;Windows, Mac&nbsp;OS&nbsp;X, GNU/Linux, y las principales versiones comerciales de Unix. Qt también está disponible para los sistemas embebidos bajo el nombre de Qtopia Core.</p> -<p>Qt es un producto de Trolltech. Consulte <tt>http://www.trolltech.com/qt/</tt> para más información.</p> - - - - About Qt - Acerca de Qt - - - - <p>This program uses Qt version %1.</p> - <p>Este programa utiliza la versión %1 de Qt.</p> - - - - <p>This program uses Qt Open Source Edition version %1.</p><p>Qt Open Source Edition is intended for the development of Open Source applications. You need a commercial Qt license for development of proprietary (closed source) applications.</p><p>Please see <tt>http://www.trolltech.com/company/model.html</tt> for an overview of Qt licensing.</p> - <p>Este programa utiliza la edición Open Source de Qt versión %1.</p> -<p>La edición Open Source de Qt está destinada al desarrollo de aplicaciones libres. Necesita una licencia comercial de Qt para desarrollar aplicaciones privativas (de código cerrado).</p> -<p>Consulte en <tt>http://www.trolltech.com/company/model.html</tt> una explicación de las licencias de Qt.</p> - - - - <h3>About Qt</h3>%1<p>Qt is a C++ toolkit for cross-platform application development.</p><p>Qt provides single-source portability across MS&nbsp;Windows, Mac&nbsp;OS&nbsp;X, Linux, and all major commercial Unix variants. Qt is also available for embedded devices as Qtopia Core.</p><p>Qt is a Trolltech product. See <a href="http://www.trolltech.com/qt/">www.trolltech.com/qt/</a> for more information.</p> - - - - - Show Details... - - - - - Hide Details... - - - - - <p>This program uses Qt Open Source Edition version %1.</p><p>Qt Open Source Edition is intended for the development of Open Source applications. You need a commercial Qt license for development of proprietary (closed source) applications.</p><p>Please see <a href="http://www.trolltech.com/company/model/">www.trolltech.com/company/model/</a> for an overview of Qt licensing.</p> - - - - - QMultiInputContext - - - Select IM - - - - - QMultiInputContextPlugin - - - Multiple input method switcher - - - - - Multiple input method switcher that uses the context menu of the text widgets - - - - - QNativeSocketEngine - - - The remote host closed the connection - El equpo remoto ha cerrado la conexión - - - - Network operation timed out - La operación de red ha expirado - - - - Out of resources - Insuficientes recursos - - - - Unsupported socket operation - Operación socker no admitida - - - - Protocol type not supported - Tipo de protocolo no admitido - - - - Invalid socket descriptor - Descriptor de socket no válido - - - - Network unreachable - Red inalcanzable - - - - Permission denied - Permiso denegado - - - - Connection timed out - Conexión expirada - - - - Connection refused - Conexión rechazada - - - - The bound address is already in use - La dirección enlazada ya está en uso - - - - The address is not available - La dirección no está disponible - - - - The address is protected - La dirección está protegida - - - - Datagram was to large to send - El datagrama es demasiado grande para poder ser enviado - - - - Unable to send a message - Imposible enviar un mensaje - - - - Unable to receive a message - Imposible recibir un mensaje - - - - Unable to write - Imposible escribir - - - - Network error - Error de red - - - - Another socket is already listening on the same port - Ya hay otro socket escuchando por el mismo puerto - - - - Unable to initialize non-blocking socket - Imposible inicializar el socket no bloqueante - - - - Unable to initialize broadcast socket - Imposible inicializar el socket de difusión - - - - Attempt to use IPv6 socket on a platform with no IPv6 support - Intento de usar un socket IPv6 sobre una plataforma que no contempla IPv6 - - - - Host unreachable - - - - - Datagram was too large to send - - - - - Operation on non-socket - - - - - Unknown error - Error desconocido - - - - QOCIDriver - - - Unable to logon - - - - - Unable to initialize - QOCIDriver - - - - - QOCIResult - - - Unable to bind column for batch execute - - - - - Unable to execute batch statement - - - - - Unable to goto next - - - - - Unable to alloc statement - - - - - Unable to prepare statement - - - - - Unable to bind value - - - - - Unable to execute select statement - - - - - Unable to execute statement - - - - - QODBCDriver - - - Unable to connect - - - - - Unable to connect - Driver doesn't support all needed functionality - - - - - Unable to disable autocommit - - - - - Unable to commit transaction - - - - - Unable to rollback transaction - - - - - Unable to enable autocommit - - - - - QODBCResult - - - QODBCResult::reset: Unable to set 'SQL_CURSOR_STATIC' as statement attribute. Please check your ODBC driver configuration - - - - - Unable to execute statement - - - - - Unable to fetch next - - - - - Unable to prepare statement - - - - - Unable to bind variable - - - - - QObject - - - All Files (*) - Todos los ficheros (*) - - - - All Files (*.*) - Todos los ficheros (*.*) - - - - Open - Abrir - - - - Save As - Guardar como - - - - Open - Abrir - - - - Select a Directory - Seleccionar un directorio - - - - Drive - Unidad - - - - File - Fichero - - - - Directory - Directorio - - - - Symbolic Link - Enlace simbólico - - - - Unknown - Desconocido - - - - False - Falso - - - - True - Verdadero - - - - Home - Inicio - - - - QPSQLDriver - - - Unable to connect - - - - - Could not begin transaction - - - - - Could not commit transaction - - - - - Could not rollback transaction - - - - - QPSQLResult - - - Unable to create query - - - - - QPluginLoader - - - Unknown error - Error desconocido - - - - QPrintDialog - - - locally connected - conectado localmente - - - - Aliases: %1 - Alias: %1 - - - - unknown - desconocido - - - - Unknown Location - Ubicación desconocida - - - - Printer settings - Configuración de la impresión - - - - Print in color if available - Imprimir en color si es posible - - - - Print in grayscale - Imprimir en tonos de gris - - - - Print destination - Destino de la impresión - - - - Print to printer: - Imprimir por la impresora: - - - - Print to file: - Imprimir en un fichero: - - - - Browse... - Examinar... - - - - Options - Opciones - - - - Print all - Imprimir todo - - - - Print selection - Imprimir la selección - - - - Print range - Imprimir intervalo - - - - From page: - Desde la página: - - - - To page: - Hasta la página: - - - - Print first page first - Imprimir primero la primera página - - - - Print last page first - Imprimir primero la última página - - - - Number of copies: - Número de copias: - - - - Paper format - Formato del papel - - - - Portrait - Vertical - - - - Landscape - Apaisado - - - - A0 (841 x 1189 mm) - A0 (841 x 1189 mm) - - - - A1 (594 x 841 mm) - A1 (594 x 841 mm) - - - - A2 (420 x 594 mm) - A2 (420 x 594 mm) - - - - A3 (297 x 420 mm) - A3 (297 x 420 mm) - - - - A4 (210 x 297 mm, 8.26 x 11.7 inches) - A4 (210 x 297 mm, 8,26 x 11,7 pulgadas) - - - - A5 (148 x 210 mm) - A5 (148 x 210 mm) - - - - A6 (105 x 148 mm) - A6 (105 x 148 mm) - - - - A7 (74 x 105 mm) - A7 (74 x 105 mm) - - - - A8 (52 x 74 mm) - A8 (52 x 74 mm) - - - - A9 (37 x 52 mm) - A9 (37 x 52 mm) - - - - B0 (1000 x 1414 mm) - B0 (1000 x 1414 mm) - - - - B1 (707 x 1000 mm) - B1 (707 x 1000 mm) - - - - B2 (500 x 707 mm) - B2 (500 x 707 mm) - - - - B3 (353 x 500 mm) - B3 (353 x 500 mm) - - - - B4 (250 x 353 mm) - B4 (250 x 353 mm) - - - - B5 (176 x 250 mm, 6.93 x 9.84 inches) - B5 (176 x 250 mm, 6,93 x 9,84 pulgadas) - - - - B6 (125 x 176 mm) - B6 (125 x 176 mm) - - - - B7 (88 x 125 mm) - B7 (88 x 125 mm) - - - - B8 (62 x 88 mm) - B8 (62 x 88 mm) - - - - B9 (44 x 62 mm) - B9 (44 x 62 mm) - - - - B10 (31 x 44 mm) - B10 (31 x 44 mm) - - - - C5E (163 x 229 mm) - C5E (163 x 229 mm) - - - - DLE (110 x 220 mm) - DLE (110 x 220 mm) - - - - Executive (7.5 x 10 inches, 191 x 254 mm) - Ejecutivo (7,5 x 10 pulgadas, 191 x 254 mm) - - - - Folio (210 x 330 mm) - Folio (210 x 330 mm) - - - - Ledger (432 x 279 mm) - Ledger (432 x 279 mm) - - - - Legal (8.5 x 14 inches, 216 x 356 mm) - Legal (8,5 x 14 pulgadas, 216 x 356 mm) - - - - Letter (8.5 x 11 inches, 216 x 279 mm) - Carta (8,5 x 11 pulgadas, 216 x 279 mm) - - - - Tabloid (279 x 432 mm) - Tabloide (279 x 432 mm) - - - - US Common #10 Envelope (105 x 241 mm) - Sobre US Common #10 (105 x 241 mm) - - - - PostScript Files (*.ps);;All Files (*) - Ficheros PostScript (*.ps);;Todos los ficheros (*) - - - - OK - Aceptar - - - - Cancel - Cancelar - - - - Page size: - Tamaño de página: - - - - Orientation: - Orientación: - - - - Paper source: - Fuente del papel: - - - - Print - Impr Pant - - - - File - Fichero - - - - Printer - - - - - Print To File ... - - - - - Print dialog - - - - - Size: - - - - - Properties - - - - - Printer info: - - - - - Browse - - - - - Print to file - - - - - Pages from - - - - - to - - - - - Selection - - - - - Copies - - - - - Collate - - - - - Other - - - - - Double side printing - - - - - File %1 is not writable. -Please choose a different file name. - - - - - %1 already exists. -Do you want to overwrite it? - - - - - File exists - - - - - <qt>Do you want to overwrite it?</qt> - - - - - QPrintPropertiesDialog - - - PPD Properties - - - - - Save - Guardar - - - - OK - Aceptar - - - - QProcess - - - Unknown error - Error desconocido - - - - QProgressBar - - - %1% - %1% - - - - QProgressDialog - - - Cancel - Cancelar - - - - QPushButton - - - Open - Abrir - - - - QRadioButton - - - Check - - - - - QRegExp - - - no error occurred - no se ha producido ningún error - - - - disabled feature used - se ha usado una característica no habilitada - - - - bad char class syntax - sintaxis no válida para clase de caracteres - - - - bad lookahead syntax - sintaxis no válida para lookahead - - - - bad repetition syntax - sintaxis no válida para repetición - - - - invalid octal value - valor octal no válido - - - - missing left delim - falta el delimitador izquierdo - - - - unexpected end - fin inesperado - - - - met internal limit - se alcanzó el límite interno - - - - QSQLite2Driver - - - Error to open database - - - - - Unable to begin transaction - - - - - Unable to commit transaction - - - - - Unable to rollback Transaction - - - - - QSQLite2Result - - - Unable to fetch results - - - - - Unable to execute statement - - - - - QSQLiteDriver - - - Error opening database - - - - - Error closing database - - - - - Unable to begin transaction - - - - - Unable to commit transaction - - - - - Unable to roll back transaction - - - - - QSQLiteResult - - - Unable to fetch row - - - - - Unable to execute statement - - - - - Unable to reset statement - - - - - Unable to bind parameters - - - - - Parameter count mismatch - - - - - QScrollBar - - - Scroll here - Desplazar hasta aquí - - - - Left edge - Borde izquierdo - - - - Top - Principio - - - - Right edge - Borde derecho - - - - Bottom - Final - - - - Page left - Una página a la izquierda - - - - Page up - Una página hacia arriba - - - - Page right - Una página a la derecha - - - - Page down - Una página hacia abajo - - - - Scroll left - Desplazar hacia la izquierda - - - - Scroll up - Desplazar hacia arriba - - - - Scroll right - Desplazar hacia la derecha - - - - Scroll down - Desplazar hacia abajo - - - - Line up - Alinear - - - - Position - - - - - Line down - - - - - QShortcut - - - Space - Espacio - - - - Esc - Esc - - - - Tab - Tab - - - - Backtab - Tabulador hacia atrás - - - - Backspace - Borrar - - - - Return - Retorno - - - - Enter - Intro - - - - Ins - Ins - - - - Del - Supr - - - - Pause - Pausa - - - - Print - Impr Pant - - - - SysReq - PetSis - - - - Home - Inicio - - - - End - Fin - - - - Left - Izquierda - - - - Up - Arriba - - - - Right - Derecha - - - - Down - Abajo - - - - PgUp - Re Pág - - - - PgDown - Av Pág - - - - CapsLock - Bloq Mayús - - - - NumLock - Bloq Num - - - - ScrollLock - Bloq Despl - - - - Menu - Menú - - - - Help - Ayuda - - - - Back - Anterior - - - - Forward - Siguiente - - - - Stop - Detener - - - - Refresh - Actualizar - - - - Volume Down - Bajar el volumen - - - - Volume Mute - Silenciar - - - - Volume Up - Subir el volumen - - - - Bass Boost - Potenciar los graves - - - - Bass Up - Subir los graves - - - - Bass Down - Bajar los graves - - - - Treble Up - Subir los agudos - - - - Treble Down - Bajar los agudos - - - - Media Play - Reproducir el medio - - - - Media Stop - Detener el medio - - - - Media Previous - Medio anterior - - - - Media Next - Siguiente medio - - - - Media Record - Grabar medio - - - - Favorites - Favoritos - - - - Search - Búsqueda - - - - Standby - Reposo - - - - Open URL - Abrir URL - - - - Launch Mail - Lanzar correo - - - - Launch Media - Lanzar medio - - - - Launch (0) - Lanzar (0) - - - - Launch (1) - Lanzar (1) - - - - Launch (2) - Lanzar (2) - - - - Launch (3) - Lanzar (3) - - - - Launch (4) - Lanzar (4) - - - - Launch (5) - Lanzar (5) - - - - Launch (6) - Lanzar (6) - - - - Launch (7) - Lanzar (7) - - - - Launch (8) - Lanzar (8) - - - - Launch (9) - Lanzar (9) - - - - Launch (A) - Lanzar (A) - - - - Launch (B) - Lanzar (B) - - - - Launch (C) - Lanzar (C) - - - - Launch (D) - Lanzar (D) - - - - Launch (E) - Lanzar (E) - - - - Launch (F) - Lanzar (F) - - - - Print Screen - Imprimir pantalla - - - - Page Up - Retroceder página - - - - Page Down - Avanzar página - - - - Caps Lock - Bloqueo de mayúsculas - - - - Num Lock - Bloq num - - - - Number Lock - Bloqueo numérico - - - - Scroll Lock - Bloqueo del desplazamiento - - - - Insert - Insertar - - - - Delete - Borrar - - - - Escape - Escape - - - - System Request - Petición del sistema - - - - Select - Seleccionar - - - - Yes - - - - - No - No - - - - Context1 - Contexto1 - - - - Context2 - Contexto2 - - - - Context3 - Contexto3 - - - - Context4 - Contexto4 - - - - Call - Llamar - - - - Hangup - Descolgar - - - - Flip - Voltear - - - - Ctrl - Ctrl - - - - Shift - May - - - - Alt - Alt - - - - Meta - Meta - - - - + - + - - - - F%1 - F%1 - - - - Home Page - - - - - QSlider - - - Page left - Una página a la izquierda - - - - Page up - Una página hacia arriba - - - - Position - - - - - Page right - Una página a la derecha - - - - Page down - Una página hacia abajo - - - - QSocks5SocketEngine - - - Socks5 timeout error connecting to socks server - Error de expiracioń socks5 mientras se establecía una conexión al servidor socks - - - - QSpinBox - - - More - - - - - Less - - - - - QSql - - - Delete - Borrar - - - - Delete this record? - ¿Borrar este registro? - - - - Yes - - - - - No - No - - - - Insert - Insertar - - - - Update - Actualizar - - - - Save edits? - ¿Guardar las modificaciones? - - - - Cancel - Cancelar - - - - Confirm - Confirmar - - - - Cancel your edits? - ¿Cancelar sus modificaciones? - - - - QTDSDriver - - - Unable to open connection - - - - - Unable to use database - - - - - QTabBar - - - Scroll Left - - - - - Scroll Right - - - - - QTcpServer - - - Socket operation unsupported - Operación socket no admitida - - - - QTextControl - - - &Undo - &Deshacer - - - - &Redo - &Rehacer - - - - Cu&t - Cor&tar - - - - &Copy - &Copiar - - - - Copy &Link Location - - - - - &Paste - &Pegar - - - - Delete - Borrar - - - - Select All - Seleccionar todo - - - - QTextEdit - - - &Undo - &Deshacer - - - - &Redo - &Rehacer - - - - Cu&t - Cor&tar - - - - &Copy - &Copiar - - - - &Paste - &Pegar - - - - Delete - Borrar - - - - Select All - Seleccionar todo - - - - QToolButton - - - Press - - - - - Open - Abrir - - - - QUdpSocket - - - This platform does not support IPv6 - La plataforma no contempla IPv6 - - - - QUndoGroup - - - Undo - - - - - Redo - - - - - QUndoModel - - - <empty> - - - - - QUndoStack - - - Undo - - - - - Redo - - - - - QUnicodeControlCharacterMenu - - - LRM Left-to-right mark - LRM Left-to-right mark - - - - RLM Right-to-left mark - RLM Right-to-left mark - - - - ZWJ Zero width joiner - ZWJ Zero width joiner - - - - ZWNJ Zero width non-joiner - ZWNJ Zero width non-joiner - - - - ZWSP Zero width space - ZWSP Zero width space - - - - LRE Start of left-to-right embedding - LRE Start of left-to-right embedding - - - - RLE Start of right-to-left embedding - RLE Start of right-to-left embedding - - - - LRO Start of left-to-right override - LRO Start of left-to-right override - - - - RLO Start of right-to-left override - RLO Start of right-to-left override - - - - PDF Pop directional formatting - PDF Pop directional formatting - - - - Insert Unicode control character - Insertar carácter de control Unicode - - - - QWhatsThisAction - - - What's This? - ¿Qué es esto? - - - - QWidget - - - * - * - - - - QWorkspace - - - &Restore - &Restaurar - - - - &Move - &Mover - - - - &Size - &Tamaño - - - - Mi&nimize - Mi&nimizar - - - - Ma&ximize - Ma&ximizar - - - - &Close - &Cerrar - - - - Stay on &Top - Permanecer en &primer plano - - - - Sh&ade - Sombre&ar - - - - %1 - [%2] - %1 - [%2] - - - - Minimize - Minimizar - - - - Restore Down - Restaurar abajo - - - - Close - Cerrar - - - - &Unshade - Q&uitar sombra - - - - QXml - - - no error occurred - no se ha producido ningún error - - - - error triggered by consumer - error debido al consumidor - - - - unexpected end of file - fin de fichero inesperado - - - - more than one document type definition - más de una definición de tipo de documento - - - - error occurred while parsing element - se ha producido un error durante el análisis de un elemento - - - - tag mismatch - etiqueta desequilibrada - - - - error occurred while parsing content - se ha producido un error durante el análisis del contenido - - - - unexpected character - carácter inesperado - - - - invalid name for processing instruction - nombre de instrucción de tratamiento no válido - - - - version expected while reading the XML declaration - se esperaba la versión al leer la declaración XML - - - - wrong value for standalone declaration - valor erróneo para la declaración independiente - - - - encoding declaration or standalone declaration expected while reading the XML declaration - se esperaba una declaración de codificación o declaración autónoma al leer la declaración XML - - - - standalone declaration expected while reading the XML declaration - se esperaba una declaración independiente al leer la declaración XML - - - - error occurred while parsing document type definition - se ha producido un error durante el análisis de la definición de tipo de documento - - - - letter is expected - se esperaba una letra - - - - error occurred while parsing comment - se ha producido un error durante el análisis de un comentario - - - - error occurred while parsing reference - se ha producido un error durante el análisis de una referencia - - - - internal general entity reference not allowed in DTD - no se permiten referencias a entidades internas generales en la DTD - - - - external parsed general entity reference not allowed in attribute value - no se permiten referencias a entidades externas generales ya analizadas en el valor de un atributo - - - - external parsed general entity reference not allowed in DTD - no se permiten referencias a entidades externas generales ya analizadas en la DTD - - - - unparsed entity reference in wrong context - referencia a entidad no analizada en un contexto no válido - - - - recursive entities - entidades recursivas - - - - error in the text declaration of an external entity - error en la declaración de texto de una entidad externa - - - diff -Nru treeline-2.0.2/i18n/translations/qt_fr.ts treeline-3.0.1/i18n/translations/qt_fr.ts --- treeline-2.0.2/i18n/translations/qt_fr.ts 2018-04-08 20:12:57.000000000 +0000 +++ treeline-3.0.1/i18n/translations/qt_fr.ts 1970-01-01 00:00:00.000000000 +0000 @@ -1,4290 +0,0 @@ - - - - PPDOptionsModel - - - Name - Nom - - - - Value - - - - - Q3Accel - - - %1, %2 not defined - La séquence %1, %2 n'est pas définie - - - - Ambiguous %1 not handled - Séquence ambiguë %1 non traitée - - - - Q3DataTable - - - True - Vrai - - - - False - Faux - - - - Insert - Insérer - - - - Update - Actualiser - - - - Delete - Supprimer - - - - Q3FileDialog - - - Copy or Move a File - Copie ou déplace un fichier - - - - Read: %1 - Lecture : %1 - - - - Write: %1 - Écriture : %1 - - - - Cancel - Annuler - - - - All Files (*) - Tous les fichiers (*) - - - - Name - Nom - - - - Size - Taille - - - - Type - Type - - - - Date - Date - - - - Attributes - Attributs - - - - &OK - &OK - - - - Look &in: - Chercher &dans : - - - - File &name: - &Nom de fichier : - - - - File &type: - &Type de fichier : - - - - Back - Précédent (historique) - - - - One directory up - Aller au dossier parent - - - - Create New Folder - Créer un nouveau dossier - - - - List View - Affichage liste - - - - Detail View - Affichage détaillé - - - - Preview File Info - Informations du fichier prévisualisé - - - - Preview File Contents - Contenu du fichier prévisualisé - - - - Read-write - Lecture-écriture - - - - Read-only - Lecture seule - - - - Write-only - Écriture seule - - - - Inaccessible - Inaccessible - - - - Symlink to File - Lien symbolique vers un fichier - - - - Symlink to Directory - Lien symbolique vers un dossier - - - - Symlink to Special - Lien symbolique vers un fichier spécial - - - - File - Fichier - - - - Dir - Dossier - - - - Special - Fichier spécial - - - - Open - Ouvrir - - - - Save As - Enregistrer sous - - - - &Open - &Ouvrir - - - - &Save - &Enregistrer - - - - &Rename - &Renommer - - - - &Delete - Suppri&mer - - - - R&eload - R&echarger - - - - Sort by &Name - Trier par &nom - - - - Sort by &Size - Trier par ta&ille - - - - Sort by &Date - Trier par &date - - - - &Unsorted - &Non trié - - - - Sort - Tri - - - - Show &hidden files - Afficher les fic&hiers cachés - - - - the file - le fichier - - - - the directory - le dossier - - - - the symlink - le lien symbolique - - - - Delete %1 - Supprimer %1 - - - - <qt>Are you sure you wish to delete %1 "%2"?</qt> - <qt>Voulez-vous vraiment supprimer %1 "%2"?</qt> - - - - &Yes - &Oui - - - - &No - &Non - - - - New Folder 1 - Nouveau dossier 1 - - - - New Folder - Nouveau dossier - - - - New Folder %1 - Nouveau dossier %1 - - - - Find Directory - Chercher dans le dossier - - - - Directories - Dossiers - - - - Directory: - Dossier : - - - - Error - Erreur - - - - %1 -File not found. -Check path and filename. - %1 -Impossible de trouver le fichier. -Vérifier le chemin et le nom du fichier. - - - - All Files (*.*) - Tous les fichiers (*.*) - - - - Open - Ouvrir - - - - Select a Directory - Sélectionner un dossier - - - - Q3LocalFs - - - Could not read directory -%1 - Impossible de lire le dossier -%1 - - - - Could not create directory -%1 - Impossible de créer le dossier -%1 - - - - Could not remove file or directory -%1 - Impossible de supprimer le fichier ou dossier -%1 - - - - Could not rename -%1 -to -%2 - Impossible de renommer -%1 -en -%2 - - - - Could not open -%1 - Impossible d'ouvrir -%1 - - - - Could not write -%1 - Impossible d'écrire -%1 - - - - Q3MainWindow - - - Line up - Aligner - - - - Customize... - Personnaliser... - - - - Q3NetworkProtocol - - - Operation stopped by the user - Opération interrompue par l'utilisateur - - - - Q3ProgressDialog - - - Cancel - Annuler - - - - Q3TabDialog - - - OK - OK - - - - Apply - Appliquer - - - - Help - Aide - - - - Defaults - Par défaut - - - - Cancel - Annuler - - - - Q3TextEdit - - - &Undo - &Annuler - - - - &Redo - A&nnuler Annuler - - - - Cu&t - Co&uper - - - - &Copy - Cop&ier - - - - &Paste - Co&ller - - - - Clear - Effacer - - - - Select All - Tout sélectionner - - - - Q3TitleBar - - - System - - - - - Restore up - - - - - Minimize - Réduire - - - - Restore down - - - - - Maximize - - - - - Close - Fermer - - - - Contains commands to manipulate the window - - - - - Puts a minimized back to normal - - - - - Moves the window out of the way - - - - - Puts a maximized window back to normal - - - - - Makes the window full screen - - - - - Closes the window - - - - - Displays the name of the window and contains controls to manipulate it - - - - - Q3ToolBar - - - More... - Reste... - - - - Q3UrlOperator - - - The protocol `%1' is not supported - Le protocole '%1' n'est pas géré - - - - The protocol `%1' does not support listing directories - Le protocole `%1' ne permet pas de lister les fichiers d'un dossier - - - - The protocol `%1' does not support creating new directories - Le protocole `%1' ne permet pas de créer de nouveaux dossiers - - - - The protocol `%1' does not support removing files or directories - Le protocole `%1' ne permet pas de supprimer des fichiers ou des dossiers - - - - The protocol `%1' does not support renaming files or directories - Le protocole `%1' ne permet pas de renommer des fichiers ou des dossiers - - - - The protocol `%1' does not support getting files - Le protocole `%1' ne permet pas de recevoir des fichiers - - - - The protocol `%1' does not support putting files - Le protocole `%1' ne permet pas d'envoyer des fichiers - - - - The protocol `%1' does not support copying or moving files or directories - Le protocole `%1' ne permet pas de copier ou de déplacer des fichiers - - - - (unknown) - (inconnu) - - - - Q3Wizard - - - &Cancel - &Annuler - - - - < &Back - < &Précédent - - - - &Next > - &Suivant > - - - - &Finish - &Terminer - - - - &Help - &Aide - - - - QAbstractSocket - - - Host not found - Hôte introuvable - - - - Connection refused - Connexion refusée - - - - Socket operation timed out - Opération socket expirée - - - - Socket is not connected - - - - - QAbstractSpinBox - - - &Step up - &Augmenter - - - - Step &down - &Diminuer - - - - QApplication - - - QT_LAYOUT_DIRECTION - Translate this string to the string 'LTR' in left-to-right languages or to 'RTL' in right-to-left languages (such as Hebrew and Arabic) to get proper widget layout. - LTR - - - - Executable '%1' requires Qt %2, found Qt %3. - L'exécutable '%1' requiert Qt %2 (Qt %3 présent). - - - - Incompatible Qt Library Error - Erreur : bibliothèque Qt incompatible - - - - Activate - Activer - - - - Activates the program's main window - Active la fenêtre principale du programme - - - - QAxSelect - - - Select ActiveX Control - - - - - OK - OK - - - - &Cancel - &Annuler - - - - COM &Object: - - - - - QCheckBox - - - Uncheck - - - - - Check - - - - - Toggle - - - - - QColorDialog - - - Hu&e: - &Teinte : - - - - &Sat: - &Saturation : - - - - &Val: - &Valeur : - - - - &Red: - &Rouge : - - - - &Green: - &Vert : - - - - Bl&ue: - Ble&u : - - - - A&lpha channel: - Canal a&lpha : - - - - &Basic colors - Couleurs de &base - - - - &Custom colors - &Couleurs personnalisées - - - - &Define Custom Colors >> - Définir des &couleurs personnalisées >> - - - - OK - OK - - - - Cancel - Annuler - - - - &Add to Custom Colors - &Ajouter aux couleurs personnalisées - - - - Select color - Sélection d'une couleur - - - - QComboBox - - - Open - Ouvrir - - - - QDB2Driver - - - Unable to connect - - - - - Unable to commit transaction - - - - - Unable to rollback transaction - - - - - Unable to set autocommit - - - - - QDB2Result - - - Unable to execute statement - - - - - Unable to prepare statement - - - - - Unable to bind variable - - - - - Unable to fetch record %1 - - - - - Unable to fetch next - - - - - Unable to fetch first - - - - - QDateTimeEdit - - - AM - AM - - - - am - am - - - - PM - PM - - - - pm - pm - - - - QDialog - - - What's This? - Qu'est-ce que c'est ? - - - - QDialogButtonBox - - - OK - OK - - - - Save - Enregistrer - - - - Open - Ouvrir - - - - Cancel - Annuler - - - - Close - Fermer - - - - Apply - Appliquer - - - - Reset - - - - - Help - Aide - - - - Don't Save - - - - - Discard - - - - - &Yes - &Oui - - - - Yes to &All - Oui à &tout - - - - &No - &Non - - - - N&o to All - Non à to&ut - - - - Save All - - - - - Abort - Abandonner - - - - Retry - Réessayer - - - - Ignore - Ignorer - - - - Restore Defaults - - - - - Close without Saving - - - - - QDialogButtons - - - Yes to All - Oui à tout - - - - OK to All - OK à tout - - - - No to All - Non à tout - - - - Cancel All - Tout annuler - - - - Yes - Oui - - - - OK - OK - - - - No - Non - - - - Cancel - Annuler - - - - Apply - Appliquer - - - - Ignore - Ignorer - - - - Retry - Réessayer - - - - Abort - Abandonner - - - - Help - Aide - - - - %1 to All - %1 à tout - - - - QDirModel - - - Name - Nom - - - - Size - Taille - - - - Type - Type - - - - Modified - Modifié - - - - Kind - Match OS X Finder - - - - - Type - All other platforms - Type - - - - Date Modified - - - - - QErrorMessage - - - &Show this message again - &Afficher ce message de nouveau - - - - &OK - &OK - - - - Debug Message: - Message de débogage: - - - - Warning: - Avertissement: - - - - Fatal Error: - Erreur fatale: - - - - QFileDialog - - - Cancel - Annuler - - - - Back - Précédent (historique) - - - - Create New Folder - Créer un nouveau dossier - - - - List View - Affichage liste - - - - Detail View - Affichage détaillé - - - - Open - Ouvrir - - - - &Open - &Ouvrir - - - - &Save - &Enregistrer - - - - &Rename - &Renommer - - - - &Delete - Suppri&mer - - - - Sort by &Name - Trier par &nom - - - - Sort by &Size - Trier par ta&ille - - - - Sort by &Date - Trier par &date - - - - &Unsorted - &Non trié - - - - Sort - Tri - - - - Show &hidden files - Afficher les fic&hiers cachés - - - - Directories - Dossiers - - - - Save - Enregistrer - - - - All Files (*) - Tous les fichiers (*) - - - - %1 already exists. -Do you want to replace it? - Le fichier %1 existe déjà. Voulez-vous l'écraser ? - - - - %1 -File not found. -Please verify the correct file name was given. - %1 -Fichier introuvable. -Veuillez vérifier que le nom du fichier est correct. - - - - My Computer - Mon ordinateur - - - - &Reload - &Recharger - - - - Parent Directory - Dossier parent - - - - Look in: - Chercher dans : - - - - File name: - Nom de fichier : - - - - Files of type: - Fichiers de type : - - - - Directory: - Dossier : - - - - -File not found. -Please verify the correct file name was given - - - - - %1 -Directory not found. -Please verify the correct directory name was given. - %1 -Dossier introuvable. -Veuillez vérifier que le nom du dossier est correct. - - - - '%1' is write protected. -Do you want to delete it anyway? - - - - - Are sure you want to delete '%1'? - - - - - Could not delete directory. - - - - - All Files (*.*) - Tous les fichiers (*.*) - - - - Save As - Enregistrer sous - - - - Open - Ouvrir - - - - Select a Directory - Sélectionner un dossier - - - - Drive - Unité - - - - File - Fichier - - - - Unknown - Inconnu - - - - QFileDialogPrivate - - - %1 -Directory not found. -Please verify the correct directory name was given. - %1 -Dossier introuvable. -Veuillez vérifier que le nom du dossier est correct. - - - - QFontDialog - - - &Font - &Police - - - - Font st&yle - St&yle de police - - - - &Size - &Taille - - - - Effects - Effets - - - - Stri&keout - &Barré - - - - &Underline - &Souligné - - - - Sample - Exemple - - - - OK - OK - - - - Apply - Appliquer - - - - Cancel - Annuler - - - - Close - Fermer - - - - Select Font - Choisir une police - - - - Wr&iting System - &Système d'écriture - - - - QFtp - - - Host %1 found - Hôte %1 trouvé - - - - Host found - Hôte trouvé - - - - Connected to host %1 - Connecté à l'hôte %1 - - - - Connected to host - Connecté à l'hôte - - - - Connection to %1 closed - Connexion à %1 arrêtée - - - - Connection closed - Connexion arrêtée - - - - Host %1 not found - Hôte %1 introuvable - - - - Connection refused to host %1 - Connexion à l'hôte %1 refusée - - - - Unknown error - Erreur inconnue - - - - Connecting to host failed: -%1 - Échec de la connexion à l'hôte -%1 - - - - Login failed: -%1 - Échec du login: -%1 - - - - Listing directory failed: -%1 - Échec du listage du dossier : -%1 - - - - Changing directory failed: -%1 - Échec du changement de dossier : -%1 - - - - Downloading file failed: -%1 - Échec du téléchargement du fichier : -%1 - - - - Uploading file failed: -%1 - Échec du télédéchargement : -%1 - - - - Removing file failed: -%1 - Échec de la suppression d'un fichier : -%1 - - - - Creating directory failed: -%1 - Échec de la création d'un dossier : -%1 - - - - Removing directory failed: -%1 - Échec de la suppression d'un dossier : -%1 - - - - Not connected - Non connecté - - - - Connection refused for data connection - Connexion donnée refusée - - - - QHostInfo - - - Unknown error - Erreur inconnue - - - - QHostInfoAgent - - - Host not found - Hôte introuvable - - - - Unknown address type - Adresse de type inconnu - - - - Unknown error - Erreur inconnue - - - - QHttp - - - Connection refused - Connexion refusée - - - - Host %1 not found - Hôte %1 introuvable - - - - Wrong content length - Longueur du contenu invalide - - - - HTTP request failed - Échec de la requête HTTP - - - - Host %1 found - Hôte %1 trouvé - - - - Host found - Hôte trouvé - - - - Connected to host %1 - Connecté à l'hôte %1 - - - - Connected to host - Connecté à l'hôte - - - - Connection to %1 closed - Connexion à %1 arrêtée - - - - Connection closed - Connexion arrêtée - - - - Unknown error - Erreur inconnue - - - - Request aborted - Requête interrompue - - - - No server set to connect to - Aucun serveur spécifié - - - - Server closed connection unexpectedly - Connexion interrompue par le serveur - - - - Invalid HTTP response header - Entête de réponse HTTP invalide - - - - Invalid HTTP chunked body - Fragment HTTP invalide - - - - QIBaseDriver - - - Error opening database - - - - - Could not start transaction - - - - - Unable to commit transaction - - - - - Unable to rollback transaction - - - - - QIBaseResult - - - Unable to create BLOB - - - - - Unable to write BLOB - - - - - Unable to open BLOB - - - - - Unable to read BLOB - - - - - Could not find array - - - - - Could not get array data - - - - - Could not get query info - - - - - Could not start transaction - - - - - Unable to commit transaction - - - - - Could not allocate statement - - - - - Could not prepare statement - - - - - Could not describe input statement - - - - - Could not describe statement - - - - - Unable to close statement - - - - - Unable to execute query - - - - - Could not fetch next item - - - - - Could not get statement info - - - - - QIODevice - - - Permission denied - Accès refusé - - - - Too many open files - Trop de fichiers ouverts simultanément - - - - No such file or directory - Aucun fichier ou dossier de ce nom - - - - No space left on device - Aucun espace disponible sur le périphérique - - - - Unknown error - Erreur inconnue - - - - QInputContext - - - XIM - XIM - - - - XIM input method - Méthode d'entrée XIM - - - - Windows input method - Méthode d'entrée Windows - - - - Mac OS X input method - Méthode d'entrée Mac OS X - - - - QInputDialog - - - OK - OK - - - - Cancel - Annuler - - - - QLibrary - - - QLibrary::load_sys: Cannot load %1 (%2) - - - - - QLibrary::unload_sys: Cannot unload %1 (%2) - - - - - QLibrary::resolve_sys: Symbol "%1" undefined in %2 (%3) - - - - - Could not mmap '%1': %2 - - - - - Plugin verification data mismatch in '%1' - - - - - Could not unmap '%1': %2 - - - - - The plugin '%1' uses incompatible Qt library. (%2.%3.%4) [%5] - - - - - The plugin '%1' uses incompatible Qt library. Expected build key "%2", got "%3" - - - - - Unknown error - Erreur inconnue - - - - QLineEdit - - - Select All - Tout sélectionner - - - - &Undo - &Annuler - - - - &Redo - A&nnuler Annuler - - - - Cu&t - Co&uper - - - - &Copy - Cop&ier - - - - &Paste - Co&ller - - - - Delete - Supprimer - - - - QMYSQLDriver - - - Unable to open database ' - - - - - Unable to connect - - - - - Unable to begin transaction - - - - - Unable to commit transaction - - - - - Unable to rollback transaction - - - - - QMYSQLResult - - - Unable to fetch data - - - - - Unable to execute query - - - - - Unable to store result - - - - - Unable to prepare statement - - - - - Unable to reset statement - - - - - Unable to bind value - - - - - Unable to execute statement - - - - - Unable to bind outvalues - - - - - Unable to store statement results - - - - - QMenu - - - Close - Fermer - - - - Open - Ouvrir - - - - Execute - - - - - QMenuBar - - - About - - - - - Config - - - - - Preference - - - - - Options - Options - - - - Setting - - - - - Setup - - - - - Quit - - - - - Exit - - - - - About %1 - - - - - About Qt - À propos de Qt - - - - Preferences - - - - - Quit %1 - - - - - QMessageBox - - - OK - OK - - - - Cancel - Annuler - - - - &Yes - &Oui - - - - &No - &Non - - - - &Abort - &Abandonner - - - - &Retry - &Réessayer - - - - &Ignore - &Ignorer - - - - Yes to &All - Oui à &tout - - - - N&o to All - Non à to&ut - - - - About Qt - À propos de Qt - - - - Help - Aide - - - - <h3>About Qt</h3>%1<p>Qt is a C++ toolkit for cross-platform application development.</p><p>Qt provides single-source portability across MS&nbsp;Windows, Mac&nbsp;OS&nbsp;X, Linux, and all major commercial Unix variants. Qt is also available for embedded devices as Qtopia Core.</p><p>Qt is a Trolltech product. See <tt>http://www.trolltech.com/qt/</tt> for more information.</p> - <h3>À propos de Qt</h3>%1<p>Qt est une boîte à outils C++ pour le développement multi-plateforme d'applications graphiques.</p><p>Qt permet de porter un même source code sur MS&nbsp;Windows, Mac&nbsp;OS&nbsp;X, Linux ainsi que les principales versions Unix commerciales. Qt est aussi disponible pour les systèmes embarqués sous le nom de Qtopia Core.</p><p>Qt est un produit de Trolltech. Consultez <tt>http://www.trolltech.com/qt/</tt> pour de plus amples renseignements.</p> - - - - <p>This program uses Qt version %1.</p> - <p>Ce programme utilise la version %1 de Qt.</p> - - - - <p>This program uses Qt Open Source Edition version %1.</p><p>Qt Open Source Edition is intended for the development of Open Source applications. You need a commercial Qt license for development of proprietary (closed source) applications.</p><p>Please see <tt>http://www.trolltech.com/company/model.html</tt> for an overview of Qt licensing.</p> - <p>Ce programme utilise l'édition Open Source de Qt version %1.</p><p>L'édition Open Source de Qt est destinée au développement de programmes Open Source. Si vous désirez écrire des applications commerciales (code source fermé), vous devez posséder une licence Qt commerciale.</p><p>Veuillez consulter <tt>http://www.trolltech.com/company/model.html</tt> pour un aperçu des licences applicables à Qt.</p> - - - - <h3>About Qt</h3>%1<p>Qt is a C++ toolkit for cross-platform application development.</p><p>Qt provides single-source portability across MS&nbsp;Windows, Mac&nbsp;OS&nbsp;X, Linux, and all major commercial Unix variants. Qt is also available for embedded devices as Qtopia Core.</p><p>Qt is a Trolltech product. See <a href="http://www.trolltech.com/qt/">www.trolltech.com/qt/</a> for more information.</p> - - - - - Show Details... - - - - - Hide Details... - - - - - <p>This program uses Qt Open Source Edition version %1.</p><p>Qt Open Source Edition is intended for the development of Open Source applications. You need a commercial Qt license for development of proprietary (closed source) applications.</p><p>Please see <a href="http://www.trolltech.com/company/model/">www.trolltech.com/company/model/</a> for an overview of Qt licensing.</p> - - - - - QMultiInputContext - - - Select IM - - - - - QMultiInputContextPlugin - - - Multiple input method switcher - - - - - Multiple input method switcher that uses the context menu of the text widgets - - - - - QNativeSocketEngine - - - The remote host closed the connection - L'hôte distant a fermé la connexion - - - - Network operation timed out - L'opération réseau a expiré - - - - Out of resources - Manque de ressources - - - - Unsupported socket operation - Opération socket non supportée - - - - Protocol type not supported - Protocol non géré - - - - Invalid socket descriptor - Descripteur de socket invalide - - - - Network unreachable - Réseau impossible à rejoindre - - - - Permission denied - Accès refusé - - - - Connection timed out - Connexion expirée - - - - Connection refused - Connexion refusée - - - - The bound address is already in use - L'adresse liée est déjà en usage - - - - The address is not available - L'adresse n'est pas disponible - - - - The address is protected - L'adresse est protégée - - - - Datagram was to large to send - Le datagramme est trop gros pour pouvoir être envoyé - - - - Unable to send a message - Impossible d'envoyer un message - - - - Unable to receive a message - Impossible de recevoir un message - - - - Unable to write - Impossible d'écrire - - - - Network error - Erreur réseau - - - - Another socket is already listening on the same port - Un autre socket écoute déjà sur le même port - - - - Unable to initialize non-blocking socket - Impossible d'initialiser le socket asynchrone - - - - Unable to initialize broadcast socket - Impossible d'initialiser le socket broadcast - - - - Attempt to use IPv6 socket on a platform with no IPv6 support - Tentative d'utiliser un socket IPv6 sur une plateforme qui ne supporte pas IPv6 - - - - Host unreachable - - - - - Datagram was too large to send - - - - - Operation on non-socket - - - - - Unknown error - Erreur inconnue - - - - QOCIDriver - - - Unable to logon - - - - - Unable to initialize - QOCIDriver - - - - - QOCIResult - - - Unable to bind column for batch execute - - - - - Unable to execute batch statement - - - - - Unable to goto next - - - - - Unable to alloc statement - - - - - Unable to prepare statement - - - - - Unable to bind value - - - - - Unable to execute select statement - - - - - Unable to execute statement - - - - - QODBCDriver - - - Unable to connect - - - - - Unable to connect - Driver doesn't support all needed functionality - - - - - Unable to disable autocommit - - - - - Unable to commit transaction - - - - - Unable to rollback transaction - - - - - Unable to enable autocommit - - - - - QODBCResult - - - QODBCResult::reset: Unable to set 'SQL_CURSOR_STATIC' as statement attribute. Please check your ODBC driver configuration - - - - - Unable to execute statement - - - - - Unable to fetch next - - - - - Unable to prepare statement - - - - - Unable to bind variable - - - - - QObject - - - All Files (*) - Tous les fichiers (*) - - - - All Files (*.*) - Tous les fichiers (*.*) - - - - Open - Ouvrir - - - - Save As - Enregistrer sous - - - - Open - Ouvrir - - - - Select a Directory - Sélectionner un dossier - - - - Drive - Unité - - - - File - Fichier - - - - Directory - Dossier - - - - Symbolic Link - Lien symbolique - - - - Unknown - Inconnu - - - - False - Faux - - - - True - Vrai - - - - Home - Début - - - - QPSQLDriver - - - Unable to connect - - - - - Could not begin transaction - - - - - Could not commit transaction - - - - - Could not rollback transaction - - - - - QPSQLResult - - - Unable to create query - - - - - QPluginLoader - - - Unknown error - Erreur inconnue - - - - QPrintDialog - - - locally connected - connecté en local - - - - unknown - inconnu - - - - Unknown Location - Emplacement inconnu - - - - OK - OK - - - - Cancel - Annuler - - - - Printer settings - Configuration de l'impression - - - - Print in color if available - Imprimer en couleur si possible - - - - Print in grayscale - Imprimer en niveaux de gris - - - - Print destination - Type d'impression - - - - Print to printer: - Imprimer avec l'imprimante : - - - - Print to file: - Imprimer dans un fichier : - - - - Browse... - Parcourir... - - - - Options - Options - - - - Print all - Imprimer tout - - - - Print range - Imprimer la sélection - - - - From page: - De la page : - - - - To page: - À la page : - - - - Print first page first - Imprimer d'abord la première page - - - - Print last page first - Imprimer d'abord la dernière page - - - - Number of copies: - Nombre de copies : - - - - Paper format - Format du papier - - - - Portrait - Portrait - - - - Landscape - Paysage - - - - A0 (841 x 1189 mm) - A0 (841 x 1189 mm) - - - - A1 (594 x 841 mm) - A1 (594 x 841 mm) - - - - A2 (420 x 594 mm) - A2 (420 x 594 mm) - - - - A3 (297 x 420 mm) - A3 (297 x 420 mm) - - - - A5 (148 x 210 mm) - A5 (148 x 210 mm) - - - - A6 (105 x 148 mm) - A6 (105 x 148 mm) - - - - A7 (74 x 105 mm) - A7 (74 x 105 mm) - - - - A8 (52 x 74 mm) - A8 (52 x 74 mm) - - - - A9 (37 x 52 mm) - A9 (37 x 52 mm) - - - - B0 (1000 x 1414 mm) - B0 (1000 x 1414 mm) - - - - B1 (707 x 1000 mm) - B1 (707 x 1000 mm) - - - - B2 (500 x 707 mm) - B2 (500 x 707 mm) - - - - B3 (353 x 500 mm) - B3 (353 x 500 mm) - - - - B4 (250 x 353 mm) - B4 (250 x 353 mm) - - - - B6 (125 x 176 mm) - B6 (125 x 176 mm) - - - - B7 (88 x 125 mm) - B7 (88 x 125 mm) - - - - B8 (62 x 88 mm) - B8 (62 x 88 mm) - - - - B9 (44 x 62 mm) - B9 (44 x 62 mm) - - - - B10 (31 x 44 mm) - B10 (31 x 44 mm) - - - - C5E (163 x 229 mm) - C5E (163 x 229 mm) - - - - DLE (110 x 220 mm) - DLE (110 x 220 mm) - - - - Folio (210 x 330 mm) - Folio (210 x 330 mm) - - - - Ledger (432 x 279 mm) - Ledger (432 x 279 mm) - - - - Tabloid (279 x 432 mm) - Tabloïde (279 x 432 mm) - - - - US Common #10 Envelope (105 x 241 mm) - US Common #10 Envelope (105 x 241 mm) - - - - Aliases: %1 - Alias : %1 - - - - PostScript Files (*.ps);;All Files (*) - Fichiers PostScript (*.ps);;Tous les fichiers (*) - - - - A4 (210 x 297 mm, 8.26 x 11.7 inches) - A4 (210 x 297 mm) - - - - B5 (176 x 250 mm, 6.93 x 9.84 inches) - B5 (176 x 250 mm) - - - - Executive (7.5 x 10 inches, 191 x 254 mm) - Executive (7,5 x 10 pouces, 191 x 254 mm) - - - - Legal (8.5 x 14 inches, 216 x 356 mm) - Legal (8.5 x 14 pouces, 216 x 356 mm) - - - - Letter (8.5 x 11 inches, 216 x 279 mm) - Letter (8,5 x 11 pouces, 216 x 279 mm) - - - - Print selection - Imprimer la sélection - - - - Page size: - Dimensions : - - - - Orientation: - Orientation : - - - - Paper source: - Source du papier : - - - - Print - Impr écran - - - - File - Fichier - - - - Printer - - - - - Print To File ... - - - - - Print dialog - - - - - Size: - - - - - Properties - - - - - Printer info: - - - - - Browse - - - - - Print to file - - - - - Pages from - - - - - to - - - - - Selection - - - - - Copies - - - - - Collate - - - - - Other - - - - - Double side printing - - - - - File %1 is not writable. -Please choose a different file name. - - - - - %1 already exists. -Do you want to overwrite it? - - - - - File exists - - - - - <qt>Do you want to overwrite it?</qt> - - - - - QPrintPropertiesDialog - - - PPD Properties - - - - - Save - Enregistrer - - - - OK - OK - - - - QProcess - - - Unknown error - Erreur inconnue - - - - QProgressBar - - - %1% - %1 % - - - - QProgressDialog - - - Cancel - Annuler - - - - QPushButton - - - Open - Ouvrir - - - - QRadioButton - - - Check - - - - - QRegExp - - - no error occurred - aucune erreur ne s'est produite - - - - disabled feature used - option désactivée - - - - bad char class syntax - syntaxe invalide pour classe de caractère - - - - bad lookahead syntax - syntaxe invalide pour lookahead - - - - bad repetition syntax - syntaxe invalide pour répétition - - - - invalid octal value - valeur octale invalide - - - - missing left delim - délémiteur gauche manquant - - - - unexpected end - fin impromptue - - - - met internal limit - rencontré limite interne - - - - QSQLite2Driver - - - Error to open database - - - - - Unable to begin transaction - - - - - Unable to commit transaction - - - - - Unable to rollback Transaction - - - - - QSQLite2Result - - - Unable to fetch results - - - - - Unable to execute statement - - - - - QSQLiteDriver - - - Error opening database - - - - - Error closing database - - - - - Unable to begin transaction - - - - - Unable to commit transaction - - - - - Unable to roll back transaction - - - - - QSQLiteResult - - - Unable to fetch row - - - - - Unable to execute statement - - - - - Unable to reset statement - - - - - Unable to bind parameters - - - - - Parameter count mismatch - - - - - QScrollBar - - - Scroll here - Défiler jusqu'ici - - - - Left edge - Extrême gauche - - - - Top - En haut - - - - Right edge - Extrême droite - - - - Bottom - En bas - - - - Page left - Page précédente - - - - Page up - Page précédente - - - - Page right - Page suivante - - - - Page down - Page suivante - - - - Scroll left - Défiler vers la gauche - - - - Scroll up - Défiler vers le haut - - - - Scroll right - Défiler vers la droite - - - - Scroll down - Défiler vers le bas - - - - Line up - Aligner - - - - Position - - - - - Line down - - - - - QShortcut - - - Space - Espace - - - - Esc - Échap - - - - Tab - Tab - - - - Backtab - Tab arr - - - - Backspace - Effacement - - - - Return - Retour - - - - Enter - Entrée - - - - Ins - Inser - - - - Del - Suppr - - - - Pause - Pause - - - - Print - Impr écran - - - - SysReq - Syst - - - - Home - Début - - - - End - Fin - - - - Left - Gauche - - - - Up - Haut - - - - Right - Droite - - - - Down - Bas - - - - PgUp - Page préc - - - - PgDown - Page suiv - - - - CapsLock - Verr maj - - - - NumLock - Verr num - - - - ScrollLock - Arrêt défil - - - - Menu - Menu - - - - Help - Aide - - - - Back - Précédent (historique) - - - - Forward - Successeur (historique) - - - - Stop - Stop - - - - Refresh - Rafraîchir - - - - Volume Down - Volume bas - - - - Volume Mute - Volume muet - - - - Volume Up - Volume haut - - - - - Bass Boost - Graves fort - - - - Bass Up - Graves haut - - - - Bass Down - Graves bas - - - - Treble Up - Aigus haut - - - - Treble Down - Aigus bas - - - - Media Play - Média démarrer - - - - Media Stop - Média arrêt - - - - Media Previous - Média précédent - - - - Media Next - Média suivant - - - - Media Record - Média enregistrer - - - - Favorites - Préférés - - - - Search - Recherche - - - - Standby - Attente - - - - Open URL - Ouvrir URL - - - - Launch Mail - Lancer courrier - - - - Launch Media - Lancer média - - - - Launch (0) - Lancer (0) - - - - Launch (1) - Lancer (1) - - - - Launch (2) - Lancer (2) - - - - Launch (3) - Lancer (3) - - - - Launch (4) - Lancer (4) - - - - Launch (5) - Lancer (5) - - - - Launch (6) - Lancer (6) - - - - Launch (7) - Lancer (7) - - - - Launch (8) - Lancer (8) - - - - Launch (9) - Lancer (9) - - - - Launch (A) - Lancer (A) - - - - Launch (B) - Lancer (B) - - - - Launch (C) - Lancer (C) - - - - Launch (D) - Lancer (D) - - - - Launch (E) - Lancer (E) - - - - Launch (F) - Lancer (F) - - - - Print Screen - - - - - Page Up - - - - - Page Down - - - - - Caps Lock - - - - - Num Lock - - - - - Number Lock - - - - - Scroll Lock - - - - - Insert - Insérer - - - - Delete - Supprimer - - - - Escape - Échapement - - - - System Request - Système - - - - Select - Sélectionner - - - - Yes - Oui - - - - No - Non - - - - Context1 - Contexte1 - - - - Context2 - Contexte2 - - - - Context3 - Contexte3 - - - - Context4 - Contexte4 - - - - Call - Appeler - - - - Hangup - Raccrocher - - - - Flip - Retourner - - - - Ctrl - Ctrl - - - - Shift - Maj - - - - Alt - Alt - - - - Meta - Méta - - - - + - + - - - - F%1 - F%1 - - - - Home Page - - - - - QSlider - - - Page left - Page précédente - - - - Page up - Page précédente - - - - Position - - - - - Page right - Page suivante - - - - Page down - Page suivante - - - - QSocks5SocketEngine - - - Socks5 timeout error connecting to socks server - Erreur d'expiration socks5 lors de l'établissement d'une connexion au serveur socks - - - - QSpinBox - - - More - - - - - Less - - - - - QSql - - - Delete - Supprimer - - - - Delete this record? - Supprimer cet enregistrement ? - - - - Yes - Oui - - - - No - Non - - - - Insert - Insérer - - - - Update - Actualiser - - - - Save edits? - Enregistrer les modifications ? - - - - Cancel - Annuler - - - - Confirm - Confirmer - - - - Cancel your edits? - Annuler vos modifications ? - - - - QTDSDriver - - - Unable to open connection - - - - - Unable to use database - - - - - QTabBar - - - Scroll Left - - - - - Scroll Right - - - - - QTcpServer - - - Socket operation unsupported - Operation socket non supportée - - - - QTextControl - - - &Undo - &Annuler - - - - &Redo - A&nnuler Annuler - - - - Cu&t - Co&uper - - - - &Copy - Cop&ier - - - - Copy &Link Location - - - - - &Paste - Co&ller - - - - Delete - Supprimer - - - - Select All - Tout sélectionner - - - - QTextEdit - - - Select All - Tout sélectionner - - - - &Undo - &Annuler - - - - &Redo - A&nnuler Annuler - - - - Cu&t - Co&uper - - - - &Copy - Cop&ier - - - - &Paste - Co&ller - - - - Delete - Supprimer - - - - QToolButton - - - Press - - - - - Open - Ouvrir - - - - QUdpSocket - - - This platform does not support IPv6 - Cette plateforme ne supporte pas IPv6 - - - - QUndoGroup - - - Undo - - - - - Redo - - - - - QUndoModel - - - <empty> - - - - - QUndoStack - - - Undo - - - - - Redo - - - - - QUnicodeControlCharacterMenu - - - LRM Left-to-right mark - LRM Left-to-right mark - - - - RLM Right-to-left mark - RLM Right-to-left mark - - - - ZWJ Zero width joiner - ZWJ Zero width joiner - - - - ZWNJ Zero width non-joiner - ZWNJ Zero width non-joiner - - - - ZWSP Zero width space - ZWSP Zero width space - - - - LRE Start of left-to-right embedding - LRE Start of left-to-right embedding - - - - RLE Start of right-to-left embedding - RLE Start of right-to-left embedding - - - - LRO Start of left-to-right override - LRO Start of left-to-right override - - - - RLO Start of right-to-left override - RLO Start of right-to-left override - - - - PDF Pop directional formatting - PDF Pop directional formatting - - - - Insert Unicode control character - Insérer caractère de contrôle Unicode - - - - QWhatsThisAction - - - What's This? - Qu'est-ce que c'est ? - - - - QWidget - - - * - * - - - - QWorkspace - - - &Restore - &Restaurer - - - - &Move - &Déplacer - - - - &Size - &Redimensionner - - - - Mi&nimize - Réd&uire - - - - Ma&ximize - Ma&ximiser - - - - &Close - &Fermer - - - - Stay on &Top - &Rester au premier plan - - - - Minimize - Réduire - - - - Restore Down - Restaurer en bas - - - - Close - Fermer - - - - Sh&ade - &Enrouler - - - - %1 - [%2] - %1 - [%2] - - - - &Unshade - &Dérouler - - - - QXml - - - no error occurred - aucune erreur ne s'est produite - - - - error triggered by consumer - erreur déclenchée par le consommateur - - - - unexpected end of file - fin de fichier impromptue - - - - more than one document type definition - plus d'une définition de type de document - - - - error occurred while parsing element - une erreur s'est produite lors de l'analyse d'un élément - - - - tag mismatch - balise débalancée - - - - error occurred while parsing content - une erreur s'est produise lors de l'analyse du contenu - - - - unexpected character - caractère impromptu - - - - invalid name for processing instruction - nom d'instruction de traitement invalide - - - - version expected while reading the XML declaration - version attendue dans la déclaration XML - - - - wrong value for standalone declaration - valeur invalide pour déclaration autonome - - - - error occurred while parsing document type definition - une erreur s'est produite lors de l'analyse d'une définition de type de document - - - - letter is expected - lettre attendue - - - - error occurred while parsing comment - une erreur s'est produise lors de l'analyse d'un commentaire - - - - error occurred while parsing reference - une erreur s'est produite lors de l'analyse d'une référence - - - - internal general entity reference not allowed in DTD - appel d'entité interne générale non permis dans la DTD - - - - external parsed general entity reference not allowed in attribute value - appel d'entité externe parsée non permis dans la valeur d'un attribut - - - - external parsed general entity reference not allowed in DTD - appel d'entité externe parsée générale non permis dans la DTD - - - - unparsed entity reference in wrong context - appel d'entité non parsée dans un contexte invalide - - - - recursive entities - entités récursives - - - - error in the text declaration of an external entity - erreur dans la déclaration de texte d'une entité externe - - - - encoding declaration or standalone declaration expected while reading the XML declaration - déclaration d'encodage ou déclaration autonome attendue dans la déclaration XML - - - - standalone declaration expected while reading the XML declaration - déclaration autonome attendue dans la déclaration XML - - - diff -Nru treeline-2.0.2/i18n/translations/qt_pt.ts treeline-3.0.1/i18n/translations/qt_pt.ts --- treeline-2.0.2/i18n/translations/qt_pt.ts 2018-04-08 20:12:57.000000000 +0000 +++ treeline-3.0.1/i18n/translations/qt_pt.ts 1970-01-01 00:00:00.000000000 +0000 @@ -1,3962 +0,0 @@ - - - - PPDOptionsModel - - - Name - Nome - - - - Value - Valor - - - - Q3Accel - - - %1, %2 not defined - %1, %2 indefinido - - - - Ambiguous %1 not handled - %1 ambíguo não tratado - - - - Q3DataTable - - - True - Verdadeiro - - - - False - Falso - - - - Insert - Inserir - - - - Update - Actualizar - - - - Delete - Remover - - - - Q3FileDialog - - - Copy or Move a File - Copiar ou Mover um Ficheiro - - - - Read: %1 - Ler: %1 - - - - Write: %1 - Escrever: %1 - - - - Cancel - Cancelar - - - - All Files (*) - Todos os Ficheiros (*) - - - - Name - Nome - - - - Size - Tamanho - - - - Type - Tipo - - - - Date - Data - - - - Attributes - Atributos - - - - &OK - &OK - - - - Look &in: - Ver &em: - - - - File &name: - &Nome do Ficheiro: - - - - File &type: - &Tipo de Ficheiro: - - - - Back - Recuar - - - - One directory up - Pasta Mãe - - - - Create New Folder - Criar Nova Pasta - - - - List View - Vista Abreviada - - - - Detail View - Vista Detalhada - - - - Preview File Info - Antever Informação do Ficheiro - - - - Preview File Contents - Antever Conteúdo do Ficheiro - - - - Read-write - Leitura e escrita - - - - Read-only - Apenas Leitura - - - - Write-only - Apenas Escrita - - - - Inaccessible - Inacessível - - - - Symlink to File - Ligação para Ficheiro - - - - Symlink to Directory - Ligação para Pasta - - - - Symlink to Special - Ligação para Especial - - - - File - Ficheiro - - - - Dir - Pasta - - - - Special - Especial - - - - Open - Abrir - - - - Save As - Guardar Como - - - - &Open - &Abrir - - - - &Save - &Gravar - - - - &Rename - &Mudar Nome - - - - &Delete - &Apagar - - - - R&eload - &Recarregar - - - - Sort by &Name - Ordenar pelo &Nome - - - - Sort by &Size - Ordenar pelo &Tamanho - - - - Sort by &Date - Ordenar pela &Data - - - - &Unsorted - Não &Ordenado - - - - Sort - Ordenar - - - - Show &hidden files - Mostrar ficheiros &escondidos - - - - the file - o ficheiro - - - - the directory - a pasta - - - - the symlink - a ligação - - - - Delete %1 - Apagar %1 - - - - <qt>Are you sure you wish to delete %1 "%2"?</qt> - <qt>Deseja mesmo apagar %1 "%2"?</qt> - - - - &Yes - &Sim - - - - &No - &Não - - - - New Folder 1 - Nova Pasta 1 - - - - New Folder - Nova Pasta - - - - New Folder %1 - Nova Pasta %1 - - - - Find Directory - Procurar Pasta - - - - Directories - Pastas - - - - Directory: - Pasta: - - - - Error - Erro - - - - %1 -File not found. -Check path and filename. - %1 -Ficheiro não encontrado. -Verifique a localização e o nome. - - - - All Files (*.*) - Todos os Ficheiros (*.*) - - - - Open - Abrir - - - - Select a Directory - Seleccione uma Pasta - - - - Q3LocalFs - - - Could not read directory -%1 - Não foi possível ler a pasta -%1 - - - - Could not create directory -%1 - Não foi possível criar a pasta -%1 - - - - Could not remove file or directory -%1 - Não foi possível apagar o ficheiro ou a pasta -%1 - - - - Could not rename -%1 -to -%2 - Não foi possível mudar o nome -%1 -para -%2 - - - - Could not open -%1 - Não foi possível abrir -%1 - - - - Could not write -%1 - Nao foi possível escrever -%1 - - - - Q3MainWindow - - - Line up - Alinhar - - - - Customize... - Configurar... - - - - Q3NetworkProtocol - - - Operation stopped by the user - Operação interrompida pelo utilizador - - - - Q3ProgressDialog - - - Cancel - Cancelar - - - - Q3TabDialog - - - OK - OK - - - - Apply - Aplicar - - - - Help - Ajuda - - - - Defaults - Predefinições - - - - Cancel - Cancelar - - - - Q3TextEdit - - - &Undo - &Desfazer - - - - &Redo - &Refazer - - - - Cu&t - Cor&tar - - - - &Copy - &Copiar - - - - &Paste - Co&lar - - - - Clear - Limpar - - - - Select All - Seleccionar Tudo - - - - Q3TitleBar - - - System - Sistema - - - - Restore up - Restaurar acima - - - - Minimize - Minimizar - - - - Restore down - Restaurar abaixo - - - - Maximize - Maximizar - - - - Close - Fechar - - - - Contains commands to manipulate the window - Contém comandos para manipular a janela - - - - Puts a minimized back to normal - Coloca uma janela minimizada no estado normal - - - - Moves the window out of the way - Tira a janela da frente - - - - Puts a maximized window back to normal - Coloca uma janela maximizada no estado normal - - - - Makes the window full screen - Coloca a janela em ecrã completo - - - - Closes the window - Fecha a janela - - - - Displays the name of the window and contains controls to manipulate it - Mostra o nome da janela e contém controlos para a manipular - - - - Q3ToolBar - - - More... - Mais... - - - - Q3UrlOperator - - - The protocol `%1' is not supported - O protocolo '%1' não é suportado - - - - The protocol `%1' does not support listing directories - O protocolo '%1' não suporta listagem de pastas - - - - The protocol `%1' does not support creating new directories - O protocolo '%1' não suporta criação de novas pastas - - - - The protocol `%1' does not support removing files or directories - O protocolo '%1' não suporta eliminação de ficheiros ou pastas - - - - The protocol `%1' does not support renaming files or directories - O protocolo '%1' não suporta mudança de nome de ficheiros ou pastas - - - - The protocol `%1' does not support getting files - O protocolo '%1' não suporta obtenção de ficheiros - - - - The protocol `%1' does not support putting files - O protocolo '%1' não suporta colocação de ficheiros - - - - The protocol `%1' does not support copying or moving files or directories - O protocolo '%1' não suporta copiar ou mover ficheiros ou pastas - - - - (unknown) - (desconhecido) - - - - Q3Wizard - - - &Cancel - &Cancelar - - - - < &Back - < &Recuar - - - - &Next > - &Avançar > - - - - &Finish - &Terminar - - - - &Help - &Ajuda - - - - QAbstractSocket - - - Host not found - Máquina desconhecida - - - - Connection refused - Ligação recusada - - - - Socket operation timed out - Operação de 'socket' expirada - - - - Socket is not connected - 'Socket' desligado - - - - QAbstractSpinBox - - - &Step up - &Passo acima - - - - Step &down - Passo &abaixo - - - - QApplication - - - Activate - Activar - - - - Executable '%1' requires Qt %2, found Qt %3. - O executável '%1' requere Qt %2, Qt %3 encontrado. - - - - Incompatible Qt Library Error - Erro de Incompatibilidade da Biblioteca Qt - - - - QT_LAYOUT_DIRECTION - Translate this string to the string 'LTR' in left-to-right languages or to 'RTL' in right-to-left languages (such as Hebrew and Arabic) to get proper widget layout. - LTR - - - - Activates the program's main window - Activa a janela principal do programa - - - - QAxSelect - - - Select ActiveX Control - Seleccionar Controlo ActiveX - - - - OK - OK - - - - &Cancel - &Cancelar - - - - COM &Object: - &Objecto COM: - - - - QCheckBox - - - Uncheck - Desactivar - - - - Check - Activar - - - - Toggle - Comutar - - - - QColorDialog - - - Hu&e: - C&or: - - - - &Sat: - &Saturação: - - - - &Val: - &Valor: - - - - &Red: - &Vermelho: - - - - &Green: - V&erde: - - - - Bl&ue: - &Azul: - - - - A&lpha channel: - Canal &transparência: - - - - &Basic colors - Cores &básicas - - - - &Custom colors - Cores c&ustomizadas - - - - &Define Custom Colors >> - &Definir Cores Customizadas >> - - - - OK - OK - - - - Cancel - Cancelar - - - - &Add to Custom Colors - &Adicionar às Cores Customizadas - - - - Select color - Seleccionar cor - - - - QComboBox - - - Open - Abrir - - - - QDB2Driver - - - Unable to connect - Ligação não possível - - - - Unable to commit transaction - Finalização de transacção não possível - - - - Unable to rollback transaction - Anulação de transacção não possível - - - - Unable to set autocommit - Finalização automática não possível - - - - QDB2Result - - - Unable to execute statement - Execução não possível - - - - Unable to prepare statement - Preparação não possível - - - - Unable to bind variable - Ligação de variável não possível - - - - Unable to fetch record %1 - Obtenção do registo %1 não possível - - - - Unable to fetch next - Obtenção do seguinte não possível - - - - Unable to fetch first - Obtenção do primeiro não possível - - - - QDateTimeEdit - - - AM - AM - - - - am - am - - - - PM - PM - - - - pm - pm - - - - QDialog - - - What's This? - O Que é Isto? - - - - QDialogButtonBox - - - OK - OK - - - - Save - Gravar - - - - Open - Abrir - - - - Cancel - Cancelar - - - - Close - Fechar - - - - Apply - Aplicar - - - - Reset - Restaurar - - - - Help - Ajuda - - - - Don't Save - Não Gravar - - - - Discard - Descartar - - - - &Yes - &Sim - - - - Yes to &All - Sim para &Todos - - - - &No - &Não - - - - N&o to All - N&ão para Todos - - - - Save All - Gravar Todos - - - - Abort - Abortar - - - - Retry - Tentar Novamente - - - - Ignore - Ignorar - - - - Restore Defaults - Restaurar Predefinições - - - - Close without Saving - Fechar sem Gravar - - - - QDirModel - - - Name - Nome - - - - Size - Tamanho - - - - Kind - Match OS X Finder - Tipo - - - - Type - All other platforms - Tipo - - - - Date Modified - Data de Modificação - - - - QErrorMessage - - - Debug Message: - Mensagem Depuração: - - - - Warning: - Aviso: - - - - Fatal Error: - Erro Fatal: - - - - &Show this message again - &Mostrar esta mensagem novamente - - - - &OK - &OK - - - - QFileDialog - - - All Files (*) - Todos os Ficheiros (*) - - - - Directories - Pastas - - - - &Open - &Abrir - - - - &Save - &Gravar - - - - Open - Abrir - - - - Save - Gravar - - - - %1 already exists. -Do you want to replace it? - %1 já existe. -Deseja substituir? - - - - %1 -File not found. -Please verify the correct file name was given. - %1 -Ficheiro não encontrado. -Por favor verifique o nome do ficheiro. - - - - My Computer - O Meu Computador - - - - Sort - Ordenar - - - - &Rename - &Mudar o Nome - - - - &Delete - &Apagar - - - - &Reload - &Recarregar - - - - Sort by &Name - Ordenar pelo &Nome - - - - Sort by &Size - Ordenar pelo &Tamanho - - - - Sort by &Date - Ordenar pela &Data - - - - &Unsorted - &Não Ordenado - - - - Show &hidden files - Mostrar ficheiros &escondidos - - - - Back - Recuar - - - - Parent Directory - Pasta Mãe - - - - Create New Folder - Nova Pasta - - - - List View - Vista Abreviada - - - - Detail View - Vista Detalhada - - - - Look in: - Ver em: - - - - File name: - Nome do FIcheiro: - - - - Files of type: - FIcheiros do tipo: - - - - Cancel - Cancelar - - - - Directory: - Pasta: - - - - -File not found. -Please verify the correct file name was given - -Ficheiro não encontrado. -Por favor verifique o nome do ficheiro - - - - %1 -Directory not found. -Please verify the correct directory name was given. - %1 -Pasta não encontrada. -Por favor verifique o nome da pasta. - - - - '%1' is write protected. -Do you want to delete it anyway? - '%1' está protegido contra escrita. -Deseja apagar de qualquer forma? - - - - Are sure you want to delete '%1'? - Deseja mesmo apagar '%1'? - - - - Could not delete directory. - Não foi possível apagar a pasta. - - - - All Files (*.*) - Todos os Ficheiros (*.*) - - - - Save As - Gravar Como - - - - Open - Abrir - - - - Select a Directory - Seleccione uma Pasta - - - - Drive - Unidade - - - - File - Ficheiro - - - - Unknown - Desconhecido - - - - QFontDialog - - - &Font - &Tipo de Letra - - - - Font st&yle - &Estilo Tipo de Letra - - - - &Size - &Tamanho - - - - Effects - Efeitos - - - - Stri&keout - &Riscar - - - - &Underline - &Sublinhar - - - - Sample - Amostra - - - - Wr&iting System - &Sistema de Escrita - - - - Select Font - Seleccione Tipo de Letra - - - - QFtp - - - Not connected - Desligado - - - - Host %1 not found - Servidor %1 não encontrado - - - - Connection refused to host %1 - Ligação ao servidor %1 recusada - - - - Connected to host %1 - Ligado ao servidor %1 - - - - Connection refused for data connection - Ligação de dados recusada - - - - Unknown error - Erro desconhecido - - - - Connecting to host failed: -%1 - A ligação ao servidor falhou: -%1 - - - - Login failed: -%1 - A autenticação falhou: -%1 - - - - Listing directory failed: -%1 - A listagem da pasta falhou: -%1 - - - - Changing directory failed: -%1 - A mudança de pasta falhou: -%1 - - - - Downloading file failed: -%1 - A descarga do ficheiro falhou: -%1 - - - - Uploading file failed: -%1 - O carregamento do ficheiro falhou: -%1 - - - - Removing file failed: -%1 - A remoção do ficheiro falhou: -%1 - - - - Creating directory failed: -%1 - A criação da pasta falhou: -%1 - - - - Removing directory failed: -%1 - A remoção da pasta falhou: -%1 - - - - Connection closed - Ligação fechada - - - - Host %1 found - Servidor %1 encontrado - - - - Connection to %1 closed - Ligação a %1 fechada - - - - Host found - Servidor encontrado - - - - Connected to host - Ligado ao servidor - - - - QHostInfo - - - Unknown error - Erro desconhecido - - - - QHostInfoAgent - - - Host not found - Servidor Não encontrado - - - - Unknown address type - Tipo de endereço desconhecido - - - - Unknown error - Erro desconhecido - - - - QHttp - - - Unknown error - Erro desconhecido - - - - Request aborted - Pedido abortado - - - - No server set to connect to - Nenhum servidor para ligar - - - - Wrong content length - Tamanho de conteúdo errado - - - - Server closed connection unexpectedly - O servidor fechou a ligação inesperadamente - - - - Connection refused - Ligação recusada - - - - Host %1 not found - Servidor %1 não encontrado - - - - HTTP request failed - O pedido HTTP falhou - - - - Invalid HTTP response header - Cabeçalho de resposta HTTP inválido - - - - Invalid HTTP chunked body - Corpo parcial HTTP inválido - - - - Host %1 found - Servidor %1 encontrado - - - - Connected to host %1 - Ligado ao servidor %1 - - - - Connection to %1 closed - Ligação a %1 fechada - - - - Host found - Servidor encontrado - - - - Connected to host - Ligado ao servidor - - - - Connection closed - Ligação fechada - - - - QIBaseDriver - - - Error opening database - Erro ao abrir a base de dados - - - - Could not start transaction - Não foi possível iniciar a transacção - - - - Unable to commit transaction - Não foi possível finalizar a transacção - - - - Unable to rollback transaction - Não foi possível anular a transacção - - - - QIBaseResult - - - Unable to create BLOB - Não foi possível criar o BLOB - - - - Unable to write BLOB - Não foi possível escrever o BLOB - - - - Unable to open BLOB - Não foi possível abrir o BLOB - - - - Unable to read BLOB - Não foi possível ler o BLOB - - - - Could not find array - Não foi possível encontrar o array - - - - Could not get array data - Não foi possível obter os dados do array - - - - Could not get query info - Não foi possível obter informação da query - - - - Could not start transaction - Não foi possível iniciar a transacção - - - - Unable to commit transaction - Não foi possível finalizar a transacção - - - - Could not allocate statement - Não foi possível alocar a expressão - - - - Could not prepare statement - Não foi possível preparar a expressão - - - - Could not describe input statement - Não foi possível descrever a expressão de entrada - - - - Could not describe statement - Não foi possível descrever a expressão - - - - Unable to close statement - Não foi possível fechar a expressão - - - - Unable to execute query - Não foi possível executar a query - - - - Could not fetch next item - Não foi possível obter o elemento seguinte - - - - Could not get statement info - Não foi possível obter informação da expressão - - - - QIODevice - - - Permission denied - Permissão negada - - - - Too many open files - Demasiados ficheiros abertos - - - - No such file or directory - Ficheiro ou pasta inexistente - - - - No space left on device - Dispositivo sem espaço livre - - - - Unknown error - Erro desconhecido - - - - QInputContext - - - XIM - XIM - - - - XIM input method - Método de entrada XIM - - - - Windows input method - Método de entrada Windows - - - - Mac OS X input method - Método de entrada Max OS X - - - - QLibrary - - - QLibrary::load_sys: Cannot load %1 (%2) - QLibrary::load_sys: Não foi possível carregar %1 (%2) - - - - QLibrary::unload_sys: Cannot unload %1 (%2) - QLibrary::unload_sys: Não foi possível descarregar %1 (%2) - - - - QLibrary::resolve_sys: Symbol "%1" undefined in %2 (%3) - QLibrary::resolve_sys: Símbolo "%1" indefinido em %2 (%3) - - - - Could not mmap '%1': %2 - Não foi possivel mapear '%1': %2 - - - - Plugin verification data mismatch in '%1' - Dados de verificação do plugin incorrectos em '%1' - - - - Could not unmap '%1': %2 - Não foi possível desmapear '%1': %2 - - - - The plugin '%1' uses incompatible Qt library. (%2.%3.%4) [%5] - O plugin '%1' usa uma biblioteca Qt incompatível. (%2.%3.%4) [%5] - - - - The plugin '%1' uses incompatible Qt library. Expected build key "%2", got "%3" - O plugin '%1' usa uma biblioteca Qt incompatível. A chave de compilação esperada "%2", ficou "%3" - - - - Unknown error - Erro desconhecido - - - - QLineEdit - - - &Undo - &Desfazer - - - - &Redo - &Refazer - - - - Cu&t - Cor&tar - - - - &Copy - &Copiar - - - - &Paste - Co&lar - - - - Delete - Apagar - - - - Select All - Seleccionar Tudo - - - - QMYSQLDriver - - - Unable to open database ' - Não foi possível abrir a base de dados ' - - - - Unable to connect - Não foi possível estabelecer a ligação - - - - Unable to begin transaction - Não foi possível iniciar a transacção - - - - Unable to commit transaction - Não foi possível finalizar a transacção - - - - Unable to rollback transaction - Não foi possível anular a transacção - - - - QMYSQLResult - - - Unable to fetch data - Não foi possível obter dados - - - - Unable to execute query - Não foi possível executar a query - - - - Unable to store result - Não foi possível guardar o resultado - - - - Unable to prepare statement - Não foi possível preparar a expressão - - - - Unable to reset statement - Não foi possível restaurar a expressão - - - - Unable to bind value - Não foi possível fazer a ligação do valor - - - - Unable to execute statement - Não foi possível executar a expressão - - - - Unable to bind outvalues - Não foi possível fazer a ligação dos valores externos - - - - Unable to store statement results - Não foi possível guardar os resultados da expressão - - - - QMenu - - - Close - Fechar - - - - Open - Abrir - - - - Execute - Executar - - - - QMenuBar - - - About - Sobre - - - - Config - Configurar - - - - Preference - Preferências - - - - Options - Opções - - - - Setting - Alteração - - - - Setup - Configuração - - - - Quit - Sair - - - - Exit - Sair - - - - About %1 - Sobre %1 - - - - About Qt - Acerca do Qt - - - - Preferences - Preferências - - - - Quit %1 - Sair de %1 - - - - QMessageBox - - - Help - Ajuda - - - - OK - OK - - - - About Qt - Acerca do Qt - - - - <p>This program uses Qt version %1.</p> - <p>Este programa usa Qt versão %1.</p> - - - - <h3>About Qt</h3>%1<p>Qt is a C++ toolkit for cross-platform application development.</p><p>Qt provides single-source portability across MS&nbsp;Windows, Mac&nbsp;OS&nbsp;X, Linux, and all major commercial Unix variants. Qt is also available for embedded devices as Qtopia Core.</p><p>Qt is a Trolltech product. See <a href="http://www.trolltech.com/qt/">www.trolltech.com/qt/</a> for more information.</p> - <h3>Acerca do Qt</h3>%1<p>Qt é um conjunto de ferramentas para desenvolvimento de aplicações multiplataforma.</p>O Qt oferece portabilidade de código fonte único em MS&nbsp;Windows, Mac&nbsp;OS&nbsp;X, Linux e todas as principais variantes comerciais de Unix. O Qt está igualmente disponível para dispositivos embebidos como Qtopia Core.</p><p>O Qt é um produto Trolltech. Veja <a href="http://www.trolltech.com/qt/">www.trolltech.com/qt/</a> para mais informação.</p> - - - - <p>This program uses Qt Open Source Edition version %1.</p><p>Qt Open Source Edition is intended for the development of Open Source applications. You need a commercial Qt license for development of proprietary (closed source) applications.</p><p>Please see <a href="http://www.trolltech.com/company/model.html">www.trolltech.com/company/model.html</a> for an overview of Qt licensing.</p> - <p>Este programa utiliza Qt Open Source Edition versão %1.</p><p>Qt Open Source Edition é indicado para o desenvolvimento de aplicações de código fonte aberto. Necessita de uma licença comercial Qt para o desenvolvimento de aplicações proprietárias (código fonte fechado).</p><p>Por favor veja <a href="http://www.trolltech.com/company/model.html">www.trolltech.com/company/model.html</a>para uma visão geral do licenciamento Qt.</p> - - - - Show Details... - Mostrar Detalhes... - - - - Hide Details... - Não Mostrar Detalhes... - - - - <p>This program uses Qt Open Source Edition version %1.</p><p>Qt Open Source Edition is intended for the development of Open Source applications. You need a commercial Qt license for development of proprietary (closed source) applications.</p><p>Please see <a href="http://www.trolltech.com/company/model/">www.trolltech.com/company/model/</a> for an overview of Qt licensing.</p> - <p>Este programa usa Qt Open Source Edition versão %1.</p><p>Qt Open Source Edition é indicado para o desenvolvimento de aplicações/programas open source. Se pretender desenvolver aplicações sem disponibilizar o codigo fonte, então precisará de obter uma licença comercial.</p><p>Por favor consulte <a href="http://www.trolltech.com/company/model/">www.trolltech.com/company/model/</a>para obter mais informação acerca de licenças Qt.</p> - - - - QMultiInputContext - - - Select IM - Seleccione Método de Entrada - - - - QMultiInputContextPlugin - - - Multiple input method switcher - Seleccionador de método de entrada - - - - Multiple input method switcher that uses the context menu of the text widgets - Seleccionador de método de entrada que utiliza o menu de contexto dos elementos de texto - - - - QNativeSocketEngine - - - The remote host closed the connection - A máquina remota fechou a ligação - - - - Network operation timed out - Operação de rede expirada - - - - Out of resources - Sem recursos - - - - Unsupported socket operation - Operação de 'socket' não suportada - - - - Protocol type not supported - Tipo de protocolo não suportado - - - - Invalid socket descriptor - Descritor de 'socket' inválido - - - - Network unreachable - Rede inalcançável - - - - Permission denied - Permissão negada - - - - Connection timed out - Ligação expirada - - - - Connection refused - Ligação recusada - - - - The bound address is already in use - O endereço de ligação já está em uso - - - - The address is not available - O endereço não está disponível - - - - The address is protected - O endereço está protegido - - - - Unable to send a message - Não foi possível enviar uma mensagem - - - - Unable to receive a message - Não foi possível receber uma mensagem - - - - Unable to write - Não foi possível escrever - - - - Network error - Erro de rede - - - - Another socket is already listening on the same port - Outro 'socket' já está à escuta no mesmo porto - - - - Unable to initialize non-blocking socket - Não foi possível inicializar 'socket' não bloqueante - - - - Unable to initialize broadcast socket - Não foi possível inicializar 'socket' de transmissão - - - - Attempt to use IPv6 socket on a platform with no IPv6 support - Tentativa de utilização de 'socket' IPv6 numa plataforma sem suporte IPv6 - - - - Host unreachable - Máquina inalcançável - - - - Datagram was too large to send - Datagrama demasiado grande para enviar - - - - Operation on non-socket - Operação em não 'socket' - - - - Unknown error - Erro desconhecido - - - - QOCIDriver - - - QOCIDriver - Unable to initialize - QOCIDriver - - - - Unable to logon - Não foi possível autenticar - - - - Unable to initialize - QOCIDriver - Não foi possível inicializar - - - - QOCIResult - - - Unable to bind column for batch execute - Não foi possível fazer a licação da coluna para execução 'batch' - - - - Unable to execute batch statement - Não foi possível executar a expressão de 'batch' - - - - Unable to goto next - Não foi possível passar ao seguinte - - - - Unable to alloc statement - Não foi possível alocar a expressão - - - - Unable to prepare statement - Não foi possível preparar a expressão - - - - Unable to bind value - Não foi possível fazer o ligamento do valor - - - - Unable to execute select statement - Não foi possível executar a expressão select - - - - Unable to execute statement - Não foi possível executar a expressão - - - - QODBCDriver - - - Unable to connect - Não foi possível ligar - - - - Unable to connect - Driver doesn't support all needed functionality - Não foi possível ligar - O 'driver' não suporta todas as funcionalidades necessárias - - - - Unable to disable autocommit - Não foi possível desactivar finalização automática - - - - Unable to commit transaction - Não foi possível finalizar a transacção - - - - Unable to rollback transaction - Não foi possível anular a transacção - - - - Unable to enable autocommit - Não foi possível activar finalização automática - - - - QODBCResult - - - QODBCResult::reset: Unable to set 'SQL_CURSOR_STATIC' as statement attribute. Please check your ODBC driver configuration - QODBCResult::reset: Não foi possível definir 'SQL_CURSOR_STATIC' como atributo da expressão. Por favor verifique a configuração do seu 'driver' ODBC - - - - Unable to execute statement - Não foi possível executar a expressão - - - - Unable to fetch next - Não foi possível obter o seguinte - - - - Unable to prepare statement - Não foi possível preparar a expressão - - - - Unable to bind variable - Não foi possível fazer o ligamento da variável - - - - QObject - - - False - Falso - - - - True - Verdadeiro - - - - Home - Início - - - - QPSQLDriver - - - Unable to connect - Não foi possível ligar - - - - Could not begin transaction - Não foi possível iniciar a transacção - - - - Could not commit transaction - Não foi possível finalizar a transacção - - - - Could not rollback transaction - Não foi possível anular a transacção - - - - QPSQLResult - - - Unable to create query - Não foi possível criar a 'query' - - - - QPluginLoader - - - Unknown error - Erro desconhecido - - - - QPrintDialog - - - locally connected - ligado localmente - - - - Aliases: %1 - Nomes Alternativos: %1 - - - - unknown - desconhecido - - - - Portrait - Retrato - - - - Landscape - Paisagem - - - - A0 (841 x 1189 mm) - A0 (841 x 1189 mm) - - - - A1 (594 x 841 mm) - A1 (594 x 841 mm) - - - - A2 (420 x 594 mm) - A2 (420 x 594 mm) - - - - A3 (297 x 420 mm) - A3 (297 x 420 mm) - - - - A4 (210 x 297 mm, 8.26 x 11.7 inches) - A4 (210 x 297 mm, 8.26 x 11.7 polegadas) - - - - A5 (148 x 210 mm) - A5 (148 x 210 mm) - - - - A6 (105 x 148 mm) - A6 (105 x 148 mm) - - - - A7 (74 x 105 mm) - A7 (74 x 105 mm) - - - - A8 (52 x 74 mm) - A8 (52 x 74 mm) - - - - A9 (37 x 52 mm) - A9 (37 x 52 mm) - - - - B0 (1000 x 1414 mm) - B0 (1000 x 1414 mm) - - - - B1 (707 x 1000 mm) - B1 (707 x 1000 mm) - - - - B2 (500 x 707 mm) - B2 (500 x 707 mm) - - - - B3 (353 x 500 mm) - B3 (353 x 500 mm) - - - - B4 (250 x 353 mm) - B4 (250 x 353 mm) - - - - B5 (176 x 250 mm, 6.93 x 9.84 inches) - B5 (176 x 250 mm, 6.93 x 9.84 polegadas) - - - - B6 (125 x 176 mm) - B6 (125 x 176 mm) - - - - B7 (88 x 125 mm) - B7 (88 x 125 mm) - - - - B8 (62 x 88 mm) - B8 (62 x 88 mm) - - - - B9 (44 x 62 mm) - B9 (44 x 62 mm) - - - - B10 (31 x 44 mm) - B10 (31 x 44 mm) - - - - C5E (163 x 229 mm) - C5E (163 x 229 mm) - - - - DLE (110 x 220 mm) - DLE (110 x 220 mm) - - - - Executive (7.5 x 10 inches, 191 x 254 mm) - Executivo (7.5 x 10 polegadas, 191 x 254 mm) - - - - Folio (210 x 330 mm) - Folio (210 x 330 mm) - - - - Ledger (432 x 279 mm) - Ledger (432 x 279 mm) - - - - Legal (8.5 x 14 inches, 216 x 356 mm) - Legal (8.5 x 14 polegadas, 216 x 356 mm) - - - - Letter (8.5 x 11 inches, 216 x 279 mm) - Carta (8.5 x 11 polegadas, 216 x 279 mm) - - - - Tabloid (279 x 432 mm) - Tablóide (279 x 432 mm) - - - - US Common #10 Envelope (105 x 241 mm) - Envelope #10 Comum EUA (105 x 241 mm) - - - - OK - OK - - - - Cancel - Cancelar - - - - Page size: - Tamanho página: - - - - Orientation: - Orientação: - - - - Paper source: - Fonte papel: - - - - Print - Imprimir - - - - File - Ficheiro - - - - Printer - Impressora - - - - Print To File ... - Imprimir Para Ficheiro ... - - - - Print dialog - Diálogo de impressão - - - - Paper format - Formato do papel - - - - Size: - Tamanho: - - - - Properties - Propriedades - - - - Printer info: - Informação da impressora: - - - - Browse - Navegar - - - - Print to file - Imprimir para um ficheiro - - - - Print range - Selecção de páginas - - - - Print all - Imprimir todas - - - - Pages from - Páginas de - - - - to - a - - - - Selection - Selecção - - - - Copies - Cópias - - - - Number of copies: - Número de cópias: - - - - Collate - Juntar - - - - Print last page first - Imprimir a última página primeiro - - - - Other - Outro - - - - Print in color if available - Imprimir a cores se disponível - - - - Double side printing - Imprimir nos dois lados do papel - - - - File %1 is not writable. -Please choose a different file name. - Não é possível escrever no ficheiro %1. -Por favor escolha um nome diferente. - - - - %1 already exists. -Do you want to overwrite it? - %1 já existe. -Deseja substituir? - - - - File exists - O ficheiro existe - - - - <qt>Do you want to overwrite it?</qt> - <qt>Deseja gravar por cima?</qt> - - - - Print selection - Selecção de Impressão - - - - QPrintPropertiesDialog - - - PPD Properties - Propriedades PPD - - - - Save - Gravar - - - - OK - OK - - - - QProgressBar - - - %1% - %1% - - - - QProgressDialog - - - Cancel - Cancelar - - - - QPushButton - - - Open - Abrir - - - - QRadioButton - - - Check - Activar - - - - QRegExp - - - no error occurred - sem erros - - - - disabled feature used - funcionalidade desactivada está a ser utilizada - - - - bad char class syntax - má sintaxe de classe de caracteres - - - - bad lookahead syntax - má sintaxe de antecipação - - - - bad repetition syntax - má sintaxe de repetição - - - - invalid octal value - valor octal inválido - - - - missing left delim - delimitador esquerdo em falta - - - - unexpected end - fim inesperado - - - - met internal limit - limite interno alcançado - - - - QSQLite2Driver - - - Error to open database - Erro ao abrir base de dados - - - - Unable to begin transaction - Não foi possível iniciar a transacção - - - - Unable to commit transaction - Não foi possível finalizar a transacção - - - - Unable to rollback Transaction - Não foi possível anular a transacção - - - - QSQLite2Result - - - Unable to fetch results - Não foi possível obter os resultados - - - - Unable to execute statement - Não foi possível executar a expressão - - - - QSQLiteDriver - - - Error opening database - Erro ao abrir a base de dados - - - - Error closing database - Erro ao fechar a base de dados - - - - Unable to begin transaction - Não foi possível iniciar a transacção - - - - Unable to commit transaction - Não foi possível finalizar a transacção - - - - Unable to roll back transaction - Não foi possível anular a transacção - - - - QSQLiteResult - - - Unable to fetch row - Não foi possível obter a linha - - - - Unable to execute statement - Não foi possível executar a expressão - - - - Unable to reset statement - Não foi possível restaurar a expressão - - - - Unable to bind parameters - Não foi possível fazer a ligação dos parametros - - - - Parameter count mismatch - Incorrespondência de contagem de parâmetros - - - - QScrollBar - - - Scroll here - Deslizar aqui - - - - Left edge - Borda esquerda - - - - Top - Topo - - - - Right edge - Borda direita - - - - Bottom - Fundo - - - - Page left - Página para esquerda - - - - Page up - Página para cima - - - - Page right - Página para direita - - - - Page down - Página para baixo - - - - Scroll left - Deslizar para esquerda - - - - Scroll up - Deslizar para cima - - - - Scroll right - Deslizar para a direita - - - - Scroll down - Deslizar para baixo - - - - Line up - Linha acima - - - - Position - Posição - - - - Line down - Linha abaixo - - - - QShortcut - - - Space - Space - - - - Esc - Esc - - - - Tab - Tab - - - - Backtab - Backtab - - - - Backspace - Backspace - - - - Return - Return - - - - Enter - Enter - - - - Ins - Insert - - - - Del - Delete - - - - Pause - Pause - - - - Print - Print - - - - SysReq - SysReq - - - - Home - Home - - - - End - End - - - - Left - Esquerda - - - - Up - Cima - - - - Right - Direita - - - - Down - Baixo - - - - PgUp - PgUp - - - - PgDown - PgDown - - - - CapsLock - CapsLock - - - - NumLock - Num Lock - - - - ScrollLock - ScrollLock - - - - Menu - Menu - - - - Help - Ajuda - - - - Back - Anterior - - - - Forward - Seguinte - - - - Stop - Parar - - - - Refresh - Refrescar - - - - Volume Down - Volume Cima - - - - Volume Mute - Volume Mute - - - - Volume Up - Volume Baixo - - - - Bass Boost - Bass Boost - - - - Bass Up - Bass Cima - - - - Bass Down - Bass Baixo - - - - Treble Up - Treble Cima - - - - Treble Down - Treble Baixo - - - - Media Play - Tocar Média - - - - Media Stop - Parar Média - - - - Media Previous - Média Anterior - - - - Media Next - Média Seguinte - - - - Media Record - Gravação Média - - - - Favorites - Favoritos - - - - Search - Procurar - - - - Standby - Hibernação - - - - Open URL - Abrir Endereço - - - - Launch Mail - Correio Electrónico - - - - Launch Media - Média - - - - Launch (0) - Executar (0) - - - - Launch (1) - Executar (1) - - - - Launch (2) - Executar (2) - - - - Launch (3) - Executar (3) - - - - Launch (4) - Executar (4) - - - - Launch (5) - Executar (5) - - - - Launch (6) - Executar (6) - - - - Launch (7) - Executar (7) - - - - Launch (8) - Executar (8) - - - - Launch (9) - Executar (9) - - - - Launch (A) - Executar (A) - - - - Launch (B) - Executar (B) - - - - Launch (C) - Executar (C) - - - - Launch (D) - Executar (D) - - - - Launch (E) - Executar (E) - - - - Launch (F) - Executar (F) - - - - Print Screen - Print Screen - - - - Page Up - Page Up - - - - Page Down - Page Down - - - - Caps Lock - Caps Lock - - - - Num Lock - Num Lock - - - - Number Lock - Number Lock - - - - Scroll Lock - Scroll Lock - - - - Insert - Insert - - - - Delete - Delete - - - - Escape - Escape - - - - System Request - System Request - - - - Select - Select - - - - Yes - Sim - - - - No - Não - - - - Context1 - Contexto1 - - - - Context2 - Contexto2 - - - - Context3 - Contexto3 - - - - Context4 - Contexto4 - - - - Call - Chamar - - - - Hangup - Desligar - - - - Flip - Inverter - - - - Ctrl - Ctrl - - - - Shift - Shift - - - - Alt - Alt - - - - Meta - Meta - - - - + - + - - - - F%1 - F%1 - - - - Home Page - Página Principal - - - - QSlider - - - Page left - Página para esquerda - - - - Page up - Página para cima - - - - Position - Posição - - - - Page right - Página para direita - - - - Page down - Página para baixo - - - - QSocks5SocketEngine - - - Socks5 timeout error connecting to socks server - Socks5 expirado ao ligar ao servidor socks - - - - QSpinBox - - - More - Mais - - - - Less - Menos - - - - QSql - - - Delete - Apagar - - - - Delete this record? - Apagar este registo? - - - - Yes - Sim - - - - No - Não - - - - Insert - Inserir - - - - Update - Actualizar - - - - Save edits? - Gravar as alterações? - - - - Cancel - Cancelar - - - - Confirm - Confirmar - - - - Cancel your edits? - Cancelar as alterações? - - - - QTDSDriver - - - Unable to open connection - Não foi possível estabelecer a ligação - - - - Unable to use database - Não foi possível utilizar a base de dados - - - - QTabBar - - - Scroll Left - Deslizar para Esquerda - - - - Scroll Right - Deslizar para Direita - - - - QTcpServer - - - Socket operation unsupported - Operação de 'socket' não suportada - - - - QTextControl - - - &Undo - &Desfazer - - - - &Redo - &Refazer - - - - Cu&t - Cor&tar - - - - &Copy - &Copiar - - - - Copy &Link Location - Copiar &Localização da Ligação - - - - &Paste - Co&lar - - - - Delete - Apagar - - - - Select All - Seleccionar Tudo - - - - QToolButton - - - Press - Pressionar - - - - Open - Abrir - - - - QUdpSocket - - - This platform does not support IPv6 - Esta plataforma não suporta IPv6 - - - - QUndoGroup - - - Undo - Desfazer - - - - Redo - Refazer - - - - QUndoModel - - - <empty> - <vazio> - - - - QUndoStack - - - Undo - Desfazer - - - - Redo - Refazer - - - - QUnicodeControlCharacterMenu - - - LRM Left-to-right mark - LRM Marca esquerda-para-direita - - - - RLM Right-to-left mark - RLM Marca direita-para-esquerda - - - - ZWJ Zero width joiner - ZWJ Ligador de comprimento zero - - - - ZWNJ Zero width non-joiner - ZWNJ Não-ligador de comprimento zero - - - - ZWSP Zero width space - ZWSP Espaço de comprimento zero - - - - LRE Start of left-to-right embedding - LRE Início de encaixe esquerda-para-direita - - - - RLE Start of right-to-left embedding - RLE Início de encaixe direita-para-esquerda - - - - LRO Start of left-to-right override - LRO Início de sobreposição esquerda-para-direita - - - - RLO Start of right-to-left override - RLO Início de sobreposição direita-para-esquerda - - - - PDF Pop directional formatting - PDF Formatação pop direccional - - - - Insert Unicode control character - Inserir carácter de controlo Unicode - - - - QWhatsThisAction - - - What's This? - O Que é Isto? - - - - QWidget - - - * - * - - - - QWorkspace - - - &Restore - &Restaurar - - - - &Move - &Mover - - - - &Size - &Tamanho - - - - Mi&nimize - Mi&nimizar - - - - Ma&ximize - Ma&ximizar - - - - &Close - &Fechar - - - - Stay on &Top - Permanecer no &Topo - - - - Sh&ade - Sombr&a - - - - %1 - [%2] - %1 - [%2] - - - - Minimize - Minimizar - - - - Restore Down - Restaurar Baixo - - - - Close - Fechar - - - - &Unshade - &Sair Sombra - - - - QXml - - - no error occurred - não ocorreu nenhum erro - - - - error triggered by consumer - erro disparado pelo consumidor - - - - unexpected end of file - fim de ficheiro inesperado - - - - more than one document type definition - mais de uma definição de tipo de documento - - - - error occurred while parsing element - erro ao analisar elemento - - - - tag mismatch - má combinação de etiqueta - - - - error occurred while parsing content - erro ao analisar o conteúdo - - - - unexpected character - carácter inesperado - - - - invalid name for processing instruction - nome inválido de instrução de processamento - - - - version expected while reading the XML declaration - versão esperada ao ler a declaração XML - - - - wrong value for standalone declaration - valor errado para declaração única - - - - encoding declaration or standalone declaration expected while reading the XML declaration - declaração de codificação ou declaração única esperada ao ler a declaração XML - - - - standalone declaration expected while reading the XML declaration - declaração única esperada ao ler a declaração XML - - - - error occurred while parsing document type definition - erro ao analisar a definição de tipo de documento - - - - letter is expected - uma letra é esperada - - - - error occurred while parsing comment - erro ao analisar comentário - - - - error occurred while parsing reference - erro ao analisar referência - - - - internal general entity reference not allowed in DTD - referência de entidade geral interna não permitida na DTD - - - - external parsed general entity reference not allowed in attribute value - referência de entidade geral analisada externa não permitida no valor do atributo - - - - external parsed general entity reference not allowed in DTD - referência de entidade geral analisada externa não permitida na DTD - - - - unparsed entity reference in wrong context - referência de entidade não analisada em contexto errado - - - - recursive entities - entidades recursivas - - - - error in the text declaration of an external entity - erro na declaração de uma entidade externa - - - diff -Nru treeline-2.0.2/i18n/translations/qt_ru.ts treeline-3.0.1/i18n/translations/qt_ru.ts --- treeline-2.0.2/i18n/translations/qt_ru.ts 2018-04-08 20:12:57.000000000 +0000 +++ treeline-3.0.1/i18n/translations/qt_ru.ts 1970-01-01 00:00:00.000000000 +0000 @@ -1,5933 +0,0 @@ - - - - PPDOptionsModel - - - Name - Имя - - - - Value - - - - - Q3Accel - - - %1, %2 not defined - - - - - Ambiguous %1 not handled - - - - - Q3DataTable - - - True - True - - - - False - False - - - - Insert - Вставить - - - - Update - Обновить - - - - Delete - Удалить - - - - Q3FileDialog - - - Copy or Move a File - Копировать или переместить файл - - - - Read: %1 - Открытие: %1 - - - - Write: %1 - Запись: %1 - - - - Cancel - Отмена - - - - All Files (*) - Все файлы (*) - - - - Name - Имя - - - - Size - Размер - - - - Type - Тип - - - - Date - Дата - - - - Attributes - Атрибуты - - - - &OK - &OK - - - - Look &in: - &Смотреть в: - - - - File &name: - &Имя файла: - - - - File &type: - &Тип файла: - - - - Back - Назад - - - - One directory up - Вверх на один уровень - - - - Create New Folder - Создать новый каталог - - - - List View - Список - - - - Detail View - Детальный вид - - - - Preview File Info - Предпросмотр информации о файле - - - - Preview File Contents - Предпросмотр содержимого файла - - - - Read-write - Чтение-запись - - - - Read-only - Только чтение - - - - Write-only - Только запись - - - - Inaccessible - Нет доступа - - - - Symlink to File - Ссылка на файл - - - - Symlink to Directory - Ссылка на каталог - - - - Symlink to Special - Ссылка на спецфайл - - - - File - Файл - - - - Dir - Каталог - - - - Special - Спецфайл - - - - Open - Открыть - - - - Save As - Сохранить как - - - - &Open - &Открыть - - - - &Save - &Сохранить - - - - &Rename - &Переименовать - - - - &Delete - &Удалить - - - - R&eload - О&бновить - - - - Sort by &Name - По &имени - - - - Sort by &Size - По &размеру - - - - Sort by &Date - По &дате - - - - &Unsorted - &Не упорядочивать - - - - Sort - Упорядочить - - - - Show &hidden files - Показать &скрытые файлы - - - - the file - файл - - - - the directory - каталог - - - - the symlink - ссылку - - - - Delete %1 - Удалить %1 - - - - <qt>Are you sure you wish to delete %1 "%2"?</qt> - <qt>Вы действительно хотите удалить %1 "%2"?</qt> - - - - &Yes - &Да - - - - &No - &Нет - - - - New Folder 1 - Новый каталог 1 - - - - New Folder - Новый каталог - - - - New Folder %1 - Новый каталог %1 - - - - Find Directory - Найти каталог - - - - Directories - Каталоги - - - - Directory: - Каталог: - - - - Error - Ошибка - - - - %1 -File not found. -Check path and filename. - %1 -Файл не найден. -Проверьте правильность пути и имени файла. - - - - All Files (*.*) - Все файлы (*.*) - - - - Open - Открыть - - - - Select a Directory - Выбрать каталог - - - - Q3Ftp - - - Not connected - Нет соединения - - - - Host %1 not found - Узел %1 не обнаружен - - - - Connection refused to host %1 - Отказано в соединении с узлом %1 - - - - Connected to host %1 - Установлено соединение с узлом %1 - - - - Connection refused for data connection - Отказано в соединении передачи данных - - - - Unknown error - Неизвестная ошибка - - - - Connecting to host failed: -%1 - Ошибка соединения с узлом: -%1 - - - - Login failed: -%1 - Ошибка входа в систему: -%1 - - - - Listing directory failed: -%1 - Ошибка просмотра каталога: -%1 - - - - Changing directory failed: -%1 - Ошибка смены каталога: -%1 - - - - Downloading file failed: -%1 - Ошибка загрузки файла: -%1 - - - - Uploading file failed: -%1 - Ошибка отправки файла: -%1 - - - - Removing file failed: -%1 - Ошибка удаления файла: -%1 - - - - Creating directory failed: -%1 - Ошибка создания каталога: -%1 - - - - Removing directory failed: -%1 - Ошибка удаления каталога: -%1 - - - - Connection closed - Соединение разорвано - - - - Host %1 found - Обнаружен узел %1 - - - - Connection to %1 closed - Соединение с узлом %1 разорвано - - - - Host found - Узел обнаружен - - - - Connected to host - Соединение с узлом установлено - - - - Q3Http - - - Unknown error - Неизвестная ошибка - - - - Request aborted - Запрос отменен - - - - No server set to connect to - Не выбран сервер для подключения - - - - Wrong content length - Неверная длина данных - - - - Server closed connection unexpectedly - Неожиданный разрыв соединения сервером - - - - Connection refused - Отказано в соединении - - - - Host %1 not found - Узел %1 не обнаружен - - - - HTTP request failed - Ошибка HTTP-запроса - - - - Invalid HTTP response header - Получен некорректный HTTP-заголовок - - - - Invalid HTTP chunked body - Некорректный HTTP-ответ - - - - Host %1 found - Обнаружен узел %1 - - - - Connected to host %1 - Установлено соединение с узлом %1 - - - - Connection to %1 closed - Соединение с узлом %1 разорвано - - - - Host found - Узел обнаружен - - - - Connected to host - Соединение с узлом установлено - - - - Connection closed - Соединение разорвано - - - - Q3LocalFs - - - Could not read directory -%1 - Невозможно просмотреть каталог -%1 - - - - Could not create directory -%1 - Невозможно создать каталог -%1 - - - - Could not remove file or directory -%1 - Невозможно удалить файл или каталог -%1 - - - - Could not rename -%1 -to -%2 - Невозможно переименовать -%1 -в -%2 - - - - Could not open -%1 - Невозможно открыть -%1 - - - - Could not write -%1 - Невозможно записать -%1 - - - - Q3MainWindow - - - Line up - Выровнять - - - - Customize... - Настроить... - - - - Q3NetworkProtocol - - - Operation stopped by the user - Операция прервана пользователем - - - - Q3ProgressDialog - - - Cancel - Отмена - - - - Q3TabDialog - - - OK - OK - - - - Apply - Применить - - - - Help - Справка - - - - Defaults - По умолчанию - - - - Cancel - Отмена - - - - Q3TextEdit - - - &Undo - &Отменить - - - - &Redo - &Повторить - - - - Cu&t - &Вырезать - - - - &Copy - &Копировать - - - - &Paste - &Вставить - - - - Clear - Очистить - - - - Select All - Выделить все - - - - Q3TitleBar - - - System - - - - - Restore up - - - - - Minimize - Свернуть - - - - Restore down - - - - - Maximize - Развернуть - - - - Close - Закрыть - - - - Contains commands to manipulate the window - - - - - Puts a minimized back to normal - - - - - Moves the window out of the way - - - - - Puts a maximized window back to normal - - - - - Makes the window full screen - - - - - Closes the window - - - - - Displays the name of the window and contains controls to manipulate it - - - - - Q3ToolBar - - - More... - Больше... - - - - Q3UrlOperator - - - The protocol `%1' is not supported - Протокол `%1' не поддерживается - - - - The protocol `%1' does not support listing directories - Протокол `%1' не поддерживает просмотр каталогов - - - - The protocol `%1' does not support creating new directories - Протокол `%1' не поддерживает создание новых каталогов - - - - The protocol `%1' does not support removing files or directories - Протокол `%1' не поддерживает удаление файлов или каталогов - - - - The protocol `%1' does not support renaming files or directories - Протокол `%1' не поддерживает переименование файлов или каталогов - - - - The protocol `%1' does not support getting files - Протокол `%1' не поддерживает доставку файлов - - - - The protocol `%1' does not support putting files - Протокол `%1' не поддерживает отправку файлов - - - - The protocol `%1' does not support copying or moving files or directories - Протокол `%1' не поддерживает копирование или перемещение файлов и каталогов - - - - (unknown) - (неизвестно) - - - - Q3Wizard - - - &Cancel - &Отмена - - - - < &Back - < &Назад - - - - &Next > - &Вперед > - - - - &Finish - &Финиш - - - - &Help - &Справка - - - - QAbstractSocket - - - Host not found - - - - - Connection refused - Отказано в соединении - - - - Socket operation timed out - - - - - Socket is not connected - - - - - QAbstractSpinBox - - - &Step up - - - - - Step &down - - - - - QAccel - - - Space - Space - - - - Esc - Esc - - - - Tab - Tab - - - - Backtab - Backtab - - - - Backspace - Backspace - - - - Return - Return - - - - Enter - Enter - - - - Ins - Ins - - - - Del - Del - - - - Pause - Pause - - - - Print - Print - - - - SysReq - SysReq - - - - Home - Home - - - - End - End - - - - Left - Left - - - - Up - Up - - - - Right - Right - - - - Down - Down - - - - PgUp - PgUp - - - - PgDown - PgDown - - - - CapsLock - CapsLock - - - - NumLock - NumLock - - - - ScrollLock - ScrollLock - - - - Ctrl - Ctrl - - - - Alt - Alt - - - - Shift - Shift - - - - + - + - - - - F%1 - F%1 - - - - Menu - Меню - - - - Help - Справка - - - - Back - Назад - - - - Forward - Вперед - - - - Stop - Стоп - - - - Refresh - Обновить - - - - Volume Down - Тише - - - - Volume Mute - Выключить звук - - - - Volume Up - Громче - - - - Bass Boost - Bass Boost - - - - Bass Up - Bass Up - - - - Bass Down - Bass Down - - - - Treble Up - Treble Up - - - - Treble Down - Treble Down - - - - Media Play - Воспроизведение - - - - Media Stop - Остановить воспроизведение - - - - Media Previous - Воспроизвести предыдущее - - - - Media Next - Воспроизвести следующее - - - - Media Record - Запись - - - - Favorites - Избранное - - - - Search - Поиск - - - - Standby - Дежурный режим - - - - Open URL - Открыть URL - - - - Launch Mail - Почта - - - - Launch Media - Проигрыватель - - - - Launch (0) - Запустить (0) - - - - Launch (1) - Запустить (1) - - - - Launch (2) - Запустить (2) - - - - Launch (3) - Запустить (3) - - - - Launch (4) - Запустить (4) - - - - Launch (5) - Запустить (5) - - - - Launch (6) - Запустить (6) - - - - Launch (7) - Запустить (7) - - - - Launch (8) - Запустить (8) - - - - Launch (9) - Запустить (9) - - - - Launch (A) - Запустить (A) - - - - Launch (B) - Запустить (B) - - - - Launch (C) - Запустить (C) - - - - Launch (D) - Запустить (D) - - - - Launch (E) - Запустить (E) - - - - Launch (F) - Запустить (F) - - - - Meta - Meta - - - - QApplication - - - QT_LAYOUT_DIRECTION - Translate this string to the string 'LTR' in left-to-right languages or to 'RTL' in right-to-left languages (such as Hebrew and Arabic) to get proper widget layout. - LTR - - - - Executable '%1' requires Qt %2, found Qt %3. - Программный модуль '%1' требует Qt %2, найдена версия %3. - - - - Incompatible Qt Library Error - Ошибка совместимости библиотеки Qt - - - - Activate - - - - - Activates the program's main window - - - - - QAquaStyle - - - OK - OK - - - - Cancel - Отмена - - - - QAxSelect - - - Select ActiveX Control - - - - - OK - OK - - - - &Cancel - &Отмена - - - - COM &Object: - - - - - QAxServerBase - - - &Help - &Справка - - - - QCheckBox - - - Uncheck - - - - - Check - - - - - Toggle - - - - - QColorDialog - - - Hu&e: - &Тон: - - - - &Sat: - &Нас: - - - - &Val: - &Ярк: - - - - &Red: - &Крас: - - - - &Green: - &Зел: - - - - Bl&ue: - С&ин: - - - - A&lpha channel: - &Альфа-канал: - - - - &Basic colors - &Основные цвета - - - - &Custom colors - &Собственные цвета - - - - &Define Custom Colors >> - &Выбрать собственные цвета >> - - - - OK - OK - - - - Cancel - Отмена - - - - &Add to Custom Colors - &Добавить к собственным цветам - - - - Select color - Выбрать цвет - - - - QComboBox - - - Open - Открыть - - - - QDB2Driver - - - Unable to connect - - - - - Unable to commit transaction - - - - - Unable to rollback transaction - - - - - Unable to set autocommit - - - - - QDB2Result - - - Unable to execute statement - - - - - Unable to prepare statement - - - - - Unable to bind variable - - - - - Unable to fetch record %1 - - - - - Unable to fetch next - - - - - Unable to fetch first - - - - - QDataTable - - - True - True - - - - False - False - - - - Insert - Вставить - - - - Update - Обновить - - - - Delete - Удалить - - - - QDateTimeEdit - - - AM - - - - - am - - - - - PM - - - - - pm - - - - - QDialog - - - What's This? - Что это? - - - - Help - Справка - - - - QDialogButtonBox - - - OK - OK - - - - Save - Сохранить - - - - Open - Открыть - - - - Cancel - Отмена - - - - Close - Закрыть - - - - Apply - Применить - - - - Reset - - - - - Help - Справка - - - - Don't Save - - - - - Discard - - - - - &Yes - &Да - - - - Yes to &All - - - - - &No - &Нет - - - - N&o to All - - - - - Save All - - - - - Abort - - - - - Retry - - - - - Ignore - - - - - Restore Defaults - - - - - Close without Saving - - - - - QDialogButtons - - - Yes - Да - - - - OK - OK - - - - No - Нет - - - - Cancel - Отмена - - - - Apply - Применить - - - - Help - Справка - - - - QDirModel - - - Name - Имя - - - - Size - Размер - - - - Type - Тип - - - - Kind - Match OS X Finder - - - - - Type - All other platforms - Тип - - - - Date Modified - - - - - QErrorMessage - - - &Show this message again - &Показывать это сообщение в дальнейшем - - - - &OK - &OK - - - - Debug Message: - Отладочное сообщение: - - - - Warning: - Предупреждение: - - - - Fatal Error: - Критическая ошибка: - - - - QFileDialog - - - Copy or Move a File - Копировать или переместить файл - - - - Read: %1 - Открытие: %1 - - - - Write: %1 - Запись: %1 - - - - Cancel - Отмена - - - - All Files (*) - Все файлы (*) - - - - Name - Имя - - - - Size - Размер - - - - Type - Тип - - - - Date - Дата - - - - Attributes - Атрибуты - - - - OK - OK - - - - Look &in: - &Смотреть в: - - - - File &name: - &Имя файла: - - - - File &type: - &Тип файла: - - - - Back - Назад - - - - One directory up - Вверх на один уровень - - - - Create New Folder - Создать новый каталог - - - - List View - Список - - - - Detail View - Детальный вид - - - - Preview File Info - Предпросмотр информации о файле - - - - Preview File Contents - Предпросмотр содержимого файла - - - - Read-write - Чтение-запись - - - - Read-only - Только чтение - - - - Write-only - Только запись - - - - Inaccessible - Нет доступа - - - - Symlink to File - Ссылка на файл - - - - Symlink to Directory - Ссылка на каталог - - - - Symlink to Special - Ссылка на спецфайл - - - - File - Файл - - - - Dir - Каталог - - - - Special - Спецфайл - - - - Open - Открыть - - - - Save As - Сохранить как - - - - &Open - &Открыть - - - - &Save - &Сохранить - - - - &Rename - &Переименовать - - - - &Delete - &Удалить - - - - R&eload - О&бновить - - - - Sort by &Name - По &имени - - - - Sort by &Size - По &размеру - - - - Sort by &Date - По &дате - - - - &Unsorted - &Не упорядочивать - - - - Sort - Упорядочить - - - - Show &hidden files - Показать &скрытые файлы - - - - the file - файл - - - - the directory - каталог - - - - the symlink - ссылку - - - - Delete %1 - Удалить %1 - - - - <qt>Are you sure you wish to delete %1 "%2"?</qt> - <qt>Вы действительно хотите удалить %1 "%2"?</qt> - - - - &Yes - &Да - - - - &No - &Нет - - - - New Folder 1 - Новый каталог 1 - - - - New Folder - Новый каталог - - - - New Folder %1 - Новый каталог %1 - - - - Find Directory - Найти каталог - - - - Directories - Каталоги - - - - Save - Сохранить - - - - Error - Ошибка - - - - %1 -File not found. -Check path and filename. - %1 -Файл не найден. -Проверьте правильность пути и имени файла. - - - - All Files (*.*) - Все файлы (*.*) - - - - Open - Открыть - - - - Select a Directory - Выбрать каталог - - - - Directory: - Каталог: - - - - %1 already exists. -Do you want to replace it? - - - - - %1 -File not found. -Please verify the correct file name was given. - - - - - My Computer - - - - - &Reload - - - - - Parent Directory - - - - - Look in: - - - - - File name: - - - - - Files of type: - - - - - -File not found. -Please verify the correct file name was given - - - - - %1 -Directory not found. -Please verify the correct directory name was given. - - - - - '%1' is write protected. -Do you want to delete it anyway? - - - - - Are sure you want to delete '%1'? - - - - - Could not delete directory. - - - - - Drive - - - - - Unknown - - - - - QFileDialogPrivate - - - Sort - Упорядочить - - - - &Open - &Открыть - - - - &Rename - &Переименовать - - - - &Delete - &Удалить - - - - Sort by &Name - По &имени - - - - Sort by &Size - По &размеру - - - - Sort by &Date - По &дате - - - - &Unsorted - &Не упорядочивать - - - - Show &hidden files - Показать &скрытые файлы - - - - Back - Назад - - - - Create New Folder - Создать новый каталог - - - - List View - Список - - - - Detail View - Детальный вид - - - - Open - Открыть - - - - Cancel - Отмена - - - - QFont - - - Latin - Латинский - - - - Greek - Греческий - - - - Cyrillic - Кириллица - - - - Armenian - Армянский - - - - Georgian - Грузинский - - - - Runic - Рунический - - - - Ogham - Огам - - - - Hebrew - Иврит - - - - Arabic - Арабский - - - - Syriac - Сирийский - - - - Thaana - Thaana - - - - Devanagari - Деванагари - - - - Bengali - Бенгальский - - - - Gurmukhi - Gurmukhi - - - - Gujarati - Gujarati - - - - Oriya - Ория - - - - Tamil - Тамильский - - - - Telugu - Телугу - - - - Kannada - Каннада - - - - Malayalam - Малайялам - - - - Sinhala - Sinhala - - - - Thai - Тайский - - - - Lao - Лаосский - - - - Tibetan - Тибетский - - - - Myanmar - Myanmar - - - - Khmer - Кхмерский - - - - Han - Han - - - - Hiragana - Hiragana - - - - Katakana - Katakana - - - - Hangul - Хангул - - - - Bopomofo - Bopomofo - - - - Yi - Yi - - - - Ethiopic - Эфиопский - - - - Cherokee - Ирокезский - - - - Canadian Aboriginal - Канадский аборигенный - - - - Mongolian - Монгольский - - - - Currency Symbols - Валютные символы - - - - Letterlike Symbols - Буквенные символы - - - - Number Forms - Числовые знаки - - - - Mathematical Operators - Математические операторы - - - - Technical Symbols - Технические символы - - - - Geometric Symbols - Геометрические символы - - - - Miscellaneous Symbols - Разнообразные символы - - - - Enclosed and Square - Вложенные символы - - - - Braille - Символы Брайля - - - - Unicode - Уникод - - - - QFontDialog - - - &Font - &Шрифт - - - - Font st&yle - &Стиль шрифта - - - - &Size - &Размер - - - - Effects - Эффекты - - - - Stri&keout - &Перечеркивать - - - - &Underline - П&одчеркивать - - - - &Color - &Цвет - - - - Sample - Пример - - - - Scr&ipt - &Набор - - - - OK - OK - - - - Apply - Применить - - - - Cancel - Отмена - - - - Close - Закрыть - - - - Select Font - Выбрать шрифт - - - - Wr&iting System - - - - - QFtp - - - Host %1 found - Обнаружен узел %1 - - - - Host found - Узел обнаружен - - - - Connected to host %1 - Установлено соединение с узлом %1 - - - - Connected to host - Соединение с узлом установлено - - - - Connection to %1 closed - Соединение с узлом %1 разорвано - - - - Connection closed - Соединение разорвано - - - - Host %1 not found - Узел %1 не обнаружен - - - - Connection refused to host %1 - Отказано в соединении с узлом %1 - - - - Unknown error - Неизвестная ошибка - - - - Connecting to host failed: -%1 - Ошибка соединения с узлом: -%1 - - - - Login failed: -%1 - Ошибка входа в систему: -%1 - - - - Listing directory failed: -%1 - Ошибка просмотра каталога: -%1 - - - - Changing directory failed: -%1 - Ошибка смены каталога: -%1 - - - - Downloading file failed: -%1 - Ошибка загрузки файла: -%1 - - - - Uploading file failed: -%1 - Ошибка отправки файла: -%1 - - - - Removing file failed: -%1 - Ошибка удаления файла: -%1 - - - - Creating directory failed: -%1 - Ошибка создания каталога: -%1 - - - - Removing directory failed: -%1 - Ошибка удаления каталога: -%1 - - - - Not connected - Нет соединения - - - - Connection refused for data connection - Отказано в соединении передачи данных - - - - QHeader - - - %1 - %1 - - - - QHostInfo - - - Unknown error - Неизвестная ошибка - - - - QHostInfoAgent - - - Host not found - - - - - Unknown address type - - - - - Unknown error - Неизвестная ошибка - - - - QHttp - - - Connection refused - Отказано в соединении - - - - Host %1 not found - Узел %1 не обнаружен - - - - Wrong content length - Неверная длина данных - - - - HTTP request failed - Ошибка HTTP-запроса - - - - Host %1 found - Обнаружен узел %1 - - - - Host found - Узел обнаружен - - - - Connected to host %1 - Установлено соединение с узлом %1 - - - - Connected to host - Соединение с узлом установлено - - - - Connection to %1 closed - Соединение с узлом %1 разорвано - - - - Connection closed - Соединение разорвано - - - - Unknown error - Неизвестная ошибка - - - - Request aborted - Запрос отменен - - - - No server set to connect to - Не выбран сервер для подключения - - - - Server closed connection unexpectedly - Неожиданный разрыв соединения сервером - - - - Invalid HTTP response header - Получен некорректный HTTP-заголовок - - - - Invalid HTTP chunked body - Некорректный HTTP-ответ - - - - QIBaseDriver - - - Error opening database - - - - - Could not start transaction - - - - - Unable to commit transaction - - - - - Unable to rollback transaction - - - - - QIBaseResult - - - Unable to create BLOB - - - - - Unable to write BLOB - - - - - Unable to open BLOB - - - - - Unable to read BLOB - - - - - Could not find array - - - - - Could not get array data - - - - - Could not get query info - - - - - Could not start transaction - - - - - Unable to commit transaction - - - - - Could not allocate statement - - - - - Could not prepare statement - - - - - Could not describe input statement - - - - - Could not describe statement - - - - - Unable to close statement - - - - - Unable to execute query - - - - - Could not fetch next item - - - - - Could not get statement info - - - - - QIODevice - - - Permission denied - - - - - Too many open files - - - - - No such file or directory - - - - - No space left on device - - - - - Unknown error - Неизвестная ошибка - - - - QInputContext - - - XIM - - - - - XIM input method - - - - - Windows input method - - - - - Mac OS X input method - - - - - QInputDialog - - - OK - OK - - - - Cancel - Отмена - - - - QLibrary - - - QLibrary::load_sys: Cannot load %1 (%2) - - - - - QLibrary::unload_sys: Cannot unload %1 (%2) - - - - - QLibrary::resolve_sys: Symbol "%1" undefined in %2 (%3) - - - - - Could not mmap '%1': %2 - - - - - Plugin verification data mismatch in '%1' - - - - - Could not unmap '%1': %2 - - - - - The plugin '%1' uses incompatible Qt library. (%2.%3.%4) [%5] - - - - - The plugin '%1' uses incompatible Qt library. Expected build key "%2", got "%3" - - - - - Unknown error - Неизвестная ошибка - - - - QLineEdit - - - &Undo - &Отменить - - - - &Redo - &Повторить - - - - Cu&t - &Вырезать - - - - &Copy - &Копировать - - - - &Paste - В&ставить - - - - Clear - Очистить - - - - Select All - Выделить все - - - - Delete - Удалить - - - - QLineEditPrivate - - - &Undo - &Отменить - - - - &Redo - &Повторить - - - - Cu&t - &Вырезать - - - - &Copy - &Копировать - - - - Delete - Удалить - - - - Select All - Выделить все - - - - QLocalFs - - - Could not rename -%1 -to -%2 - Невозможно переименовать -%1 -в -%2 - - - - Could not open -%1 - Невозможно открыть -%1 - - - - Could not write -%1 - Невозможно записать -%1 - - - - Could not read directory -%1 - Невозможно просмотреть каталог -%1 - - - - Could not create directory -%1 - Невозможно создать каталог -%1 - - - - Could not remove file or directory -%1 - Невозможно удалить файл или каталог -%1 - - - - QMYSQLDriver - - - Unable to open database ' - - - - - Unable to connect - - - - - Unable to begin transaction - - - - - Unable to commit transaction - - - - - Unable to rollback transaction - - - - - QMYSQLResult - - - Unable to fetch data - - - - - Unable to execute query - - - - - Unable to store result - - - - - Unable to prepare statement - - - - - Unable to reset statement - - - - - Unable to bind value - - - - - Unable to execute statement - - - - - Unable to bind outvalues - - - - - Unable to store statement results - - - - - QMainWindow - - - Line up - Выровнять - - - - Customize... - Настроить... - - - - QMenu - - - Close - Закрыть - - - - Open - Открыть - - - - Execute - - - - - QMenuBar - - - About - О программе - - - - Config - Конфигурация - - - - Preference - Настройки - - - - Options - Параметры - - - - Setting - Настройки - - - - Setup - Настройки - - - - Quit - Выход - - - - Exit - Выход - - - - About %1 - - - - - About Qt - - - - - Preferences - - - - - Quit %1 - - - - - QMessageBox - - - OK - OK - - - - Cancel - Отмена - - - - &Yes - &Да - - - - &No - &Нет - - - - &Abort - &Прервать - - - - &Retry - П&овторить - - - - &Ignore - &Игнорировать - - - - <h3>About Qt</h3><p>This program uses Qt version %1.</p><p>Qt is a C++ toolkit for multiplatform GUI &amp; application development.</p><p>Qt provides single-source portability across MS&nbsp;Windows, Mac&nbsp;OS&nbsp;X, Linux, and all major commercial Unix variants.<br>Qt is also available for embedded devices.</p><p>Qt is a Trolltech product. See <tt>http://www.trolltech.com/qt/</tt> for more information.</p> - <h3>О Qt</h3><p>Эта программа использует Qt версии %1.</p><p>Qt - это C++ GUI инструментарий для разработки многоплатформенных приложений.</p><p>Qt обеспечивает переносимость на уровне исходного кода между MS&nbsp;Windows, Mac&nbsp;OS&nbsp;X, Linux и всеми основными версиями Unix.<br>Qt также доступен для встраиваемых систем.</p><p>Qt является продуктом Trolltech. См. <tt>http://www.trolltech.com/qt/</tt> для получения более подробной информации.</p> - - - - About Qt - - - - - Help - Справка - - - - <p>This program uses Qt version %1.</p> - - - - - <h3>About Qt</h3>%1<p>Qt is a C++ toolkit for cross-platform application development.</p><p>Qt provides single-source portability across MS&nbsp;Windows, Mac&nbsp;OS&nbsp;X, Linux, and all major commercial Unix variants. Qt is also available for embedded devices as Qtopia Core.</p><p>Qt is a Trolltech product. See <a href="http://www.trolltech.com/qt/">www.trolltech.com/qt/</a> for more information.</p> - - - - - Show Details... - - - - - Hide Details... - - - - - <p>This program uses Qt Open Source Edition version %1.</p><p>Qt Open Source Edition is intended for the development of Open Source applications. You need a commercial Qt license for development of proprietary (closed source) applications.</p><p>Please see <a href="http://www.trolltech.com/company/model/">www.trolltech.com/company/model/</a> for an overview of Qt licensing.</p> - - - - - QMultiInputContext - - - Select IM - - - - - QMultiInputContextPlugin - - - Multiple input method switcher - - - - - Multiple input method switcher that uses the context menu of the text widgets - - - - - QNativeSocketEngine - - - The remote host closed the connection - - - - - Network operation timed out - - - - - Out of resources - - - - - Unsupported socket operation - - - - - Protocol type not supported - - - - - Invalid socket descriptor - - - - - Network unreachable - - - - - Permission denied - - - - - Connection timed out - - - - - Connection refused - Отказано в соединении - - - - The bound address is already in use - - - - - The address is not available - - - - - The address is protected - - - - - Unable to send a message - - - - - Unable to receive a message - - - - - Unable to write - - - - - Network error - - - - - Another socket is already listening on the same port - - - - - Unable to initialize non-blocking socket - - - - - Unable to initialize broadcast socket - - - - - Attempt to use IPv6 socket on a platform with no IPv6 support - - - - - Host unreachable - - - - - Datagram was too large to send - - - - - Operation on non-socket - - - - - Unknown error - Неизвестная ошибка - - - - QNetworkProtocol - - - Operation stopped by the user - Операция прервана пользователем - - - - QOCIDriver - - - Unable to logon - - - - - Unable to initialize - QOCIDriver - - - - - QOCIResult - - - Unable to bind column for batch execute - - - - - Unable to execute batch statement - - - - - Unable to goto next - - - - - Unable to alloc statement - - - - - Unable to prepare statement - - - - - Unable to bind value - - - - - Unable to execute select statement - - - - - Unable to execute statement - - - - - QODBCDriver - - - Unable to connect - - - - - Unable to connect - Driver doesn't support all needed functionality - - - - - Unable to disable autocommit - - - - - Unable to commit transaction - - - - - Unable to rollback transaction - - - - - Unable to enable autocommit - - - - - QODBCResult - - - QODBCResult::reset: Unable to set 'SQL_CURSOR_STATIC' as statement attribute. Please check your ODBC driver configuration - - - - - Unable to execute statement - - - - - Unable to fetch next - - - - - Unable to prepare statement - - - - - Unable to bind variable - - - - - QObject - - - All Files (*) - Все файлы (*) - - - - All Files (*.*) - Все файлы (*.*) - - - - Open - Открыть - - - - Save As - Сохранить как - - - - Open - Открыть - - - - Select a Directory - Выбрать каталог - - - - File - Файл - - - - False - False - - - - True - True - - - - Home - Home - - - - QPSQLDriver - - - Unable to connect - - - - - Could not begin transaction - - - - - Could not commit transaction - - - - - Could not rollback transaction - - - - - QPSQLResult - - - Unable to create query - - - - - QPageSetupDialog - - - OK - OK - - - - Cancel - Отмена - - - - Portrait - Портрет - - - - Landscape - Альбом - - - - QPluginLoader - - - Unknown error - Неизвестная ошибка - - - - QPrintDialog - - - locally connected - локальный - - - - Aliases: %1 - Алиасы: %1 - - - - unknown - неизвестно - - - - Unknown Location - неизвестно - - - - OK - OK - - - - Cancel - Отмена - - - - Printer settings - Настройки печати - - - - Print in color if available - Цветная печать - - - - Print in grayscale - В оттенках серого - - - - Print destination - Направление печати - - - - Print to printer: - Печать на принтер: - - - - Printer - Принтер - - - - Host - Узел - - - - Comment - Комментарий - - - - Print to file: - Печать в файл: - - - - Browse... - Обзор... - - - - Options - Параметры - - - - Print all - Печатать все - - - - Print range - Печатать диапазон - - - - From page: - Со страницы: - - - - To page: - До страницы: - - - - Print first page first - Начать с первой страницы - - - - Print last page first - Начать с последней страницы - - - - Number of copies: - Число копий: - - - - Paper format - Формат бумаги - - - - Portrait - Портрет - - - - Landscape - Альбом - - - - A0 (841 x 1189 mm) - A0 (841 x 1189 мм) - - - - A1 (594 x 841 mm) - A1 (594 x 841 мм) - - - - A2 (420 x 594 mm) - A2 (420 x 594 мм) - - - - A3 (297 x 420 mm) - A3 (297 x 420 мм) - - - - A4 (210x297 mm, 8.26x11.7 inches) - A4 (210x297 мм, 8.26x11.7 дюйм) - - - - A5 (148 x 210 mm) - A5 (148 x 210 мм) - - - - A6 (105 x 148 mm) - A6 (105 x 148 мм) - - - - A7 (74 x 105 mm) - A7 (74 x 105 мм) - - - - A8 (52 x 74 mm) - A8 (52 x 74 мм) - - - - A9 (37 x 52 mm) - A9 (37 x 52 мм) - - - - B0 (1000 x 1414 mm) - B0 (1000 x 1414 мм) - - - - B1 (707 x 1000 mm) - B1 (707 x 1000 мм) - - - - B2 (500 x 707 mm) - B2 (500 x 707 мм) - - - - B3 (353 x 500 mm) - B3 (353 x 500 мм) - - - - B4 (250 x 353 mm) - B4 (250 x 353 мм) - - - - B5 (176 x 250 mm, 6.93x9.84 inches) - B5 (176x250 мм, 6.93x9.84 дюйм) - - - - B6 (125 x 176 mm) - B6 (125 x 176 мм) - - - - B7 (88 x 125 mm) - B7 (88 x 125 мм) - - - - B8 (62 x 88 mm) - B8 (62 x 88 мм) - - - - B9 (44 x 62 mm) - B9 (44 x 62 мм) - - - - B10 (31 x 44 mm) - B10 (31 x 44 мм) - - - - C5E (163 x 229 mm) - C5E (163 x 229 мм) - - - - DLE (110 x 220 mm) - DLE (110 x 220 мм) - - - - Executive (7.5x10 inches, 191x254 mm) - Executive (7.5x10 дюйм, 191x254 мм) - - - - Folio (210 x 330 mm) - Folio (210 x 330 мм) - - - - Ledger (432 x 279 mm) - Ledger (432 x 279 мм) - - - - Legal (8.5x14 inches, 216x356 mm) - Legal (8.5x14 дюйм, 216x356 мм) - - - - Letter (8.5x11 inches, 216x279 mm) - Letter (8.5x11 дюйм, 216x279 мм) - - - - Tabloid (279 x 432 mm) - Tabloid (279 x 432 мм) - - - - US Common #10 Envelope (105 x 241 mm) - Конверт US #10 (105x241 мм) - - - - Setup Printer - Настройки принтера - - - - PostScript Files (*.ps);;All Files (*) - Файлы PostScript (*.ps);;Все файлы (*) - - - - A4 (210 x 297 mm, 8.26 x 11.7 inches) - - - - - B5 (176 x 250 mm, 6.93 x 9.84 inches) - - - - - Executive (7.5 x 10 inches, 191 x 254 mm) - - - - - Legal (8.5 x 14 inches, 216 x 356 mm) - - - - - Letter (8.5 x 11 inches, 216 x 279 mm) - - - - - Page size: - - - - - Orientation: - - - - - Paper source: - - - - - Print - Print - - - - File - Файл - - - - Print To File ... - - - - - Print dialog - - - - - Size: - - - - - Properties - - - - - Printer info: - - - - - Browse - - - - - Print to file - - - - - Pages from - - - - - to - - - - - Selection - - - - - Copies - - - - - Collate - - - - - Other - - - - - Double side printing - - - - - File %1 is not writable. -Please choose a different file name. - - - - - %1 already exists. -Do you want to overwrite it? - - - - - File exists - - - - - <qt>Do you want to overwrite it?</qt> - - - - - Print selection - - - - - QPrintDialogPrivate - - - Printer settings - Настройки печати - - - - Print in color if available - Цветная печать - - - - Print in grayscale - В оттенках серого - - - - Print destination - Направление печати - - - - Print to printer: - Печать на принтер: - - - - Print to file: - Печать в файл: - - - - Browse... - Обзор... - - - - Options - Параметры - - - - Print all - Печатать все - - - - Print range - Печатать диапазон - - - - From page: - Со страницы: - - - - To page: - До страницы: - - - - Print first page first - Начать с первой страницы - - - - Print last page first - Начать с последней страницы - - - - Number of copies: - Число копий: - - - - Paper format - Формат бумаги - - - - Portrait - Портрет - - - - Landscape - Альбом - - - - PostScript Files (*.ps);;All Files (*) - Файлы PostScript (*.ps);;Все файлы (*) - - - - OK - OK - - - - Cancel - Отмена - - - - QPrintPropertiesDialog - - - PPD Properties - - - - - Save - Сохранить - - - - OK - OK - - - - QProcess - - - Unknown error - Неизвестная ошибка - - - - QProgressBar - - - QProgressDialog - - - Cancel - Отмена - - - - QPushButton - - - Open - Открыть - - - - QRadioButton - - - Check - - - - - QRegExp - - - no error occurred - ошибки отсутствуют - - - - disabled feature used - использовались отключенные возможности - - - - bad char class syntax - bad char class syntax - - - - bad lookahead syntax - bad lookahead syntax - - - - bad repetition syntax - bad repetition syntax - - - - invalid octal value - некорректное восьмеричное значение - - - - missing left delim - отсутствует левый разделитель - - - - unexpected end - неожиданный конец - - - - met internal limit - достигнуто внутреннее ограничение - - - - QSQLite2Driver - - - Error to open database - - - - - Unable to begin transaction - - - - - Unable to commit transaction - - - - - Unable to rollback Transaction - - - - - QSQLite2Result - - - Unable to fetch results - - - - - Unable to execute statement - - - - - QSQLiteDriver - - - Error opening database - - - - - Error closing database - - - - - Unable to begin transaction - - - - - Unable to commit transaction - - - - - Unable to roll back transaction - - - - - QSQLiteResult - - - Unable to fetch row - - - - - Unable to execute statement - - - - - Unable to reset statement - - - - - Unable to bind parameters - - - - - Parameter count mismatch - - - - - QScrollBar - - - Scroll here - - - - - Left edge - - - - - Top - - - - - Right edge - - - - - Bottom - - - - - Page left - - - - - Page up - - - - - Page right - - - - - Page down - - - - - Scroll left - - - - - Scroll up - - - - - Scroll right - - - - - Scroll down - - - - - Line up - Выровнять - - - - Position - - - - - Line down - - - - - QShortcut - - - Space - Space - - - - Esc - Esc - - - - Tab - Tab - - - - Backtab - Backtab - - - - Backspace - Backspace - - - - Return - Return - - - - Enter - Enter - - - - Ins - Ins - - - - Del - Del - - - - Pause - Pause - - - - Print - Print - - - - SysReq - SysReq - - - - Home - Home - - - - End - End - - - - Left - Left - - - - Up - Up - - - - Right - Right - - - - Down - Down - - - - PgUp - PgUp - - - - PgDown - PgDown - - - - CapsLock - CapsLock - - - - NumLock - NumLock - - - - ScrollLock - ScrollLock - - - - Menu - Меню - - - - Help - Справка - - - - Back - Назад - - - - Forward - Вперед - - - - Stop - Стоп - - - - Refresh - Обновить - - - - Volume Down - Тише - - - - Volume Mute - Выключить звук - - - - Volume Up - Громче - - - - Bass Boost - Bass Boost - - - - Bass Up - Bass Up - - - - Bass Down - Bass Down - - - - Treble Up - Treble Up - - - - Treble Down - Treble Down - - - - Media Play - Воспроизведение - - - - Media Stop - Остановить воспроизведение - - - - Media Previous - Воспроизвести предыдущее - - - - Media Next - Воспроизвести следующее - - - - Media Record - Запись - - - - Favorites - Избранное - - - - Search - Поиск - - - - Standby - Дежурный режим - - - - Open URL - Открыть URL - - - - Launch Mail - Почта - - - - Launch Media - Проигрыватель - - - - Launch (0) - Запустить (0) - - - - Launch (1) - Запустить (1) - - - - Launch (2) - Запустить (2) - - - - Launch (3) - Запустить (3) - - - - Launch (4) - Запустить (4) - - - - Launch (5) - Запустить (5) - - - - Launch (6) - Запустить (6) - - - - Launch (7) - Запустить (7) - - - - Launch (8) - Запустить (8) - - - - Launch (9) - Запустить (9) - - - - Launch (A) - Запустить (A) - - - - Launch (B) - Запустить (B) - - - - Launch (C) - Запустить (C) - - - - Launch (D) - Запустить (D) - - - - Launch (E) - Запустить (E) - - - - Launch (F) - Запустить (F) - - - - Print Screen - - - - - Page Up - - - - - Page Down - - - - - Caps Lock - - - - - Num Lock - - - - - Number Lock - - - - - Scroll Lock - - - - - Insert - Вставить - - - - Delete - Удалить - - - - Escape - - - - - System Request - - - - - Select - - - - - Yes - Да - - - - No - Нет - - - - Context1 - - - - - Context2 - - - - - Context3 - - - - - Context4 - - - - - Call - - - - - Hangup - - - - - Flip - - - - - Ctrl - Ctrl - - - - Shift - Shift - - - - Alt - Alt - - - - Meta - Meta - - - - + - + - - - - F%1 - F%1 - - - - Home Page - - - - - QSlider - - - Page left - - - - - Page up - - - - - Position - - - - - Page right - - - - - Page down - - - - - QSocks5SocketEngine - - - Socks5 timeout error connecting to socks server - - - - - QSpinBox - - - More - - - - - Less - - - - - QSql - - - Delete - Удалить - - - - Delete this record? - Удалить эту запись? - - - - Yes - Да - - - - No - Нет - - - - Insert - Вставить - - - - Update - Обновить - - - - Save edits? - Сохранить изменения? - - - - Cancel - Отмена - - - - Confirm - Подтвердить - - - - Cancel your edits? - Отменить изменения? - - - - QTDSDriver - - - Unable to open connection - - - - - Unable to use database - - - - - QTabBar - - - Scroll Left - - - - - Scroll Right - - - - - QTabDialog - - - OK - OK - - - - Apply - Применить - - - - Help - Справка - - - - Defaults - По умолчанию - - - - Cancel - Отмена - - - - QTcpServer - - - Socket operation unsupported - - - - - QTextControl - - - &Undo - &Отменить - - - - &Redo - &Повторить - - - - Cu&t - &Вырезать - - - - &Copy - &Копировать - - - - Copy &Link Location - - - - - &Paste - &Вставить - - - - Delete - Удалить - - - - Select All - Выделить все - - - - QTextEdit - - - &Undo - &Отменить - - - - &Redo - &Повторить - - - - Cu&t - &Вырезать - - - - &Copy - &Копировать - - - - &Paste - &Вставить - - - - Clear - Очистить - - - - Select All - Выделить все - - - - Delete - Удалить - - - - QTitleBar - - - System Menu - Системное меню - - - - Shade - Свернуть в заголовок - - - - Unshade - Восстановить из заголовка - - - - Normalize - Восстановить - - - - Minimize - Свернуть - - - - Maximize - Развернуть - - - - Close - Закрыть - - - - QToolBar - - - More... - Больше... - - - - QToolButton - - - Press - - - - - Open - Открыть - - - - QUdpSocket - - - This platform does not support IPv6 - - - - - QUndoGroup - - - Undo - Отменить - - - - Redo - Повторить - - - - QUndoModel - - - <empty> - - - - - QUndoStack - - - Undo - Отменить - - - - Redo - Повторить - - - - QUnicodeControlCharacterMenu - - - LRM Left-to-right mark - - - - - RLM Right-to-left mark - - - - - ZWJ Zero width joiner - - - - - ZWNJ Zero width non-joiner - - - - - ZWSP Zero width space - - - - - LRE Start of left-to-right embedding - - - - - RLE Start of right-to-left embedding - - - - - LRO Start of left-to-right override - - - - - RLO Start of right-to-left override - - - - - PDF Pop directional formatting - - - - - Insert Unicode control character - - - - - QUrlOperator - - - The protocol `%1' is not supported - Протокол `%1' не поддерживается - - - - The protocol `%1' does not support listing directories - Протокол `%1' не поддерживает просмотр каталогов - - - - The protocol `%1' does not support creating new directories - Протокол `%1' не поддерживает создание новых каталогов - - - - The protocol `%1' does not support removing files or directories - Протокол `%1' не поддерживает удаление файлов или каталогов - - - - The protocol `%1' does not support renaming files or directories - Протокол `%1' не поддерживает переименование файлов или каталогов - - - - The protocol `%1' does not support getting files - Протокол `%1' не поддерживает доставку файлов - - - - The protocol `%1' does not support putting files - Протокол `%1' не поддерживает отправку файлов - - - - The protocol `%1' does not support copying or moving files or directories - Протокол `%1' не поддерживает копирование или перемещение файлов и каталогов - - - - (unknown) - (неизвестно) - - - - QWSDecoration - - - &Restore - &Восстановить - - - - &Move - &Переместить - - - - &Size - &Размер - - - - Mi&nimize - &Свернуть - - - - Ma&ximize - Р&азвернуть - - - - Close - Закрыть - - - - Windows - Windows - - - - KDE - KDE - - - - KDE2 - KDE2 - - - - BeOS - BeOS - - - - Hydro - Hydro - - - - Default - По умолчанию - - - - QWhatsThisAction - - - What's This? - Что это? - - - - QWhatsThisButton - - - What's this? - Что это? - - - - QWidget - - - * - - - - - QWizard - - - &Cancel - &Отмена - - - - < &Back - < &Назад - - - - &Next > - &Вперед > - - - - &Finish - &Финиш - - - - &Help - &Справка - - - - QWorkspace - - - &Restore - &Восстановить - - - - &Move - &Переместить - - - - &Size - &Размер - - - - Mi&nimize - &Свернуть - - - - Ma&ximize - Р&азвернуть - - - - &Close - &Закрыть - - - - Stay on &Top - Всегда &наверху - - - - Sh&ade - Свернуть в за&головок - - - - %1 - [%2] - %1 - [%2] - - - - Minimize - Свернуть - - - - Restore Down - Восстановить - - - - Close - Закрыть - - - - &Unshade - Восстановить из за&головка - - - - QWorkspacePrivate - - - &Restore - &Восстановить - - - - &Move - &Переместить - - - - &Size - &Размер - - - - Mi&nimize - &Свернуть - - - - Ma&ximize - Р&азвернуть - - - - &Close - &Закрыть - - - - Stay on &Top - Всегда &наверху - - - - Sh&ade - Свернуть в за&головок - - - - %1 - [%2] - %1 - [%2] - - - - Minimize - Свернуть - - - - Restore Down - Восстановить - - - - Close - Закрыть - - - - &Unshade - Восстановить из за&головка - - - - QXml - - - no error occurred - ошибки отсутствуют - - - - error triggered by consumer - ошибка инициирована пользователем - - - - unexpected end of file - неожиданный конец файла - - - - more than one document type definition - определен более, чем один тип документов - - - - error occurred while parsing element - в процессе грамматического разбора элемента произошла ошибка - - - - tag mismatch - отсутствует тег - - - - error occurred while parsing content - в процессе грамматического разбора произошла ошибка - - - - unexpected character - неожиданный символ - - - - invalid name for processing instruction - некорректное имя директивы - - - - version expected while reading the XML declaration - при чтении XML-тега ожидался параметр version - - - - wrong value for standalone declaration - некорректное значение параметра standalone - - - - encoding declaration or standalone declaration expected while reading the XML declaration - при чтении XML-тега ожидался параметр encoding или параметр standalone - - - - standalone declaration expected while reading the XML declaration - при чтении XML-тега ожидался параметр standalone - - - - error occurred while parsing document type definition - в процессе грамматического разбора типа документа произошла ошибка - - - - letter is expected - ожидался символ - - - - error occurred while parsing comment - в процессе грамматического разбора комментария произошла ошибка - - - - error occurred while parsing reference - в процессе грамматического разбора ссылки произошла ошибка - - - - internal general entity reference not allowed in DTD - internal general entity reference not allowed in DTD - - - - external parsed general entity reference not allowed in attribute value - external parsed general entity reference not allowed in attribute value - - - - external parsed general entity reference not allowed in DTD - external parsed general entity reference not allowed in DTD - - - - unparsed entity reference in wrong context - unparsed entity reference in wrong context - - - - recursive entities - рекурсивные объекты - - - - error in the text declaration of an external entity - error in the text declaration of an external entity - - - - QtMultiLineEdit - - - Undo - Отменить - - - - Redo - Повторить - - - - Cut - Вырезать - - - - Copy - Копировать - - - - Paste - Вставить - - - - Paste special... - Специальная вставка... - - - - Clear - Очистить - - - - Select All - Выделить все - - - diff -Nru treeline-2.0.2/i18n/translations/treeline_de.ts treeline-3.0.1/i18n/translations/treeline_de.ts --- treeline-2.0.2/i18n/translations/treeline_de.ts 2018-04-08 20:12:57.000000000 +0000 +++ treeline-3.0.1/i18n/translations/treeline_de.ts 2018-10-17 14:52:24.000000000 +0000 @@ -1,312 +1,338 @@ - + + conditional - + starts with beginnt mit - + ends with endet mit - + contains enthält - + True Wahr - + False Falsch - + and und - + or oder - + [All Types] [Alle Typen] - + Node Type Knotentyp - + &Add New Rule &Regel hinzufügen - + &Remove Rule &Regel löschen - + &OK &OK - + &Cancel &Abbrechen R&etrieve Rules... - Regelwerk &lesen... + Regelwerk &lesen... &Save Rules... - Regelwerk &speichern... + Regelwerk &speichern... - + Find &Previous Suche &Vorherigen - + Find &Next Suche &Nächsten - + &Filter &Filter - + &End Filter Filter &beenden - + &Close &Schließen Save Rules - Regelwerk speichern + Regelwerk speichern Enter a descriptive name - Geben Sie einen sprechenden Namen ein + Geben Sie einen sprechenden Namen ein - + No conditional matches were found Keine Übereinstimmungen gefunden - + Rule {0} Regel {0} Retrieve Rules - Regelwerk lesen + Regelwerk lesen Select rule set to retrieve: - Regelwerk auswählen: + Regelwerk auswählen: Remove Rule - Regel entfernen + Regel entfernen + + + + Saved Rules + Gespeicherte Regeln + + + + Name: + Name: + + + + &Load + &Laden + + + + &Save + S&peichern + + + + &Delete + &Löschen configdialog - + Configure Data Types Datentypen konfigurieren - + T&ype List &Typliste - + Field &List &Feldliste - + &Field Config Feld &konfigurieren - + &Show Advanced Erweitert &anzeigen - + &OK &OK - + &Apply &Anwenden - + &Reset &Zurücksetzen - + &Cancel &Abbrechen - + Add or Remove Data Types Datentypen hinzufügen oder löschen - + &New Type... &Neuer Typ... - + Co&py Type... Typ &kopieren... - + &Delete Type Typ &löschen - + Add Type Typ hinzufügen - + Enter new type name: - Gebe den Namen des neuen Typs ein: + Gib den Namen des neuen Typs ein: - + Rename Type Typ umbenennen - + Cannot delete data type being used by nodes - Kann keinen Datentyp löschen der noch in Benutzung ist + Kann keinen Datentyp löschen, der noch in Benutzung ist - + &Data Type &Datentyp - + Icon Icon - + Change &Icon &Icon ändern - + Derived from &Generic Type Abgeleitet von &Basistyp - + Automatic Types Automatische Typen - + Modify Co&nditional Types &Bedingte Typen ändern - + Create Co&nditional Types &Bedingte Typen anlegen - + Set Types Conditionally Typ durch Bedingung setzen - + Name Name - + Type Typ - + Move &Up Nach &oben verschieben - + Move Do&wn Nach &unten verschieben - + &New Field... &Neues Feld... - + Add Field Feld hinzufügen - + Enter new field name: Name des neuen Feldes eingeben: - + Rename Field Feld umbenennen - + F&ield &Feld @@ -341,178 +367,178 @@ &Anzahl der Textzeilen - + File Info Reference Dateiinfo-Verweis - + Parent Reference Vater-Verweis - + Child Reference Kind-Verweis - + Child Count Anzahl Kinder - + F&ield List &Feldliste - + Other Field References Verweise zu anderen Feldern - + Copy Type Typ kopieren - + Set Data Type Icon Datentyp Icon zuweisen - + Clear &Select Auswahl &zurücksetzen - + Typ&e Config Typ &Konfiguration - + O&utput &Ausgabe - + &Hide Advanced Erweitert &ausblenden - + Error - circular reference in math field equations - Fehler - Zirkulärer Verweis in berechnetem Feld + Fehler - Zirkulärer Verweis im berechnetem Feld - + Rena&me Type... Typ &umbenennen... - + &Derive from original Von &Original ableiten - + Rename from {} to: Umbenennen von {} nach: - + [None] no type set [Leer] - + Default Child &Type Standard Kind &Typ - + Output Options Ausgabeoptionen - + Add &blank lines between nodes &Leere Zeilen zwischen Knotenhinzufügen - + Allow &HTML rich text in format &HTML Text im Format erlauben - + Add text bullet&s &Aufzählungspunkte hinzufügen - + Use a table for field &data Eine Tabelle für &Feldwerte verwenden - + Combination && Child List Output &Separator Ausgabe &Trenner für Kombination && Kindliste - + Uni&que ID Reference Field - &Verweisfeld für eindeutige ID + &Verweisfeld für eindeutige ID - + None Leer - + Modify &Field List &Feldliste bearbeiten - + Sort Key Sortierfeld - + Move U&p Nach &oben verschieben - + Rena&me Field... Feld &umbenennen... - + Dele&te Field Feld &löschen - + Sort &Keys... &Sortierfelder... - + fwd >> - + rev << - + &Field Type &Feldtyp @@ -537,794 +563,928 @@ Ausdruck definieren - + &Title Format - &Überschrift format + &Überschrift Format - + Out&put Format &Ausgabeformat - + Reference Le&vel Verweis&stufe - + Refere&nce Type Verweis&typ - + The name cannot be empty Der Name kann nicht leer sein - + The name must start with a letter Der Name muss mit einem Buchstaben beginnen - + The name cannot start with "xml" Der Name kann nicht mit "xml" beginnen - + The name cannot contain spaces Der Name darf keine Leerzeichen enthalten - + The following characters are not allowed: {} Die folgenden Zeichen sind nicht erlaubt: {} - + The name was already used Der Name ist bereits in Verwendung - + forward vorwärts - + reverse rückwärts - + Sort Key Fields Sortierfelder - + Available &Fields Verfügbare &Felder - + &Sort Criteria &Sortierbedingung - + Field Feld - + Direction Richtung - + &Move Down Nach &unten - + Flip &Direction Richtung &umkehren - + Self Reference Verweis auf sich selbst - + Root Reference Verweis auf Wurzel - + Numeric Result - Numerisch + Numerisch - + Date Result Datum - + Time Result Uhrzeit - + add hinzufügen - + subtract abziehen - + multiply multiplizieren - + divide dividieren - + floor divide dividieren mit runden - + modulus modulo - + power exponieren - + sum of items summieren - + maximum Maximum - + minimum Minimum - + average Durchschnitt - + absolute value Absolutwert - + square root Quadratwurzel - + natural logarithm Natürlicher Logarithmus - + base-10 logarithm Logarithmus Basis 10 - + factorial Fakultät - + round to num digits Runden auf n Stellen - + lower integer Niedrigere Ganzzahl - + higher integer Höhere Ganzzahl - + truncated integer Abgeschnittene Ganzzahl - + floating point - Flieskommazahl + Fliesskommazahl - + sine of radians Sinus Bogenmaß - + cosine of radians Cosinus Bogenmaß - + tangent of radians Tangens Bogenmaß - + arc sine Arcus Sinus - + arc cosine Arcus Cosinus - + arc tangent Arcus Tangens - + radians to degrees Bogenmaß nach Grad - + degrees to radians Grad nach Bogenmaß - + pi constant PI - + natural log constant Eulersche Zahl - + Define Math Field Equation Mathematischen Ausdruck definieren - + Field References Feldverweis - + Reference &Level Verweis &Stufe - + Reference &Type Verweis &Typ - + Available &Field List Verfügbare &Feldliste - + &Result Type &Ergebnistyp - + Description Beschreibung - + &Equation &Gleichung - + Equation error: {} Fehler in Gleichung: {} - + Boolean Result Bool'sches Ergebnis - + Text Result Text Ergebnis - + Arithmetic Operators Arithmetische Operatoren - + Comparison Operators Vergleichsoperatoren - + Text Operators Text Operatoren - + equal to Ist gleich - + less than Ist kleiner - + greater than Ist größer - + less than or equal to Ist kleiner oder gleich - + greater than or equal to Ist größer oder gleich - + not equal to Ist nicht gleich - + true value, condition, false value Wahrer Wert, Bedingung, Falscher Wert - + true if 1st text arg starts with 2nd arg Wahr, wenn 1. Argument mit 2. Argument beginnt - + true if 1st text arg ends with 2nd arg Wahr, wenn 1. Argument mit 2. Argument endet - + true if 1st text arg contains 2nd arg Wahr, wenn 1.Argument 2.Argument enthält - + concatenate text Text aneinanderhängen - + join text using 1st arg as separator Text verbinden mit 1. Argument als Trenner - + convert text to upper case Text in Großbuchstaben konvertieren - + convert text to lower case Text in Kleinbuchsrtaben konvertieren - + in 1st arg, replace 2nd arg with 3rd arg Ersetze in 1. Argument das 2. Argument durch das 3. Argument - + Operations Operationen - + O&perator Type Typ des O&perators - + Oper&ator List Liste der Oper&atoren - + logical and Logisches Und - + logical or Logisches Oder + + + Output HTML + HTML Ausgabe + + + + Evaluate &HTML tags + HTML Tags + + + + Child Type Limits + Kind Typ Grenzen + + + + [All Types Available] + [Alle Typen verfügbar] + + + + &Select All + &Alles auswählen + + + + Select &None + Nichts + + + + Number Result + Nummer Ergebnis + dataeditors - + Today's &Date Heutiges &Datum - + &Open Link &Verweis öffnen - + Open &Folder &Verzeichnis öffnen - + External Link Externer Verweis - + Scheme Schema - + &Browse for File Datei &suchen - + File Path Type Verzeichnis Angabe - + Absolute Absolut - + Relative Relativ - + Address Adresse - + Display Name Anzeigename - + &OK &OK - + &Cancel &Abbrechen - + TreeLine - External Link File Verweis zu externer TreeLine Datei - + &Go to Target &Gehe zu Ziel - + Enable click-on-&target - Aktiviere "Click aktiviert Ziel" + Aktiviere "Click aktiviert Ziel" - + Disable click-on-&target - Deaktiviere "Click aktiviert Ziel" + Deaktiviere "Click aktiviert Ziel" - + Internal Link Interner Verweis - + Unique ID - Eindeutige ID + Eindeutige ID - + &Open Picture &Öffne Bild - + Picture Link Verweis auf Bild - + TreeLine - Picture File TreeLine Bilddatei + + + Set to &Now + Auf &heute setzen + + + + Clear &Link + Verweis löschen + + + + (Click link target in tree) + Klicke Verweis Ziel im Baum + + + + link + Verweis + dataeditview - + Unique ID - Eindeutige ID + Eindeutige ID exports - + Bookmarks Lesezeichen - + TreeLine - Export HTML TreeLine - Exportiere HTML - + TreeLine - Export Text Titles TreeLine - Exportiere Textüberschriften - + TreeLine - Export Plain Text TreeLine - Exportiere Text - + TreeLine - Export Text Tables TreeLine - Exportiere Text Tabelle - + TreeLine - Export Generic XML TreeLine - Exportiere Generisches XML - + TreeLine - Export TreeLine Subtree TreeLine - Exportiere TreeLine Teilbaum - + TreeLine - Export ODF Text TreeLine - Exportiere ODF Text - + TreeLine - Export HTML Bookmarks TreeLine - Exportiere HTML Lesezeichen - + TreeLine - Export XBEL Bookmarks TreeLine - Exportiere XBEL Lesezeichen - + &HTML &HTML - + &Text &Text - + &XML - &XML + &XML - + &ODF Outline &ODF Struktur - + Book&marks &Lesezeichen - + &Single HTML page &Einzelne HTML Seite - + Single &HTML page with navigation pane Einzelne &HTML Seite mit Navigationsleiste - + Multiple HTML &pages with navigation pane Mehrere HTML &Seiten mit Navigationsleiste - + Multiple HTML &data tables Mehrere HTML &Datentabellen - + &Tabbed title text &Titelzeile mit Tabulator - + &Unformatted output of all text &Unformatierte Ausgabe des gesamten Textes - + Tab &delimited table of children - &Tabulator begrenzte Tabelle der Kinder + &Tabulator begrenzte Tabelle der Kinder - + Generic &XML output - Generische &XML Ausgabe + Generische &XML Ausgabe - + &Subtree in TreeLine format - &Teilbaum in TreeLine Format + &Teilbaum in TreeLine Format - + &HTML format bookmarks &HTML formatierte Lesezeichen - + &XBEL format bookmarks &XBEL formatierte Lesezeichen - + File Export Datei Export - + Choose export format type Ausgabeformat wählen - + Choose export format subtype Ausgabe Unterformat wählen - + Choose export options Ausgabeoptionen wählen - + What to Export Was wird exportiert - + &Entire tree &Gesamter Baum - + Selected &branches &Ausgewählte Teilbäume - + Selected &nodes Ausgewählte &Knoten - + Other Options Andere Optionen - + &Include root node - &Wurzel einschliessen + &Wurzel einschliessen - + &Only open node children &Nur geöffnete Kindknoten - + Include &print header && footer &Kopf- und Fußzeile einschließen - + &Columns &Spalten - + Navigation pane &levels &Stufen der Navigationsleiste + + + Error - export template files not found. +Check your TreeLine installation. + Fehler - Export Vorlage nicht gefunden. Überprüfe deine TreeLine Installation. + + + + Error - cannot link to unsaved TreeLine file. +Save the file and retry. + Fehler - Kann nicht auf nicht gespeicherte TreeLine Datei verweisen. Speichere die Datei und versuche es erneut. + + + + Warning - no relative path from "{0}" to "{1}". +Continue with absolute path? + Warnunh - kein relativer Pfad von "{0}" nach "{1}". + Weitermachen mit absolutem Pfad? + + + + Parent + Eltern + + + + Tree&Line + Tree&Line + + + + &XML (generic) + &XML (generisch) + + + + Live tree view, linked to TreeLine file (for web server) + Live Baum Ansicht, verlinkt mit der TreeLine Datei (für Web Server) + + + + Live tree view, single file (embedded data) + Live Baum Ansicht, eine Datei (Daten eingeschlossen) + + + + &Comma delimited (CSV) table of descendants (level numbers) + &Komma-getrennte Tabelle (CSV) der Nachkommen (Nummern Grad) + + + + Comma &delimited (CSV) table of children (single level) + Komma-&getrennte Tabelle (CSV) der Kinder (einzelner Grad) + + + + Tab &delimited table of children (&single level) + Tab-&getrennte Tabelle der Kinder (&einzelner Grad) + + + + &Old TreeLine (2.0.x) + Altes TreeLine Format (2.0.x) + + + + &TreeLine Subtree + TreeLine Teilbaum + + + + &Include root nodes + einschließlich Wurzel Knoten + + + + Must select nodes prior to export + Sie müssen Knoten vor dem Export auswählen. + fieldformat - + yes/no ja/nein - + true/false wahr/falsch - + T/F W/F - + Y/N Y/N @@ -1364,7 +1524,7 @@ Numbering - + Boolean Boolean @@ -1379,12 +1539,12 @@ Time - + Choice Choice - + AutoChoice AutoChoice @@ -1394,12 +1554,12 @@ Combination - + AutoCombination AutoCombination - + ExternalLink ExternalLink @@ -1409,309 +1569,434 @@ InternalLink - + Picture Picture - + RegularExpression RegularExpression - + Now Now - - Optional Digit# - Optionale Ziffer # + + Optional Digit # + Optionale Ziffer # + + + + Required Digit 0 + Erforderliche Ziffer 0 + + + + Digit or Space (external) <space> + Ziffer oder Leertaste (Extern) <space> + + + + Decimal Point . + Dezimalpunkt . - - Required Digit0 - Erforderliche Ziffer 0 + + Decimal Comma , + Dezimalkomma , - - Digit or Space (external)<space> - Ziffer oder Leertaste (Extern) <space> + + Comma Separator , + Komma Trenner , - - Decimal Point. - Dezimalpunkt . + + Dot Separator . + Punkt Trenner . - - Decimal Comma, - Dezimalkomma , + + Space Separator (internal) <space> + Leertaste Trenner (Intern) <space> - - Comma Separator, - Komma Trenner , + + Optional Sign - + Optionales Vorzeichen - - - Dot Separator. - Punkt Trenner . + + Required Sign + + Erforderliches Vorzeichen + - - Space Separator (internal)<space> - Leertaste Trenner (Intern) <space> + + Exponent (capital) E + Exponent (Groß) E - - Optional Sign- - Optionales Vorzeichen - + + Exponent (small) e + Exponent (Klein) e - - Required Sign+ - Erforderliches Vorzeichen + + + Number 1 + Ziffer 1 - - Exponent (capital)E - Exponent (Groß) E + + Capital Letter A + Großbuchstabe A - - Exponent (small)e - Exponent (Klein) e + + Small Letter a + Kleinbuchstabe a - - Number1 - Ziffer 1 + + Capital Roman Numeral I + Große römische Ziffer I - - Capital LetterA - Großbuchstabe A + + Small Roman Numeral i + Kleine römische Ziffer i - - Small Lettera - Kleinbuchstabe a + + Level Separator / + Stufentrenner / - - Capital Roman NumeralI - Große römische Ziffer I + + Section Separator . + Sektionstrenner . - - Small Roman Numerali - Kleine römische Ziffer i + + "/" Character // + "/" Zeichen // - - Level Separator/ - Stufentrenner / + + "." Character .. + "." Zeichen .. - - Section Separator. - Sektionstrenner . + + Outline Example I../A../1../a)/i) + Gliederungsbeispiel I../A../1../a)/i) - - "/" Character// - "/" Zeichen // + + Section Example 1.1.1.1 + Überschrift Beispiel 1.1.1.1 - - "." Character.. - "." Zeichen .. + + Separator / + Trenner / - - Outline ExampleI../A../1../a)/i) - Gliederungsbeispiel I../A../1../a)/i) + + Example 1/2/3/4 + Beispiel 1/2/3/4 - - Section Example1.1.1.1 - Überschrift Beispiel1.1.1.1 + + Day (1 or 2 digits) d + Tag (1 oder 2 Zeichen) d - - Separator/ - Trenner / + + Day (2 digits) dd + Tag (2 Zeichen) dd - - Example1/2/3/4 - Beispiel 1/2/3/4 + + Weekday Abbreviation ddd + Wochentag Abkürzung ddd - - Day (1 or 2 digits)d - Tag (1 oder 2 Zeichen) d + + Weekday Name dddd + Wochentag Name dddd - - Day (2 digits)dd - Tag (2 Zeichen) dd + + Month (1 or 2 digits) M + Monat (1 oder 2 Zeichen) M - - Weekday Abbreviationddd - Wochentag Abkürzung ddd + + Month (2 digits) MM + Monat (2 Zeichen) MM - - Weekday Namedddd - Wochentag Name dddd + + Month Abbreviation MMM + Monat Abkürzung MMM - - Month (1 or 2 digits)M - Monat (1 oder 2 Zeichen) M + + Month Name MMMM + Monat Name MMMM - - Month (2 digits)MM - Monat (2 Zeichen) MM + + Year (2 digits) yy + Jahr (2 Zeichen) yy - - Month AbbreviationMMM - Monat Abkürzung MMM + + Year (4 digits) yyyy + Jahr (4 Zeichen) yyyy - - Month NameMMMM - Monat Name MMMM + + Hour (0-23, 1 or 2 digits) H + Stunde (0-23, 1 oder 2 Zeichen) H - - Year (2 digits)yy - Jahr (2 Zeichen) yy + + Hour (00-23, 2 digits) HH + Stunde (00-23, 2 Zeichen) HH - - Year (4 digits)yyyy - Jahr (4 Zeichen) yyyy + + Hour (1-12, 1 or 2 digits) h + Stunde (1-12, 1 oder 2 Zeichen) h - - Hour (0-23, 1 or 2 digits)H - Stunde (0-23, 1 oder 2 Zeichen) H + + Hour (01-12, 2 digits) hh + Stunde (01-12, 2 Zeichen) hh - - Hour (00-23, 2 digits)HH - Stunde (00-23, 2 Zeichen) HH + + Minute (1 or 2 digits) m + Minute (1 oder 2 Zeichen) m - - Hour (1-12, 1 or 2 digits)h - Stunde (1-12, 1 oder 2 Zeichen) h + + Minute (2 digits) mm + Minute (2 Zeichen) mm - - Hour (01-12, 2 digits)hh - Stunde (01-12, 2 Zeichen) hh + + Second (1 or 2 digits) s + Sekunde (1 oder 2 Zeichen) s - - Minute (1 or 2 digits)m - Minute (1 oder 2 Zeichen) m + + Second (2 digits) ss + Sekunde (2 Zeichen) ss - - Minute (2 digits)mm - Minute (2 Zeichen) mm + + Milliseconds (1 to 3 digits) z + Millisekunde (1 bis 3 Zeichen) z - - Second (1 or 2 digits)s - Sekunde (1 oder 2 Zeichen) s + + Milliseconds (3 digits) zzz + Millisekunde (3 Zeichen) zzz - - Second (2 digits)ss - Sekunde (2 Zeichen) ss + + AM/PM AP + AM/PM AP - - Milliseconds (1 to 3 digits)z - Millisekunde (1 bis 3 Zeichen) z + + am/pm ap + am/pm ap - - Milliseconds (3 digits)zzz - Millisekunde (3 Zeichen) zzz + + Any Character . + Beliebiges Zeichen . - - AM/PMAP - AM/PM AP + + End of Text $ + Texteende $ - - am/pmap - am/pm ap + + 0 Or More Repetitions * + 0 oder mehr Wiederholungen * - - Any Character. - Beliebiges Zeichen . + + 1 Or More Repetitions + + 1 oder mehr Wiederholungen + - - End of Text$ - Texteende $ + + 0 Or 1 Repetitions ? + 0 oder 1 Wiederholung ? - - 0 Or More Repetitions* - 0 oder mehr Wiederholungen * + + Set of Numbers [0-9] + Menge von Ziffern [0-9] - - 1 Or More Repetitions+ - 1 oder mehr Wiederholungen + + + Lower Case Letters [a-z] + Kleinbuchstabe [a-z] - - 0 Or 1 Repetitions? - 0 oder 1 Wiederholung ? + + Upper Case Letters [A-Z] + Großbuchstabe [A-Z] - - Set of Numbers[0-9] - Menge von Ziffern [0-9] + + Not a Number [^0-9] + Keine Ziffer [^0-9] - - Lower Case Letters[a-z] - Kleinbuchstabe [a-z] + + Or | + Oder | - - Upper Case Letters[A-Z] - Großbuchstabe [A-Z] + + Escape a Special Character \ + Escape für Sonderzeichen \ - - Not a Number[^0-9] - Keine Ziffer [^0-9] + + DateTime + Datum/Zeit + + + + Day (1 or 2 digits) %-d + Tag (1 oder 2 Ziffern) + + + + Day (2 digits) %d + Tag (2 Ziffern) + + + + Weekday Abbreviation %a + Wochentag Abkürzung + + + + Weekday Name %A + Wochentag Name + + + + Month (1 or 2 digits) %-m + Monat (1 oder 2 Ziffern) + + + + Month (2 digits) %m + Monat (2 Ziffern) + + + + Month Abbreviation %b + Monat Abkürzung + + + + Month Name %B + Monat Name + + + + Year (2 digits) %y + Jahr (2 Ziffern) + + + + Year (4 digits) %Y + Jahr (4 Ziffern) + + + + Week Number (0 to 53) %-U + Wochennummer (0 bis 53) + + + + Day of year (1 to 366) %-j + Tag des Jahres (1 bis 366) + + + + Hour (0-23, 1 or 2 digits) %-H + Stunde (0-23, 1 oder 2 Ziffern) + + + + Hour (00-23, 2 digits) %H + Stunde (00-23, 2 Ziffern) + + + + Hour (1-12, 1 or 2 digits) %-I + Stunde (1-12, 1 oder 2 Ziffern) + + + + Hour (01-12, 2 digits) %I + Stunde (01-12, 2 Ziffern) + + + + Minute (1 or 2 digits) %-M + Minute (1 oder 2 Ziffern) + + + + Minute (2 digits) %M + Minute (2 Ziffern) + + + + Second (1 or 2 digits) %-S + Sekunde (1 oder 2 Ziffern) + + + + Second (2 digits) %S + Sekunde (2 Ziffern) + + + + Microseconds (6 digits) %f + Microsekunde (6 Ziffern) + + + + AM/PM %p + AM/PM - - Or| - Oder | + + Comma Separator \, + Komma Trenner \, - - Escape a Special Character\ - Escape für Sonderzeichen \ + + Dot Separator \. + Punkt Trenner \. @@ -1740,95 +2025,110 @@ globalref - + TreeLine Files TreeLine Dateien - + TreeLine Files - Compressed TreeLine Dateien (komprimiert) - + TreeLine Files - Encrypted TreeLine Dateien (verschlüsselt) - + All Files Alle Dateien - + HTML Files HTML Dateien - + Text Files Text Dateien - + XML Files XML Dateien - + ODF Text Files ODF Dateien - + Treepad Files Treepad Dateien - + PDF Files PDF Dateien + + + CSV (Comma Delimited) Files + CSV Datei (Komma-getrennt) + + + + All TreeLine Files + Alle Treeline Dateien + + + + Old TreeLine Files + Alte Treeline Dateien + helpview - + &Back &Zurück - + &Forward &Vorwärts - + &Home &Eigenes Verzeichnis - + Find: Suchen: - + Find &Previous &Rückwärts Suchen - + Find &Next &Vorwärts Suchen - + Text string not found Text nicht gefunden - + Tools Werkzeuge @@ -1836,152 +2136,192 @@ imports - + &Tab indented text, one node per line &Mit Tabulator eingerückter Text, Knoten je Zeile - + Tab delimited text table with header &row Tabulator getrennter Text mit Kopfzeile Plain text, one node per &line (CR delimited) - Text, ein Knoten pro &Zeile (CR Zeilenende) + Text, ein Knoten pro &Zeile (CR Zeilenende) - + Plain text &paragraphs (blank line delimited) Text &Blöcke (Beendet mit Leerzeile) - + Treepad &file (text nodes only) Treepad &Datei (nur Text) - + &Generic XML (non-TreeLine file) Generisches &XML (Keine TreeLine Datei) - + Open &Document (ODF) outline Open &Document (ODF) Gliederung - + &HTML bookmarks (Mozilla Format) &HTML Lesezeichen (Mozilla Format) - + &XML bookmarks (XBEL format) &XML Lesezeichendatei (XBEL-Format) - + FOLDER VERZEICHNIS - + BOOKMARK LESEZEICHEN - + SEPARATOR TRENNER - + Link Verweis - + Text Text - + Import File Import Datei - + Choose Import Method Import Methode auswählen - + Invalid File Ungültige Datei - + "{0}" is not a valid TreeLine file. Use an import filter? "{0}" ist keine TreeLine Datei. Importfilter verwenden? - + TreeLine - Import File TreeLine - Import Datei - + Error - could not read file {0} Fehler - Kann Datei {0} nicht lesen - + Error - improper format in {0} Fehler - ungültiges Format in {0} - + TABLE TABELLE - + Bookmarks Lesezeichen - + Too many entries on Line {0} - + Zu viele Einträge in der Zeile {0} + + + + Plain text, one &node per line (CR delimited) + Nur Text, ein &Knoten pro Zeile (CR getrennt) + + + + Bad CSV format on Line {0} + Falsches CSV Format bei Zeile {0} + + + + Co&mma delimited (CSV) text table with level column && header row + Ko&mma-getrennter (CSV) Text Tabelle mit Stufe Spalte && Kopfzeile Reihe + + + + Comma delimited (CSV) text table &with header row + Ko&mma-getrennter (CSV) Text Tabelle mit Kopfzeile + + + + Other + Andere + + + + Old Tree&Line File (1.x or 2.x) + Alte Tree&Line Datei (1.x oder 2.x) + + + + Invalid level number on line {0} + Falsche Ebene Nummer on line {0} + + + + Invalid level structure + Fehlerhafte Ebenen Struktur matheval - + Illegal "{}" characters Ungültige Zeichen "{}" - + Child references must be combined in a function Verweise auf Kinder müssen in einer Funktion kombiniert werden - + Illegal syntax in equation Ungültige Syntax in Ausdruck - + Illegal function present: {0} Ungültige Funktion: {0} - + Illegal object type or operator: {0} Ungültiger Objekttyp oder Operator: {0} @@ -1989,591 +2329,645 @@ miscdialogs - + &OK &OK - + &Cancel &Abbrechen - + Fields Felder - + File Properties Datei Eigenschaften - + File Storage Datei Speicherung - + &Use file compression Datei&komprimierung verwenden - + Use file &encryption Datei&verschlüsselung verwenden - + Spell Check Rechtschreibprüfung - + Language code or dictionary (optional) Sprachcode oder Wörterbuch (optional) - + Math Fields Mathematisches Feld - + &Treat blank fields as zeros &Leere Felder als Null behandeln - + Encrypted File Password Passwort der verschlüsselten Datei - + Type Password for "{0}": Passwort für {0} eingeben: - + Type Password: Passwort eingeben: - + Re-Type Password: Passwort wiederholen: - + Remember password during this session Passwort für diese Sitzung merken - + Zero-length passwords are not permitted Leere Passwörter sind nicht erlaubt - + Re-typed password did not match Passworte stimmen nicht überein - + Default - Single Line Text Standard - Einzeiliger Text - + &Search Text &Suchtext - + What to Search Nach was wird gesucht - + Full &data Gesamte &Daten - + &Titles only Nur &Titel - + How to Search Wie wird gesucht - + &Key words &Schlüsselworte - + Key full &words &Ganze Worte - + F&ull phrase Ganzer &Satz - + &Regular expression &Regulärer Ausdruck - + Find Suchen - + Find &Previous Suche &Vorherige - + Find &Next Suche &Nächster - + Filter Filter - + &Filter &Filter - + &End Filter Filter &Ende - + &Close &Schließen - + Error - invalid regular expression Fehler - Ungültiger regulärer Ausdruck - + Search string "{0}" not found Suchtext "{0}" nicht gefunden - + Find and Replace Suchen und Ersetzen - + Replacement &Text Ersetzungs&text - + Any &match Jedes &Vorkommen - + Full &words Ganze &Worte - + Re&gular expression Regulärer &Ausdruck - + &Node Type &Knotentyp - + N&ode Fields &Felder - + &Find Next Suche &Nächster - + &Replace &Ersetzen - + Replace &All &Alle Ersetzen - + [All Types] [Alle Typen] - + [All Fields] [Alle Felder] - + Search text "{0}" not found Suchtext "{0}" nicht gefunden - + Error - replacement failed Fehler - Ersetzung nicht möglich - + Replaced {0} matches {0} Stellen ersetzt - + Sort Nodes Konten sortieren - + What to Sort Was wird sortiert - + &Entire tree &Gesamter Baum - + Selected &branches Ausgewählte &Teilbäume - + Selection's childre&n &Kinder der ausgewählten Teilbäume - + Selection's &siblings &Geschwister der ausgewählten Telibäume - + Sort Method Sortiermethode - + &Predefined Key Fields &Vordefinierte Schlüsselfelder - + Node &Titles Knoten&titel - + Sort Direction Sortierrichtung - + &Forward &Vorwärts - + &Reverse &Rückwärts - + &Apply &Anwenden - + Update Node Numbering Nummerierung erneuern - + What to Update Was soll erneuert werden - + &Selection's children &Kinder der Auswahl - + Root Node Wurzelelement - + Include top-level nodes Knoten der ersten Ebene einschliessen - + Handling Nodes without Numbering Fields Behandlung von Knoten ohne Nummerierungsfelder - + &Ignore and skip &Ignorieren - + &Restart numbers for next siblings &Nummerierung für nächste Geschwister neu starten - + Reserve &numbers Nummerierung &umkehren - + TreeLine Numbering TreeLine Nummerierung - + No numbering fields were found in data types Keine Nummerierungsfelder gefunden - + File Menu Datei Menü - + File Datei - + Edit Menu Bearbeiten Menü - + Edit Bearbeiten - + Node Menu Knoten Menü - + Node Knoten - + Data Menu Daten Menü - + Data Daten - + Tools Menu Werkzeug Menü - + Tools Werkzeug - + View Menu Ansicht Menü - + View Ansicht - + Window Menu Fenster Menü - + Window Fenster - + Help Menu Hilfe Menü - + Help Hilfe - + Keyboard Shortcuts Tastaturkürzel - + &Restore Defaults &Standard wiederherstellen - + Key {0} is already used Taste {0} bereits belegt - + Clear &Key Lösche &Taste - + --Separator-- −Separator− - + Customize Toolbars Symbolleisten konfigurieren - + Toolbar &Size Symbolleisten&größe - + Small Icons Kleine Icons - + Large Icons Große Icons - + Toolbar Quantity Anzahl Symbolleisten - + &Toolbars &Symbolleisten - + A&vailable Commands &Verfügbare Funktionen - + Tool&bar Commands &Symbolleisten Funktionen - + Move &Up Nach &oben verschieben - + Move &Down Nach &unten verschieben - + Tree View Font Schriftart für Baumanzeige - + Output View Font Schriftart für Ausgabe - + Editor View Font Schriftart für Editor - + TreeLine Plugins - TreeLine-Plugins + TreeLine-Plugins - + Plugin Modules Loaded - Geladene Plugins + Geladene Plugins - - - nodeformat - - Name - Name + + No menu + Kein Menü - - - optiondefaults - - Monday - Montag + + TreeLine - Serious Error + TreeLine - Schwerer Fehler - - Tuesday - Dienstag + + A serious error has occurred. TreeLine could be in an unstable state. +Recommend saving any file changes under another filename and restart TreeLine. + +The debugging info shown below can be copied and emailed to doug101@bellz.org along with +an explanation of the circumstances. + + Ein schwerer Fehler ist aufgetreten. TreeLine kann sich in einem instabilen Zustand befinden. +Empfehlung, die Datei unter einem anderen Namen zu speichern und TreeLine neu zu starten. + +Das Debug Info unterhalb kann kopiert werden und per Mail an doug101@bellz.org geschickt werden zusammen mit +Erläuterungen, unter welchen Umständen, der Fehler auftrat. - - Wednesday - Mittwoch + + Format Menu + Format Menü + + + + Format + Format + + + + Customize Fonts + Schriftarten Anpassen + + + + &Use system default font + &System Schriftart verwenden + + + + App Default Font + App-Standardschriftart + + + + &Use app default font + &App Schriftart verwenden + + + + nodeformat + + + Name + Name + + + + optiondefaults + + + Monday + Montag + + + + Tuesday + Dienstag + + + + Wednesday + Mittwoch @@ -2596,7 +2990,7 @@ Sonntag - + Startup Condition Programmstart @@ -2608,128 +3002,128 @@ Show child pane in right hand view - Kinder in rechter Ansicht anzeigen + Kinder in rechter Ansicht anzeigen - + Show descendants in output view Unterknoten in der Ansicht anzeigen - + Restore tree view states of recent files Baumansicht von kürzlich geöffneten Dateien wierherstellen - + Restore previous window geometry Fenstereinteilung wiederherstellen - + Features Available Verfügbare Funktionen - + Open files in new windows Öffne Dateien in neuem Fenster - + Click node to rename Klick auf Knoten zum Umbenennen - + Rename new nodes when created Neue Knoten nach dem Anlegen umbenennen - + Tree drag && drop available Verschieben von Bäumen mit der Maus erlauben - + Show icons in the tree view Icons in der Baumansicht anzeigen Show node unique IDs in the Data Edit view - Eindeutige ID im Bearbeiten Fenster anzeigen + Eindeutige ID im Bearbeiten Fenster anzeigen - + Show math fields in the Data Edit view Mathematische Felder im Bearbeiten Fenster anzeigen - + Show numbering fields in the Data Edit view Nummerierungsfelder im Bearbeiten Fenster anzeigen - + Undo Memory Speicher für Wiederherstellungen - + Number of undo levels Anzahl der möglichen Wiederherstellungen - + Auto Save Automatisches Speichern - + Minutes between saves (set to 0 to disable) Minuten zwischen Speichervorgängen (0 für Abschalten) - + Recent Files Zuletzt geöffnete Dateien - + Number of recent files in the file menu Anzahl zuletzt geöffneter Dateien - + Data Editor Formats Formate im Bearbeitungsfenster - + Times Zeitdarstellung - + Dates Datumsdarstellung - + First day of week Erster Wochentag - + Appearance Aussehen - + Child indent offset (in font height units) Einrückung für Kinder (In Schriftart Einheiten) @@ -2737,58 +3131,78 @@ Printer Settings - Druckeinstellungen + Druckeinstellungen - + Spell Check - Rechtschreibprüfung + Rechtschreibprüfung - + Fonts - Schriftarten + Schriftarten Window Geometry - Fensterdarstellung + Fensterdarstellung Toolbar Geometry - Funktionsleiste Darstellung + Funktionsleiste Darstellung Toolbar Commands - Funktionsleiste Kommandos + Funktionsleiste Kommandos + + + + Show breadcrumb ancestor view + Zeige Breadcrumb Vorfahren Ansicht + + + + Show child pane in right hand views + Zeige Kind Ausschnitt in der rechten Ansicht + + + + Remove inaccessible recent file entries + Lösche alle jüngsten Datei-Einträge, die nicht geöffnet werden können + + + + Activate data editors on mouse hover + Aktiviere den Daten Editor bei schwebender Maus options - + Choose configuration file location Ablage Konfigurationsfile - + User's home directory (recommended) Benutzerverzeichnis (empfohlen) - + Program directory (for portable use) Programmverzeichnis (für portablen Einsatz) - + &OK &OK - + &Cancel &Abbrechen @@ -2796,555 +3210,596 @@ printdata - + Error initializing printer Fehler beim Initialisieren des Druckers - + TreeLine - Export PDF TreeLine - PDF Exportieren + + + Warning: Page size and margin settings unsupported on current printer. +Save page adjustments? + Warnung: Seiteneinstellung und Randeinstellung werden vom derzeitigen Drucker nicht unterstützt. Sollen die Einstellungen berichtigt werden? + + + + Warning: Page size setting unsupported on current printer. +Save adjustment? + Warnung: Seiteneinstellung werden vom derzeitigen Drucker nicht unterstützt. Sollen die Einstellungen berichtigt werden + + + + Warning: Margin settings unsupported on current printer. +Save adjustments? + Warnung: Randeinstellung werden vom derzeitigen Drucker nicht unterstützt. Sollen die Einstellungen berichtigt werden + printdialogs - + Print Preview Druckvorschau - + &Print... &Drucken... - + &General Options Allgemeine &Einstellungen - + &Font Selection Schrift&auswahl - + &Header/Footer &Kopf-/Fußzeile - + Print Pre&view... Druck&vorschau... - + &OK &OK - + &Cancel &Abbrechen - + What to print Was drucken - + &Entire tree Gesamter Baum - + Selected &branches Ausgewählte Teilbäume - + Selected &nodes Ausgewählte Knoten - + Features Features - + &Include root node Wurzelelement hinzunehmen - + &Keep first child with parent Das erste Unterelement mit dem Elternelement zusammen anzeigen - + Letter (8.5 x 11 in.) Letter (8.5 × 11 Inch) - + Legal (8.5 x 14 in.) Legal (8.5 × 14 Inch) - + Tabloid (11 x 17 in.) Tabloid (11 × 17 Inch) - + A3 (279 x 420 mm) A3 (279 × 420 mm) - + A4 (210 x 297 mm) A4 (210 × 297 mm) - + A5 (148 x 210 mm) A5 (148 × 210 mm) - + Paper &Size Papiergröße - + Orientation Orientierung - + &Units &Einheiten - + Columns Spalten - + &Number of columns Spaltenanzahl - + Default Font standard Schriftart - + Select Font Schriftart auswählen - + &Font &Schriftart - + Font st&yle Schrifts&til - + Sample Beispiel - + &Header Left Kopfzeile &Links - + Header C&enter Kopfzeile Mitt&e - + Footer &Left &Fußzeile Links - + Footer Ce&nter Fußzeile M&itte - + Fiel&ds Felder - + Header and Footer Kopf- und Fußzeile - + Extra Text Extratext - + &Prefix Präfi&x - + Format &Help &Hilfe zu den Formaten - + Fit Width Breite anpassen - + Fit Page Seite anpassen - + Zoom In Vergrößern - + Zoom Out Verkleinern - + Previous Page Vorherige Seite - + Next Page Nächste Seite - + Single Page Einzelne Seite - + Facing Pages Gegenüberliegende Seiten - + Print Setup Druckeinstellungen - + Print Drucken - + Printing Setup Druckeinstellungen - + Page &Setup Seiteneinstellungen - + Included Nodes Berücksichtigte Knoten - + Onl&y open node children Nur Kinder von geöffneten Knoten - + &Draw lines to children Linien zu den Kindern zeichnen - + Indent Einrücken - + Indent Offse&t (line height units) Abstand beim Einrücken (Einheit Zeilenhöhe) - + Custom Size Benutzerdefinierte Größe - + Inches (in) Inch (in) - + Millimeters (mm) Millimeter (mm) - + Centimeters (cm) Zentimeter (cm) - + &Width: &Breite: - + Height: &Höhe: - + Portra&it &Hochformat - + Lan&dscape &Querformat - + Margins Ränder - + &Left: &Links: - + &Top: &Oben: - + &Right: &Rechts: - + &Bottom: &Unten: - + He&ader: &Kopfzeile: - + Foot&er: &Fußzeile: - + Space between colu&mns Abstand zwischen &Spalten - + &Use system default font - &System Schriftart verwenden + &System Schriftart verwenden - + &Use TreeLine output view font &TreeLine Ausgabe Schriftart verwenden - + Si&ze &Größe - + AaBbCcDdEeFfGg...TtUuVvWvXxYyZz AaBbCcDcEeFfGg...TtUuVvWwXxYyZz - + Header &Right Kopfzeile &Rechts - + Footer Righ&t Fusszeile &Rechts - + Field For&mat Feldf&ormat - + Field Format for "{0}" Feldformat für "{0}" - + Output &Format Ausgabeformat - + &Suffix &Suffix + + + Error: Page size or margins are invalid + Fehler: Seitengröße oder Seitenränder sind ungültig + + + + TreeLine PDF Printer + TreeLine PDF Drucker + + + + Select &Printer + Wähle &Drucker + recentfiles Recent Files - Zuletzt geöffnete Dateien + Zuletzt geöffnete Dateien spellcheck - + Could not find either aspell.exe, ispell.exe or hunspell.exe Browse for location? Kann weder aspell.exe, ispell.exe oder hunspell.exe finden. Speicherort suchen? - + Spell Check Error Fehler in der Rechtschreibprüfung - + Locate aspell.exe, ipsell.exe or hunspell.exe Suche aspell.exe, ispell.exe oder hunspell.exe - + Program (*.exe) Programm (*.exe) - + TreeLine Spell Check Error Make sure aspell, ispell or hunspell is installed Fehler bei der Rechtschreibprüfung. Stellen Sie sicher, dass aspell, ispell oder hunspell installiert ist - + Finished checking the branch Continue from the root branch? - Der Teilbaum wurde überprüft. + Der Teilbaum wurde überprüft. Überprüfung im Hauptbaum fortsetzen? - + TreeLine Spell Check TreeLine Rechtschreibprüfung - + Finished spell checking Rechtschreibprüfung beendet - + Spell Check Rechtschreibprüfung - + Not in Dictionary Nicht im Wörterbuch - + Word: Wort: - + Context: Kontext: - + Suggestions Vorschläge - + Ignor&e &Ignorieren - + &Ignore All &Alles Ignorieren - + &Add &Hinzufügen - + Add &Lowercase Hinzufügen in &Kleinbuchstaben - + &Replace &Ersetzen - + Re&place All Alles E&rsetzen - + &Cancel &Abbrechen + + + Finished checking the branch +Continue from the top? + Rechtschreibprüfung für den Zweig beendet. +Wieder von oben anfangen? + + treeformats - + DEFAULT DEFAULT @@ -3352,566 +3807,682 @@ treelocalcontrol - + Warning: duplicate Unique IDs found. - Warnung: Doppelt eindeutige IDs gefunden. + Warnung: Doppelt eindeutige IDs gefunden. - + Many Unique IDs were re-assigned. - Einige eindeutige IDs wurden neu zugewiesen. + Einige eindeutige IDs wurden neu zugewiesen. - + The following IDs were re-assigned: - - Die folgenden IDs wurden neu zugewiesen: - + + Die folgenden IDs wurden neu zugewiesen: + - + Internal link targets could be affected. - + Interne Verweise können betroffen sein. - + Error - could not delete backup file {} Fehler: Kann Sicherheitskopie {0} nicht löschen - + Save changes to {}? Änderungen an {0} abspeichern? - + Save changes? Änderungen abspeichern? - + &Save S&peichern - + Save File Datei speichern - + Save the current file Aktuelle Datei speichern - + Save &As... Speichern &unter... - + Save the file with a new name Die Datei unter einem neuen Namen speichern - + &Export... &Exportieren... - + Export the file in various other formats Die Datei in unterschiedlichen Formaten exportieren - + Prop&erties... &Eigenschaften... - + Set file parameters like compression and encryption Datei Eigenschaften wie Komprimierung oder Verschlüsselung setzen - + P&rint Setup... &Drucken einrichten... - + Set margins, page size and other printing options Ränder, Seitengröße und andere Druckeinstellungen - + Print Pre&view... Druck&vorschau... - + Show a preview of printing results Vorschau der Druckergebnisse anzeigen - + &Print... &Drucken... - + Print tree output based on current options Baum basierend auf aktuellen Einstellungen drucken - + Print &to PDF... Nach &PDF drucken... - + Export to PDF with current printing options Nach PDF mit aktuellen Druckoptionen exportieren - + &Undo &Rückgängig - + Undo the previous action Die letzte Aktion rückgängig machen - + &Redo W&iederherstellen - + Redo the previous undo Die letzte Aktion wiederherstellen - + Cu&t &Ausschneiden - + Cut the branch or text to the clipboard Den Teilbaum oder Text ausschneiden und in die Zwischenablage legen - + &Copy &Kopieren - + Copy the branch or text to the clipboard Den Teilbaum oder den Text in die Zwischenablage kopieren - + &Paste Ein&fügen - + Paste nodes or text from the clipboard Knoten oder Text von der Zwischenablage einfügen - + P&aste Plain Text - Nur &Text einfügen + Nur &Text einfügen - + Paste non-formatted text from the clipboard Unformatierten Text von der Zwischenablage einfügen - + &Bold Font &Fettschrift - + Set the current or selected font to bold Die aktuelle oder ausgewählte Schriftart auf "fett" setzen - + &Italic Font &Kursivschrift - + Set the current or selected font to italic Die aktuelle oder ausgewählte Schriftart auf "kursiv" setzen - + U&nderline Font &Unterstreichen - + Set the current or selected font to underline Die aktuelle oder ausgewählte Schriftart auf "unterstreichen" setzen - + &Font Size &Schriftart Größe - + Set size of the current or selected text Die Größe der aktuellen oder selektierten Schriftart setzen - + Small Klein - + Default Standard - + Large Groß - + Larger Größer - + Largest Am Größten - + Set Font Size Schriftart Größe setzen - + Font C&olor... Schriftart &Farbe... - + Set the color of the current or selected text Die Farbe der aktuellen oder ausgewählten Schriftart setzen - + &External Link... &Externer Verweis... - + Add or modify an extrnal web link Einen externen Verweis hinzufügen oder ändern - + Internal &Link... &Interner Verweis... - + Add or modify an internal node link Internen Verweis ändern oder hinzufügen - + Clear For&matting &Formatierung entfernen - + Clear current or selected text formatting Die Formatierung am aktuellen oder selektierten Text entfernen - + &Rename &Umbenennen - + Rename the current tree entry title Den Titel des aktuellen Knotens ändern - + Insert Sibling &Before &Vor ausgewähltem Element einfügen - + Insert new sibling before selection Ein neues Element vor dem ausgewählten Element einfügen - + Insert Sibling &After &Nach ausgewähltem Element einfügen - + Insert new sibling after selection Ein neues Element nach dem ausgewählten Element einfügen - + Add &Child &Kind hinzufügen - + Add new child to selected parent Neues Kind zu aktuellem Knoten hinzufügen - + &Delete Node Knoten &löschen - + Delete the selected nodes Die ausgewählten Knoten löschen - + &Indent Node Knoten &einrücken - + Indent the selected nodes Die ausgewählten Knoten einrücken - + &Unindent Node Knoten &ausrücken - + Unindent the selected nodes Die ausgewählten Knoten "ausrücken" (um eine Ebene nach links verschieben) - + &Move Up Nach &oben verschieben - + Move the selected nodes up Die ausgewählten Knoten nach oben verschieben - + M&ove Down Nach &unten verschieben - + Move the selected nodes down Die ausgewählten Knoten nach unten verschieben - + Move &First Zum Anfang verschieben - + Move the selected nodes to be the first children Das ausgewählte Knoten als erstes Unterelement setzen - + Move &Last Zum Ende verschieben - + Move the selected nodes to be the last children Das ausgewählte Knoten als letztes Unterelement setzen - + &Set Node Type Knoten&typ zuweisen - + Set the node type for selected nodes Den Typ für den ausgewählten Knoten setzen - + Set Node Type Knotentyp zuweisen - + Copy Types from &File... Typen aus &Datei kopieren... - + Copy the configuration from another TreeLine file Konfiguration aus einer anderen TreeLine-Datei übernehmen - + Flatten &by Category &Abflachen nach Kategorie - + Collapse descendants by merging fields Kinder zusammenfassen und Felder vereinen - + Add Category &Level... Kategorie &Hierarchieebene einfügen... - + Insert category nodes above children Kategorieebene oberhalb der Unterknoten einfügen - + Flatten b&y Link... - Abflachen nach &Verweis... + Abflachen nach &Verweis... - + Collapse descendants and insert parent links - Kinder zusammenfassen und Verweis zu Eltern einfügen + Kinder zusammenfassen und Verweis zu Eltern einfügen - + &Arrange by Link... - &Anordnen nach Verweisen... + &Anordnen nach Verweisen... - + Arrange descendants using parent links - Kinder anordnen nach Elternverweisen + Kinder anordnen nach Elternverweisen - + &Spell Check... &Rechtschreibprüfung... - + Spell check the tree')s text data - Rechtschreibprüfung für Text + Rechtschreibprüfung für Text - + &New Window &Neues Fenster - + Open a new window for the same file Neues Fenster für die gleiche Datei öffnen - + Error - could not write to {} Fehler - konnte nicht nach {0} schreiben - + TreeLine - Save As TreeLine - Speichern unter - + Error - could not write to file Fehler - konnte nicht auf Datei schreiben - + TreeLine - Open Configuration File TreeLine - Konfigurationsdatei schreiben - + Error - could not read file {0} Fehler - Konnte Datei {0} nicht lesen - + Cannot expand without common fields Ohne gemeinsame Felder kann der Baum nicht expandiert werden - + Category Fields Kategoriefelder - + Select fields for new level Felder für die neue Ebene auswählen - + Flatten by Link - Abflachen nach Verweisen + Abflachen nach Verweisen - + Enter a new field name for parent links - Neuer Feldname für Verweise auf Eltern + Neuer Feldname für Verweise auf Eltern - + Link Field - Verweisfeld + Verweisfeld - + Select field with links to parents - Feld mit Verweis auf Eltern auswählen + Feld mit Verweis auf Eltern auswählen - + File saved Datei gespeichert + + + Pa&ste Plain Text + Nu&r Text einfügen + + + + Paste C&hild + Kind Knoten einfügen + + + + Paste a child node from the clipboard + Kind Knoten von der Zwischenablage einfügen + + + + Paste Sibling &Before + Füge Geschwister Knoten &davor + + + + Paste a sibling before selection + Füge Geschwister Knoten vor Auswahl ein + + + + Paste Sibling &After + Füge Geschwister Knoten d&ahinter + + + + Paste a sibling after selection + Füge Geschwister Knoten hinter Auswahl ein + + + + Paste Cl&oned Child + Füge geklonten Kind Knoten ein + + + + Paste a child clone from the clipboard + Füge geklonten Kind Knoten von der Zwischenablage ein + + + + Paste Clo&ned Sibling Before + Füge geklo& Geschwwister Knoten davor ein + + + + Paste a sibling clone before selection + Füge Geschwister Knoten vor der Auswahl ein + + + + Paste Clone&d Sibling After + Füge Geschwister Knoten &danach ein + + + + Paste a sibling clone after selection + Füge Geschwister Knoten nach der Auswahl ein + + + + Clone All &Matched Nodes + Klone alle &passenden Knoten + + + + Convert all matching nodes into clones + Konvertiere alle passenden Knoten in Klone + + + + &Detach Clones + Trenne Klone + + + + Detach all cloned nodes in current branches + Trenne alle Klone Knoten im aktuellen Zweig + + + + S&wap Category Levels + Tausche Kategorie Ebene + + + + Swap child and grandchild category nodes + Tausche Kind und Enkel Kategorie Knoten + + + + Converted {0} branches into clones + {0} Zweige in Klone konvertiert + + + + No identical nodes found + Keine identischen Knoten gefunden + + + + Warning - file corruption! +Skipped bad child references in the following nodes: + + + + + Spell check the tree's text data + Rechtschreibprüfung für Text + treemaincontrol - + Warning: Could not create local socket Warnung - Socket kann nicht geöffnet werden @@ -3921,361 +4492,381 @@ Fehler - Kann Konfigurationsdatei nicht auf {} schreiben - + Error - could not read file {0} Fehler - Kann Datei {0} nicht lesen - + Error - {0} is not a valid TreeLine file - Fehler - {0} ist keine gültige TreeLine Datei + Fehler - {0} ist keine gültige TreeLine Datei - + Backup file "{}" exists. A previous session may have crashed Sicherheitskopie {} existiert. Eine vorherige Sitzung ist möglicherweise abgestürzt - + &Restore Backup Sicherungskopie &wiederherstellen - + &Delete Backup Sicherungskopie &löschen - + &Cancel File Open Datei öffnen &abbrechen - + Error - could not rename "{0}" to "{1}" Fehler - Kann {0} nicht nach {1} umbenennen - + Error - could not remove backup file {} Fehler - Kann Sicherheitskopie {} nicht entfernen - + Warning - could not load plugin module {0} - Warnung - Kann Plugin {0} nicht laden + Warnung - Kann Plugin {0} nicht laden - + &New... &Neu... - + New File Neue Datei - + Start a new file Eine neue Datei öffnen - + &Open... &Öffnen... - + Open File Datei öffnen - + Open a file from disk Datei von Datenträger öffnen - + Open Sa&mple... &Beispiele... - + Open Sample Beispiel öffnen - + Open a sample file Eine Beispieldatei öffnen - + &Import... &Importieren... - + Open a non-TreeLine file Eine nicht TreeLine Datei laden - + &Quit &Beenden - + Exit the application Die Anwendung beenden - + &Select All &Alles auswählen - + Select all text in an editor Gesamten Text im Editor auswählen - + &Configure Data Types... &Datentypen konfigurieren... - + Modify data types, fields & output lines Konfiguriere Datentypen, Felder und Ausgabezeilen - + Sor&t Nodes... Knoten&sortierung... - + Define node sort operations Knotensortierung definieren - + Update &Numbering... &Nummerierung anpassen... - + Update node numbering fields Die Nummerierung anpassen - + &Find Text... &Suche Text... - + Find text in node titles & data Text in Knoten suchen - + &Conditional Find... &Bedingtes Suchen... - + Use field conditions to find nodes Feldbasierte Bedingungen verwenden um Knoten zu finden - + Find and &Replace... Suchen und &Ersetzen... - + Replace text strings in node data Text in Knoten ersetzen - + &Text Filter... &Textfilter... - + Filter nodes to only show text matches Knoten filtern auf übereinstimmenden Text - + C&onditional Filter... &Bedingter Filter... - + Use field conditions to filter nodes Feldbasierte Bedingungen verwenden - + &General Options... Allgemeine &Einstellungen... - + Set user preferences for all files Benutzereinstellungen für alle Dateien - + Set &Keyboard Shortcuts... &Tastaturkürzel konfigurieren... - + Customize keyboard commands Tastaturkürzel anpassen - + C&ustomize Toolbars... &Symbolleiste anpassen... - + Customize toolbar buttons Symbolleiste Aktionen anpassen - + Customize Fo&nts... &Schriftarten anpassen... - + Customize fonts in various views Schriftarten der Ansichten anpassen - + &Basic Usage... &Grundlegende Verwendung... - + Display basic usage instructions Grundlegende Anleitungen anzeigen - + &Full Documentation... &Umfassende Dokumentation... - + Open a TreeLine file with full documentation TreeLine Datei mit umfassender Dokumentation anzeigen - + &About TreeLine... Über &TreeLine... - + Display version info about this program Versionsinformationen zum Programm anzeigen - + &About &Plugins... - Über &Plugins... + Über &Plugins... - + Show loaded plugin modules - Die geladenen Plugins anzeigen (Plugins sind TreeLine-Erweiterungen) + Die geladenen Plugins anzeigen (Plugins sind TreeLine-Erweiterungen) - + &Select Template &Vorlagenauswahl - + TreeLine - Open File TreeLine - Datei Öffnen - + Open Sample File Beispieldatei öffnen - + &Select Sample &Beispiel auswählen - + Conditional Find Bedingtes suchen - + Conditional Filter Bedingter Filter - + ToolsFilterCondition - ToolsFilterCondition + ToolsFilterCondition - + General Options Allgemeine Einstellungen - + Error - basic help file not found Fehler - Grundlegende Hilfedatei nicht gefunden - + TreeLine Basic Usage TreeLine grundlegende Verwendung - + Error - documentation file not found Fehler - Dokumentation nicht gefunden - + TreeLine, Version {0} by {1} - TreeLine, Version {0} von {1} + TreeLine, Version {0} von {1} + + + + Error - invalid TreeLine file {0} + Fehler - ungültige TreeLine Datei {0} + + + + TreeLine version {0} + TreeLine Version {0} + + + + written by {0} + geschrieben von + + + + Library versions: + Bibliothek Versionen: treemodel - + Main - Hauptprogramm + Hauptprogramm treenode - + New Neu @@ -4285,31 +4876,64 @@ Invalid XML file - Ungültige XML Datei + Ungültige XML Datei Bad elememnt - not a valid TreeLine file - Ungültiger Knoten - keine TreeLine Datei + Ungültiger Knoten - keine TreeLine Datei + + + + treestructure + + + Main + Hauptprogramm treeview - + Filtering by "{0}", found {1} nodes Filtern nach {0}, {1} Knoten gefunden - + Conditional filtering, found {0} nodes Bedingtes Filter hat {0} Knoten gefunden + + + Search for: + Suche nach: + + + + Search for: {0} + Suche nach: {0} + + + + Search for: {0} (not found) + Suche nach: {0} (nicht gefunden) + + + + Next: {0} + Nächste: {0} + + + + Next: {0} (not found) + Nächste: {0} (nicht gefunden) + treewindow - + Data Output Datenansicht @@ -4319,149 +4943,184 @@ Datenbearbeitung - + Title List Überschriftsliste - + &Expand Full Branch Teilbaum vollständig &expandieren - + Expand all children of the selected nodes Alle Kinder der selektierten Knoten expandieren - + &Collapse Full Branch Teilbaum vollständig &einklappen - + Collapse all children of the selected nodes Alle Kinder der selektierten Knoten einklappen - + &Previous Selection &Vorherige Auswahl - + Return to the previous tree selection Zu der vorherigen Selektion zurückkehren - + &Next Selection &Nachfolgende Auswahl - + Go to the next tree selection in history Die folgende Selektion wieder aktivieren - + Show Data &Output &Ansicht anzeigen - + Show data output in right view Die Daten-Ansicht in der rechten Fensterhälfte anzeigen - + Show Data &Editor Edit&or anzeigen - + Show data editor in right view Den Editor in der rechten Fensterhälfte anzeigen - + Show &Title List &Überschriftsliste anzeigen - + Show title list in right view Die Überschriften-Liste in der rechten Fensterhälfte anzeigen - + &Show Child Pane &Kinder mit anzeigen - + Toggle showing right-hand child views Die Kinder in der Ausgabeansicht mit anzeigen - + Show Output &Descedants - Kinder in der Ausgabe mit anzeigen + Kinder in der Ausgabe mit anzeigen - + Toggle showing output view indented descendants Die Kunde in der Ausgabeansicht eingerückt anzeigen - + &Close Window Fenster &schließen - + Close this window Dieses Fenster schließen - + &File &Datei - + &Edit &Bearbeiten - + &Node &Knoten - + &Data Da&ten - + &Tools &Extras - + &View &Ansicht - + &Window &Fenster - + &Help &Hilfe + + + Start Incremental Search + Starte inkrementelle Suche + + + + Next Incremental Search + Nächste inkrementelle Suche + + + + Previous Incremental Search + Vorherige inkrementelle Suche + + + + Show &Breadcrumb View + Zeige &Breadcrumb Ansicht + + + + Toggle showing breadcrumb ancestor view + Umschalten zu Breadcrumb Vorfahren Ansicht + + + + Show Output &Descendants + Zeige Ausgabe Abkömmlinge + + + + Fo&rmat + Fo&rmat + diff -Nru treeline-2.0.2/i18n/translations/treeline_es.ts treeline-3.0.1/i18n/translations/treeline_es.ts --- treeline-2.0.2/i18n/translations/treeline_es.ts 2018-04-08 20:12:57.000000000 +0000 +++ treeline-3.0.1/i18n/translations/treeline_es.ts 2018-10-17 14:52:24.000000000 +0000 @@ -1,1717 +1,1806 @@ - + + conditional - + starts with - comienza con + comienza con - + ends with - termina con + termina con - + contains - contiene + contiene - + True - Verdadero + Verdadero - + False - Falso + Falso - + and - y + y - + or - o + o - + [All Types] - + [Todos los tipos] - + Node Type - + Tipo de nodo - + &Add New Rule - &Añadir regla nueva + Añadir &Nueva Regla - + &Remove Rule - &Eliminar Regla + &Eliminar Regla - + &OK - &Aceptar + &OK - + &Cancel - &Cancelar - - - - R&etrieve Rules... - + &Cancelar - - &Save Rules... - - - - + Find &Previous - Buscar &Anterior + B&uscar Anterior - + Find &Next - Buscar &Siguiente + Buscar &Siguiente - + &Filter - + &Filtro - + &End Filter - + &Terminar filtro - + &Close - &Cerrar + &Cerrar - - Save Rules - + + No conditional matches were found + Coincidencias condicionales no encontradas - - Enter a descriptive name - + + Rule {0} + Regla {0} - - No conditional matches were found - + + Saved Rules + Reglas Guardadas - - Rule {0} - + + Name: + Nombre: - - Retrieve Rules - + + &Load + Car&gar - - Select rule set to retrieve: - + + &Save + Guar&dar - - Remove Rule - + + &Delete + &Borrar configdialog - + Configure Data Types - + Configurar Tipos de Datos - + T&ype List - + Lista de &Tipos - + Field &List - + Lista de &Campos - + &Field Config - + Configuración de &Campos - + &Show Advanced - + &Mostrar Avanzado - + &OK - &Aceptar + &OK - + &Apply - + &Aplicar - + &Reset - + &Reiniciar - + &Cancel - &Cancelar + &Cancelar - + Add or Remove Data Types - + Añadir o Quitar Datos de Tipos - + &New Type... - + &Nuevo Tipo... - + Co&py Type... - + Co&piar Tipo... - + &Delete Type - &Eliminar tipo + &Eliminar tipo - + Add Type - Añadir tipo + Añadir tipo - + Enter new type name: - Introducir el nombre del nuevo tipo: + Establecer nombre del nuevo tipo: - + Rename Type - Renombrar tipo + Renombrar tipo - + Cannot delete data type being used by nodes - No se puede eliminar un tipo de datos que esté siendo usado por algún nodo + No se puede eliminar un tipo de datos que esté siendo usado por algún nodo - + &Data Type - T&ipo de datos + Tipo de &Datos - + Icon - + Icono - + Change &Icon - Cambiar &icono + Cambiar &Icono - + Derived from &Generic Type - + Derivado del Tipo &Genérico - + Automatic Types - Tipos automáticos + Tipos Automáticos - + Modify Co&nditional Types - + Modificar Tipos Co&ndicionales - + Create Co&nditional Types - + Crear Tipos Co&ndicionales - + Set Types Conditionally - Configurar tipos condicionalmente + Configurar Tipos Condicionalmente - + Name - Nombre + Nombre - + Type - Tipo + Tipo - + Move &Up - Mover a&rriba + Mover A&rriba - + Move Do&wn - Mover a&bajo + Mover Aba&jo - + &New Field... - &Nuevo campo... + &Nuevo Campo... - + Add Field - Añadir campo + Añadir Campo - + Enter new field name: - Introduzca el nombre del nuevo campo: + Introduzca el nombre del nuevo campo: - + Rename Field - Renombrar campo + Renombrar Campo - + F&ield - + Cam&po Format &Help - + Ayuda sobre &Formato Extra Text - Texto adicional + Texto Adicional &Prefix - + P&refijo Suffi&x - + Sufi&jo Editor Height - Altura del editor + Altura del editor Num&ber of text lines - + Número de líneas de te&xto - + File Info Reference - Referencia de información de archivo + Referencia de información de archivo - + Parent Reference - Referencia del padre + Referencia Padre - + Child Reference - Referencia de hijo + Referencia Hija - + Child Count - + Recuento de Hijos - + F&ield List - + &Lista de Campos - + Other Field References - + Otras Referencias de Campos - + Copy Type - Copiar tipo + Copiar Tipo - + Set Data Type Icon - Configurar icono de tipo de datos + Establecer el Icono del Tipo de Datos - + Clear &Select - Borrar &selección + Borrar &Selección - + Typ&e Config - + Configurar &Tipo - + O&utput - + &Salida de Datos - + &Hide Advanced - + &Ocultar Avanzado - + Error - circular reference in math field equations - + Error - referencia circular en ecuaciones de campo matemático - + Rena&me Type... - + Renom&brar Tipo... - + &Derive from original - + &Derivado del original - + Rename from {} to: - + Renombrar de {} a: - + [None] no type set - [Nada] + [Nada] - + Default Child &Type - + Tipo Hi&jo por Defecto - + Output Options - + Opciones de Salida - + Add &blank lines between nodes - + Agregar línea en &blanco entre nodos - + Allow &HTML rich text in format - + Permitir &HTML con formato de texto enriquecido - + Add text bullet&s - + Agregar &viñetas al texto - + Use a table for field &data - + Mostrar datos de campo en tab&la - + Combination && Child List Output &Separator - - - - - Uni&que ID Reference Field - + Combinación && &separador lista de hijos de Salida - + None - Nada + Nada - + Modify &Field List - + Modificar &Lista de Campos - + Sort Key - + Clave de Clasificación - + Move U&p - + Mo&ver Arriba - + Rena&me Field... - + Renombrar Cam&po... - + Dele&te Field - + &Borrar Campo - + Sort &Keys... - + Ord&enar Claves... - + fwd - + Adelante - + rev - + Atrás - + &Field Type - + Tip&o de Campo Outpu&t Format - + Formato de Sa&lida Default &Value for New Nodes - + &Valor por Defecto para Nuevos Nodos Math Equation - + Ecuación matemática Define Equation - + Definir Ecuación - + &Title Format - + &Formato de Título - + Out&put Format - + &Formato de Salida - + Reference Le&vel - + Niv&el de Referencia - + Refere&nce Type - + Tipo de Refere&ncia - + The name cannot be empty - + El nombre no puede estar vacío - + The name must start with a letter - + El nombre debe comenzar con una letra - + The name cannot start with "xml" - + El nombre no puede comenzar con "xml" - + The name cannot contain spaces - + El nombre no puede contener espacios - + The following characters are not allowed: {} - + Los siguientes caracteres no están permitidos: {} - + The name was already used - + El nombre ya está en uso - + forward - + adelante - + reverse - + atrás - + Sort Key Fields - + Ordenar Campos Clave - + Available &Fields - + Campos &Disponibles - + &Sort Criteria - + C&riterio de Ordenación - + Field - + Campo - + Direction - Dirección + Dirección - + &Move Down - + Mover Aba&jo - + Flip &Direction - + Cam&biar Dirección - + Self Reference - + Autoreferencia - + Root Reference - - - - - Numeric Result - + Referencia Raíz - + Date Result - + Resultado de Fecha - + Time Result - + Resultado de Tiempo - + add - + sumar - + subtract - + restar - + multiply - + multiplicar - + divide - + dividir - + floor divide - + división por redondeo - + modulus - + módulo - + power - + potencia - + sum of items - + suma de ítems - + maximum - + máximo - + minimum - + mínimo - + average - + promedio - + absolute value - + valor absoluto - + square root - + raíz cuadrada - + natural logarithm - + logaritmo natural - + base-10 logarithm - + logaritmo en base 10 - + factorial - + factorial - + round to num digits - + redondear a número de dígitos - + lower integer - + entero menor - + higher integer - + entero mayor - + truncated integer - + entero truncado - + floating point - + punto flotante - + sine of radians - + seno de radián - + cosine of radians - + coseno de radián - + tangent of radians - + Tangente de Radián - + arc sine - + arcoseno - + arc cosine - + arcocoseno - + arc tangent - + arcotangente - + radians to degrees - + radianes a grados - + degrees to radians - + grados a radianes - + pi constant - + número pi (constante) - + natural log constant - + constante logaritmo natural - + Define Math Field Equation - + Definir el Campo de Ecuación Matemática - + Field References - + Referencias de Campo - + Reference &Level - + Referencia de &Nivel - + Reference &Type - + Referencia de &Tipo - + Available &Field List - + Lista de &Campos Disponible - + &Result Type - + &Resultado Tipo - + Description - + Descripción - + &Equation - + &Ecuación - + Equation error: {} - + Erron en ecuación: {} - + Boolean Result - + Resultado Booleano - + Text Result - + Texto Resultante - + Arithmetic Operators - + Operadores Aritméticos - + Comparison Operators - + Operadores Comparativos - + Text Operators - + Operadores de Texto - + equal to - + igual a - + less than - + menor que - + greater than - + mayor que - + less than or equal to - + menor o igual que - + greater than or equal to - + mayor o igual que - + not equal to - + distinto a - + true value, condition, false value - + valor verdadero, condición, valor falso - + true if 1st text arg starts with 2nd arg - + verdadero si el argumentdo del primer texto comienza con un segundo argumento - + true if 1st text arg ends with 2nd arg - + verdadero si el argumentdo del primer texto termina con un segundo argumento - + true if 1st text arg contains 2nd arg - + verdadero si el argumentdo del primer texto contiene un segundo argumento - + concatenate text - + concatenar texto - + join text using 1st arg as separator - + unir texto usando como primer argumento un separador - + convert text to upper case - + convertir texto a mayúsculas - + convert text to lower case - + convertir texto a minúsculas - + in 1st arg, replace 2nd arg with 3rd arg - + en primer argumento, reemplazar segundo argumento con tercer argumento - + Operations - + Operaciones - + O&perator Type - + Tipo de O&perador - + Oper&ator List - + Lista de Oper&adores - + logical and - + lógico y - + logical or - + lógico o + + + + Output HTML + Salida HTML + + + + Evaluate &HTML tags + Evaluar etiquetas &HTML + + + + Child Type Limits + Límites de los Tipos Hijo + + + + [All Types Available] + [Todos los Tipos Disponibles] + + + + &Select All + &Seleccionar Todo + + + + Select &None + Seleccionar &Ninguno + + + + Number Result + Resultado Numérico dataeditors - + Today's &Date - + Fecha de &Hoy - + &Open Link - + &Abrir Enlace - + Open &Folder - + Abrir &Carpeta - + External Link - + Enlace Externo - + Scheme - + Esquema - + &Browse for File - + &Seleccionar Archivo - + File Path Type - + Tipo de ruta de archivo - + Absolute - + Absoluta - + Relative - + Relativa - + Address - + Dirección - + Display Name - + Mostrar Nombre - + &OK - &Aceptar + &OK - + &Cancel - &Cancelar + &Cancelar - + TreeLine - External Link File - + TreeLine - Enlace a archivo externo - + &Go to Target - + &Ir al Destino - - Enable click-on-&target - + + Internal Link + Enlace Interno - - Disable click-on-&target - + + &Open Picture + &Abrir Imagen - - Internal Link - + + Picture Link + Enlace de Imagen - - Unique ID - + + TreeLine - Picture File + TreeLine - Archivo de Imagen - - &Open Picture - + + Set to &Now + Establecer A&hora - - Picture Link - + + Clear &Link + Borrar En&lace - - TreeLine - Picture File - + + (Click link target in tree) + (Clicar objeto a enlazar en el árbol) - - - dataeditview - - Unique ID - + + link + enlace exports - + Bookmarks - Favoritos + Favoritos - + TreeLine - Export HTML - + TreeLine - Exportar HTML - + TreeLine - Export Text Titles - + TreeLine - Exportar Textos de Títulos - + TreeLine - Export Plain Text - + TreeLine - Exportar Texto Plano - + TreeLine - Export Text Tables - + TreeLine - Exportar Tablas de Texto - + TreeLine - Export Generic XML - + TreeLine - Exportar XML Genérico - + TreeLine - Export TreeLine Subtree - + TreeLine - Exportar Subárbol TreeLine - + TreeLine - Export ODF Text - + Exportar Texto ODF - + TreeLine - Export HTML Bookmarks - + TreeLine - Exportar Favoritos HTML - + TreeLine - Export XBEL Bookmarks - + TreeLine - Exportar Favoritos XBEL - + &HTML - + &HTML - + &Text - - - - - &XML - + &Texto - + &ODF Outline - + &ODF Texto (esquema) - + Book&marks - + &Favoritos - + &Single HTML page - + Página HTML &Simple - + Single &HTML page with navigation pane - + &Página HTML simple con panel de navegación - + Multiple HTML &pages with navigation pane - + &Multiples páginas HTML con panel de navegación - + Multiple HTML &data tables - + Múltiples tablas de datos &HTML - + &Tabbed title text - &Texto de título tabulado + &Texto con tabulación por títulos - + &Unformatted output of all text - + &Salida sin formato para todo el texto - - Tab &delimited table of children - - - - - Generic &XML output - - - - - &Subtree in TreeLine format - - - - + &HTML format bookmarks - + Formato &HTML de favoritos - + &XBEL format bookmarks - + Formato &XBEL de favoritos - + File Export - + Exportar Archivo - + Choose export format type - + Seleccionar tipo de formato para exportar - + Choose export format subtype - + Seleccionar subtipo de formato para exportar - + Choose export options - + Seleccionar opciones para exportar - + What to Export - + Qué exportar - + &Entire tree - + &Todo el Árbol - + Selected &branches - + &Ramas Seleccionadas - + Selected &nodes - + &Nodos Seleccionados - + Other Options - + Otras Opciones - + &Include root node - &Incluir el nodo raíz + &Incluir el nodo raíz - + &Only open node children - + Abrir s&olo nodos hijos - + Include &print header && footer - + Incluir im&presión de encabezado y pie - + &Columns - + &Columnas - + Navigation pane &levels - + Nive&les del Panel de Navegación + + + + Error - export template files not found. +Check your TreeLine installation. + Error - Exportar archivos de plantilla no encontrados. +Verifique la instalación de su TreeLine. + + + + Error - cannot link to unsaved TreeLine file. +Save the file and retry. + Error: No se puede vincular al archivo TreeLine no guardado. +Guarde el archivo y vuelva a intentarlo. + + + + Warning - no relative path from "{0}" to "{1}". +Continue with absolute path? + Advertencia: no hay una ruta relativa entre "{0}" y "{1}". +Continuar con la ruta absoluta? + + + + Parent + Padre + + + + Tree&Line + Tree&Line + + + + &XML (generic) + &XML (genérico) + + + + Live tree view, linked to TreeLine file (for web server) + Vista de árbol en tiempo real, vinculada al archivo TreeLine (para servidor web) + + + + Live tree view, single file (embedded data) + Vista de árbol en tiempo real, archivo único (datos incrustados) + + + + &Comma delimited (CSV) table of descendants (level numbers) + Tabla &de descendientes delimitada por comas (CSV) (número de niveles) + + + + Comma &delimited (CSV) table of children (single level) + T&abla de hijos delimitada por comas (CSV) (nivel único) + + + + Tab &delimited table of children (&single level) + Ta&bla delimitada por tabulaciones según nodos hijos (nivel único) + + + + &Old TreeLine (2.0.x) + &Antiguos TreeLine (2.0.x) + + + + &TreeLine Subtree + &Subárbol TreeLine + + + + &Include root nodes + &Incluir noddos principales + + + + Must select nodes prior to export + Debe seleccionar nodos antes de exportar fieldformat - + yes/no sí/no - + true/false verdadero/falso - + T/F V/F - + Y/N S/N Text - Texto + Texto HtmlText - + Texto Html OneLineText - + Texto de una línea SpacedText - + Texto espaciado Number - Nombre + Número Math - + Matemáticas Numbering - + Numeración - + Boolean - Booleano + Booleano Date - Fecha + Fecha Time - Hora + Hora - + Choice - Elección + Elección - + AutoChoice - AutoElección + AutoElección Combination - Combinación + Combinación - + AutoCombination - + AutoCombinación - + ExternalLink - + Enlace Externo InternalLink - EnlaceInterno + EnlaceInterno - + Picture - Imagen + Imagen - + RegularExpression - + Expresión regular - + Now - Ahora + Ahora + + + + Optional Digit # + Dígito Opcional # + + + + Required Digit 0 + Dígito Requerido 0 + + + + Digit or Space (external) <space> + Dígito o Espacio (externo) <space> - - Optional Digit# - + + Decimal Point . + Punto Decimal . - - Required Digit0 - + + Decimal Comma , + Coma Decimal , - - Digit or Space (external)<space> - + + Comma Separator , + Separador Coma , - - Decimal Point. - + + Dot Separator . + Separador Punto . - - Decimal Comma, - + + Space Separator (internal) <space> + Separador Espacio (interno) <space> - - Comma Separator, - + + Optional Sign - + Signo Opcional - - - Dot Separator. - + + Required Sign + + Signo Requerido + - - Space Separator (internal)<space> - + + Exponent (capital) E + Exponente (mayúscula) E - - Optional Sign- - + + Exponent (small) e + Exponente (minúscula) e - - Required Sign+ - + + Number 1 + Número 1 - - Exponent (capital)E - + + Capital Letter A + Mayúsculas A - - Exponent (small)e - + + Small Letter a + Minúsculas a - - Number1 - + + Capital Roman Numeral I + Números Romanos (mayúsculas) I - - Capital LetterA - + + Small Roman Numeral i + Números romanos (minúsculas) i - - Small Lettera - + + Level Separator / + Separador de nivel / - - Capital Roman NumeralI - + + Section Separator . + Separador de sección . - - Small Roman Numerali - + + "/" Character // + "/" Carácter // - - Level Separator/ - + + "." Character .. + "." Carácter .. - - Section Separator. - + + Outline Example I../A../1../a)/i) + Ejemplo de esquema I../A../1../a)/i) - - "/" Character// - + + Section Example 1.1.1.1 + Ejemplo de sección 1.1.1.1 - - "." Character.. - + + Separator / + Separador / - - Outline ExampleI../A../1../a)/i) - + + Example 1/2/3/4 + Ejemplo 1/2/3/4 - - Section Example1.1.1.1 - + + Any Character . + Cualquier Carácter . - - Separator/ - + + End of Text $ + Final del Texto $ - - Example1/2/3/4 - + + 0 Or More Repetitions * + 0 o más repeticiones * - - Day (1 or 2 digits)d - + + 1 Or More Repetitions + + 1 o más repeticiones + - - Day (2 digits)dd - + + 0 Or 1 Repetitions ? + 0 ó 1 Repeticiones ? - - Weekday Abbreviationddd - + + Set of Numbers [0-9] + Conjunto de Números [0-9] - - Weekday Namedddd - + + Lower Case Letters [a-z] + Minúsculas [a-z] - - Month (1 or 2 digits)M - + + Upper Case Letters [A-Z] + Mayúsculas [A-Z] - - Month (2 digits)MM - + + Not a Number [^0-9] + No es un número [^0-9] - - Month AbbreviationMMM - + + Or | + O | - - Month NameMMMM - + + Escape a Special Character \ + Escape carácter especial \ - - Year (2 digits)yy - + + DateTime + Fecha y hora - - Year (4 digits)yyyy - + + Day (1 or 2 digits) %-d + Día (1 ó 2 dígitos) %-d - - Hour (0-23, 1 or 2 digits)H - + + Day (2 digits) %d + Día (2 dígitos) %d - - Hour (00-23, 2 digits)HH - + + Weekday Abbreviation %a + Día de la semana (abreviado) %a - - Hour (1-12, 1 or 2 digits)h - + + Weekday Name %A + Día de la semana %A - - Hour (01-12, 2 digits)hh - + + Month (1 or 2 digits) %-m + Mes (1 o 2 dígitos) %-m - - Minute (1 or 2 digits)m - + + Month (2 digits) %m + Mes (2 dígitos) %m - - Minute (2 digits)mm - + + Month Abbreviation %b + Mes (abreviado) %b - - Second (1 or 2 digits)s - + + Month Name %B + Mes (nombre completo) %B - - Second (2 digits)ss - + + Year (2 digits) %y + Año (2 dígitos) %y - - Milliseconds (1 to 3 digits)z - + + Year (4 digits) %Y + Año (4 dígitos) %Y - - Milliseconds (3 digits)zzz - + + Week Number (0 to 53) %-U + Semana (0 a 53) %-U - - AM/PMAP - + + Day of year (1 to 366) %-j + Día del año (1 a 366) %-j - - am/pmap - + + Hour (0-23, 1 or 2 digits) %-H + Hora (0-23, 1 ó 2 dígitos) %-H - - Any Character. - + + Hour (00-23, 2 digits) %H + Hora (00-23, 2 dígitos) %H - - End of Text$ - + + Hour (1-12, 1 or 2 digits) %-I + Hora (1-12, 1 ó 2 dígitos) %-I - - 0 Or More Repetitions* - + + Hour (01-12, 2 digits) %I + Hora (01-12, 2 dígitos) %I - - 1 Or More Repetitions+ - + + Minute (1 or 2 digits) %-M + Minutos (1 ó 2 dígitos) %-M - - 0 Or 1 Repetitions? - + + Minute (2 digits) %M + Minutos (2 dígitos) %M - - Set of Numbers[0-9] - + + Second (1 or 2 digits) %-S + Segundos (1 ó 2 dígitos) %-S - - Lower Case Letters[a-z] - + + Second (2 digits) %S + Segundos (2 dígitos) %S - - Upper Case Letters[A-Z] - + + Microseconds (6 digits) %f + Microsegundos (6 dígitos) %f - - Not a Number[^0-9] - + + AM/PM %p + AM/PM %p - - Or| - + + Comma Separator \, + Separador Coma \, - - Escape a Special Character\ - + + Dot Separator \. + Separador Punto \. @@ -1740,822 +1829,929 @@ globalref - + TreeLine Files - Archivos TreeLine + Archivos TreeLine - + TreeLine Files - Compressed - Archivos TreeLine - Comprimido + Archivos TreeLine - Comprimido - + TreeLine Files - Encrypted - Archivos TreeLine - Codificado + Archivos TreeLine - Encriptado - + All Files - Todos los archivos + Todos los Archivos - + HTML Files - + Archivos HTML - + Text Files - Archivos de texto + Archivos de Texto - + XML Files - Archivos XML + Archivos XML - + ODF Text Files - + Archivos de Texto ODF - + Treepad Files - Archivos de Treepad + Archivos de Treepad - + PDF Files - + Archivos PDF + + + + CSV (Comma Delimited) Files + Archivos CSV (Delimitado por Comas) + + + + All TreeLine Files + Todos los Archivos TreeLine + + + + Old TreeLine Files + Archivos TreeLine Antiguos helpview - + &Back &Volver - + &Forward &Siguiente - + &Home &Inicio - + Find: - + Buscar: - + Find &Previous - Buscar &Anterior + Buscar &Anterior - + Find &Next - Buscar &Siguiente + &Buscar Siguiente - + Text string not found - Cadena de texto no encontrada + Cadena de texto no encontrada - + Tools - + Herramientas imports - + &Tab indented text, one node per line - + &Sangrado de texto por tabulaciones, un nodo por línea - + Tab delimited text table with header &row - - - - - Plain text, one node per &line (CR delimited) - + Tabl&a de texto delimitada por tabulaciones con fila de encabezado - + Plain text &paragraphs (blank line delimited) - + Texto &plano en párrafos (delimitado por línea en blanco) - + Treepad &file (text nodes only) - &Archivo Treepad (únicamente nodos de texto) + &Archivo Treepad (únicamente nodos de texto) - + &Generic XML (non-TreeLine file) - + XML &genérico (No es un archivo TreeLine) - + Open &Document (ODF) outline - + Open &Document (ODF) - + &HTML bookmarks (Mozilla Format) - + &HTML favoritos (Formato Mozilla) - + &XML bookmarks (XBEL format) - favoritos en &XML (formato XBEL) + Favoritos en &XML (Formato XBEL) - + FOLDER - CARPETA + CARPETA - + BOOKMARK - FAVORITO + FAVORITO - + SEPARATOR - SEPARADOR + SEPARADOR - + Link - Enlace + Enlace - + Text - Texto + Texto - + Import File - + Importar Archivo - + Choose Import Method - + Escoger Método de Importación - + Invalid File - + Archivo no Válido - + "{0}" is not a valid TreeLine file. Use an import filter? - + "{0}" no es un archivo válido TreeLine. + +¿Usar filtro de importación de archivo? - + TreeLine - Import File - + TreeLine - Importar archivo - + Error - could not read file {0} - + Error-no se puede leer archivo {0} - + Error - improper format in {0} - + Error - formato incorrecto en {0} - + TABLE - + TABLA - + Bookmarks - Favoritos + Favoritos - + Too many entries on Line {0} - + Demasiadas entradas en la Línea {0} + + + + Plain text, one &node per line (CR delimited) + Text&o plano, un &nodo por línea (delimitado por retorno de carro -CR-) + + + + Bad CSV format on Line {0} + Formato CSV.erróneo en Línea {0} + + + + Co&mma delimited (CSV) text table with level column && header row + T&exto delimitado por comas (CSV) tabla de texto con nivel de columna y fila de encabezado + + + + Comma delimited (CSV) text table &with header row + Texto &delimitado por comas (CSV) tabla con fila de encabezado + + + + Other + Otro + + + + Old Tree&Line File (1.x or 2.x) + Archivo Antiguo de Tree&Line (1.x o 2.x) + + + + Invalid level number on line {0} + Número de nivel no válido en la línea {0} + + + + Invalid level structure + Estructura de nivel no válida matheval - + Illegal "{}" characters - + Caracteres "{}" no permitidos - + Child references must be combined in a function - + Las referencias secundarias deben combinarse en una función - + Illegal syntax in equation - + Sintaxis ilegar en la ecuación - + Illegal function present: {0} - + Función ilegal presente: {0} - + Illegal object type or operator: {0} - + Tipo de objeto u operador no permitido: {0} miscdialogs - + &OK - &Aceptar + &OK - + &Cancel - &Cancelar + &Cancelar - + Fields - Campos + Campos - + File Properties - + Propiedades de Archivo - + File Storage - Almacenamiento de ficheros + Almacenamiento de Archivos - + &Use file compression - + &Comprimir Archivo - + Use file &encryption - + &Encriptar Archivo - + Spell Check - Comprobación de la ortografía + Comprobación de la ortografía - + Language code or dictionary (optional) - + Código de idioma o +diccionario (opcional) - + Math Fields - + Campos Matemáticos - + &Treat blank fields as zeros - + Considerar los espacios en blanco como ceros (&0) - + Encrypted File Password - Contraseña del archivo + Contraseña del Archivo Encriptado - + Type Password for "{0}": - + Escriba la Contraseña para "{0}": - + Type Password: - Introduzca la contraseña: + Escriba la Contraseña: - + Re-Type Password: - Introduzca de nuevo la contraseña: + Escriba de nuevo la contraseña: - + Remember password during this session - Recuerde la contraseña durante esta sesión + Recordar la contraseña durante esta sesión - + Zero-length passwords are not permitted - No se permiten contraseñas de longitud cero + No se permiten contraseñas de longitud cero - + Re-typed password did not match - La contraseña introducida la segunda vez no coincide + La contraseña reescrita no coincide - + Default - Single Line Text - + Predeterminado - Texto Simple - + &Search Text - + &Buscar texto - + What to Search - + Dónde Buscar - + Full &data - + En to&do - + &Titles only - + Sólo en &títulos - + How to Search - + Cómo Buscar - + &Key words - + &Contiene - + Key full &words - + Texto E&xacto - + F&ull phrase - + Frase co&mpleta - + &Regular expression - + Exp&resión regular - + Find - Buscar + Buscar - + Find &Previous - Buscar &Anterior + Buscar &Anterior - + Find &Next - Buscar &Siguiente + Buscar &Siguiente - + Filter - + Filtro - + &Filter - + &Filtro - + &End Filter - + C&errar Filtro - + &Close - &Cerrar + &Cerrar - + Error - invalid regular expression - + Error - Expresión regular no válida - + Search string "{0}" not found - + Buscar cadena "{0}". No encontrada - + Find and Replace - + Buscar y reemplazar - + Replacement &Text - + Reemplazar &Texto - + Any &match - + Co&ntiene - + Full &words - + Coinci&de - + Re&gular expression - + Expresión re&gular - + &Node Type - + Tipo de &Nodo - + N&ode Fields - + Campos de N&odo - + &Find Next - + &Buscar Siguiente - + &Replace - &Reemplazar + &Reemplazar - + Replace &All - + Reemplaz&ar Todo - + [All Types] - + [Todos los tipos] - + [All Fields] - + [Todos los campos] - + Search text "{0}" not found - + Búsqueda del texto "{0}" no encontrado - + Error - replacement failed - + Error - reemplazo fallido - + Replaced {0} matches - + Reemplazadas {0} coincidencias - + Sort Nodes - + Ordenar Nodos - + What to Sort - + Qué ordenar - + &Entire tree - + Árbol compl&eto - + Selected &branches - + Ramas &Seleccionadas - + Selection's childre&n - + &Hijos seleccionados - + Selection's &siblings - + Her&manos seleccionados - + Sort Method - + Método de ordenado - + &Predefined Key Fields - + Nombres de Campos &Predefinidos - + Node &Titles - + &Títulos de Nodos - + Sort Direction - + Dirección de Ordenado - + &Forward - &Siguiente + &Adelante - + &Reverse - + At&rás - + &Apply - + A&plicar - + Update Node Numbering - + Actualizar Numeración de Nodo - + What to Update - + Qué Actualizar - + &Selection's children - + &Selección de Hijos - + Root Node - Nodo raíz + Nodo Principal - + Include top-level nodes - + Incluir nodos de nivel superior - + Handling Nodes without Numbering Fields - + Manejar nodos sin Campos de Numeración - + &Ignore and skip - + &Ignorar y omitir - + &Restart numbers for next siblings - + &Reiniciar números para los siguientes hermanos - + Reserve &numbers - + Reserva &números - + TreeLine Numbering - + Numeración TreeLine - + No numbering fields were found in data types - + No se encontraron campos de numeración en los tipos de datos - + File Menu - + Menú de Archivo - + File - + Archivo - + Edit Menu - + Menú Edición - + Edit - + Editar - + Node Menu - + Menú de Nodo - + Node - + Nodo - + Data Menu - + Menú de datos - + Data - + Datos - + Tools Menu - + Menú de Herramientas - + Tools - + Herramientas - + View Menu - + Menú Ver - + View - + Ver - + Window Menu - + Menú Ventana - + Window - + Ventana - + Help Menu - + Menú Ayuda - + Help - + Ayuda - + Keyboard Shortcuts - + Atajos de Teclado - + &Restore Defaults - + &Restaurar Valores por &Defecto - + Key {0} is already used - + Clave {0} ya está en uso - + Clear &Key - + Borrar Cla&ve - + --Separator-- - + --Separador-- - + Customize Toolbars - + Personalizar Barra de Herramientas - + Toolbar &Size - + Tamaño Barra de Herramienta&s - + Small Icons - + Iconos Pequeños - + Large Icons - + Iconos Grandes - + Toolbar Quantity - + Cantidad de barras de herramientas - + &Toolbars - &Barras de herramientas + Barras de herramien&tas - + A&vailable Commands - + Comandos Dis&ponibles - + Tool&bar Commands - + Comandos de la &barra de herramientas - + Move &Up - Mover a&rriba + Mover &Arriba - + Move &Down - + Mover Aba&jo - + Tree View Font - + Fuente del Árbol - + Output View Font - + Fuente de Salida - + Editor View Font - + Fuente del Editor - + TreeLine Plugins - Extensiones de TreeLine + Extensiones de TreeLine - + Plugin Modules Loaded - Extensiones cargadas + Extensiones cargadas + + + + No menu + No.menu + + + + TreeLine - Serious Error + TreeLine - Error Grave + + + + A serious error has occurred. TreeLine could be in an unstable state. +Recommend saving any file changes under another filename and restart TreeLine. + +The debugging info shown below can be copied and emailed to doug101@bellz.org along with +an explanation of the circumstances. + + Se ha producido un error grave. TreeLine podría estar en un estado inestable. +Se recomienda guardar cualquier cambio de archivo con otro nombre de archivo y reiniciar TreeLine. + +La información de depuración que se muestra a continuación se puede copiar y enviar por correo electrónico a doug101@bellz.org junto con una explicación de las circunstancias del error. + + + + + Format Menu + Menú Formato + + + + Format + Formato + + + + Customize Fonts + Personalizar Fuentes + + + + &Use system default font + &Usar fuente por defecto del sistema + + + + App Default Font + Fuente por defecto de la aplicación + + + + &Use app default font + &Usar la fuente predeterminada de la aplicación nodeformat - + Name - Nombre + Nombre @@ -2563,666 +2759,685 @@ Monday - Lunes + Lunes Tuesday - Martes + Martes Wednesday - Miércoles + Miércoles Thursday - Jueves + Jueves Friday - Viernes + Viernes Saturday - Sábado + Sábado Sunday - Domingo + Domingo - + Startup Condition - Situación de inicio + Condición de Inicio Automatically open last file used - Abrir automáticamente el último archivo usado - - - - Show child pane in right hand view - + Abrir automáticamente el último archivo usado - + Show descendants in output view - + Mostrar hijos en la vista de salida - + Restore tree view states of recent files - + Restaurar estados de vista en árbol de archivos recientes - + Restore previous window geometry - + Restaurar la geometría de la ventana anterior - + Features Available - Funcionalidades disponibles + Funcionalidades disponibles - + Open files in new windows - + Abrir archivos en una nueva ventana - + Click node to rename - + Clicar en nodo para renombrar - + Rename new nodes when created - Renombrar los nodos nuevos cuando sean creados + Renombrar los nodos nuevos cuando sean creados - + Tree drag && drop available - Disponible la opción de pulsar y arrastrar en el árbol + Disponible la opción de pulsar y arrastrar en el árbol - + Show icons in the tree view - Mostrar iconos en la vista de árbol - - - - Show node unique IDs in the Data Edit view - + Mostrar iconos en la vista de árbol - + Show math fields in the Data Edit view - + Mostrar campos matemáticos en la vista de Edición de Datos - + Show numbering fields in the Data Edit view - + Mostrar campos de numeración en la vista de Edición de Datos - + Undo Memory - Memoria usada por la opción "deshacer" + Memoria usada por la opción "deshacer" - + Number of undo levels - Número de niveles de deshacer + Número de niveles de deshacer - + Auto Save - Grabación automática + Grabación automática - + Minutes between saves (set to 0 to disable) - + Minutos entre respaldos (seleccione 0 para desactivar) - + Recent Files - Archivos recientes + Archivos recientes - + Number of recent files in the file menu - + Cantidad de archivos recientes +en el menú de archivo - + Data Editor Formats - Formatos del editor de datos + Formatos del editor de datos - + Times - Horas + Horas - + Dates - Fechas + Fechas - + First day of week - + Primer día de la semana - + Appearance - Apariencia + Apariencia - + Child indent offset (in font height units) - - - - - Printer Settings - + Corrección de sangrado de hijos +(en unidades de altura de fuente) - + Spell Check - Comprobación de la ortografía + Comprobación de la ortografía - - Fonts - + + Show breadcrumb ancestor view + Mostrar panel de navegación - - Window Geometry - + + Show child pane in right hand views + Motrar paneles hijos en la vista derecha - - Toolbar Geometry - + + Remove inaccessible recent file entries + Eliminar archivos inaccesibles en la vista de archivos recientes - - Toolbar Commands - + + Activate data editors on mouse hover + Activar editores de datos cuando el puntero del ratón esté encima options - + Choose configuration file location - + Elija la ubicación del archivo de configuración - + User's home directory (recommended) - + Directorio de inicio de usuario (recomendado) - + Program directory (for portable use) - + Carpeta del programa (para uso portable) - + &OK - &Aceptar + &Aceptar - + &Cancel - &Cancelar + &Cancelar printdata - + Error initializing printer - + Error inicializando la impresora - + TreeLine - Export PDF - + TreeLine - Exportar a PDF + + + + Warning: Page size and margin settings unsupported on current printer. +Save page adjustments? + Advertencia: configuración de tamaño de página y margen no admitida en la impresora actual. + ¿Guardar los ajustes de página? + + + + Warning: Page size setting unsupported on current printer. +Save adjustment? + Advertencia: configuración de tamaño de página no admitida en la impresora actual. + ¿Guardar ajustes? + + + + Warning: Margin settings unsupported on current printer. +Save adjustments? + Advertencia: configuración de margen no compatible en la impresora actual. +¿Guardar los ajustes? printdialogs - + Print Preview - Vista previa de impresión + Vista Previa de Impresión - + &Print... - &Imprimir.... + &Imprimir... - + &General Options - + Opciones &Generales - + &Font Selection - + Selección de &Fuente - + &Header/Footer - + &Encabezado y pie - + Print Pre&view... - + &Vista Previa... - + &OK - &Aceptar + &OK - + &Cancel - &Cancelar + &Cancelar - + What to print - + Qué imprimir - + &Entire tree - + &Árbol completo - + Selected &branches - + &Ramas seleccionadas - + Selected &nodes - + &Nodos seleccionados - + Features - Funcionalidades + Características - + &Include root node - &Incluir el nodo raíz + Incluir el nodo rai&z - + &Keep first child with parent - + &Mantener el primer nodo hijo con el padre - + Letter (8.5 x 11 in.) - + Carta (8.5 x 11 in.) - + Legal (8.5 x 14 in.) - + Legal (8.5 x 14 in.) - + Tabloid (11 x 17 in.) - + Tabloide (11 x 17 in.) - + A3 (279 x 420 mm) - + A3 (279 x 420 mm) - + A4 (210 x 297 mm) - + A4 (210 x 297 mm) - + A5 (148 x 210 mm) - + A5 (148 x 210 mm) - + Paper &Size - + Tamaño del Pape&l - + Orientation - + Orientación - + &Units - + &Unidades - + Columns - Columnas + Columnas - + &Number of columns - + &Número de columnas - + Default Font - + Fuente predeterminada - + Select Font - + Seleccionar Fuente - + &Font - + &Fuente - + Font st&yle - + E&stilo de fuente - + Sample - + Ejemplo - + &Header Left - Cabecera a la &izquierda + Encabezado I&zquierdo - + Header C&enter - Cabecera &centrada + Encabezado cen&trado - + Footer &Left - + Pie Iz&quierdo - + Footer Ce&nter - Pie de página ce&ntrado + Pie ce&ntrado - + Fiel&ds - Cam&pos + Campo&s - + Header and Footer - Cabecera y pie de página + Encabezado y pie de página - + Extra Text - Texto adicional + Texto adicional - + &Prefix - + &Prefijo - + Format &Help - + A&yuda de Formato - + Fit Width - + Ajuste de ancho - + Fit Page - + Ajuste de página - + Zoom In - + Zoom Aumentar - + Zoom Out - + Zorrm Reducir - + Previous Page - + Página anterior - + Next Page - + Página Siguiente - + Single Page - + Página sencilla - + Facing Pages - + Páginas enfrentadas - + Print Setup - + Configuración de Impresión - + Print - + Imprimir - + Printing Setup - + Configuración de Impresión - + Page &Setup - + Configuración de &Página - + Included Nodes - + Nodos Incluídos - + Onl&y open node children - + Incluir solo nodos &hijos - + &Draw lines to children - + &Dibujar líneas para los nodos hijos - + Indent - + Sangrar - + Indent Offse&t (line height units) - + Sangrado de &Salida +(unidades de altura de línea) - + Custom Size - + Tamaño Personalizado - + Inches (in) - + Pulgadas (in) - + Millimeters (mm) - + Milímetros (mm) - + Centimeters (cm) - + Centímetros (cm) - + &Width: - + A&nchura: - + Height: - + Altura: - + Portra&it - + Retra&to - + Lan&dscape - + Paisa&je - + Margins - + Márgenes - + &Left: - + Iz&quierda: - + &Top: - + A&rriba: - + &Right: - + Derec&ha: - + &Bottom: - + A&bajo: - + He&ader: - + Encabe&zado: - + Foot&er: - + &Pie: - + Space between colu&mns - - - - - &Use system default font - + Espacio entre colu&mnas - + &Use TreeLine output view font - + &Usar la misma que haya configurada en "Salida de Datos" de TreeLine - + Si&ze - + &Tamaño - + AaBbCcDdEeFfGg...TtUuVvWvXxYyZz - + AaBbCcDdEeFfGg...TtUuVvWvXxYyZz - + Header &Right - + Encabezado &Derecho - + Footer Righ&t - + Pie De&recho - + Field For&mat - + For&mato de Campo - + Field Format for "{0}" - + Formato de Campo para "{0}" - + Output &Format - + Salida &Formato - + &Suffix - + &Sufijo + + + + Error: Page size or margins are invalid + Error: Tamaño de página o márgenes inválidos + + + + TreeLine PDF Printer + TreeLine Impresora PDF + + + + Select &Printer + Seleccionar Im&presora @@ -3230,1234 +3445,1341 @@ Recent Files - Archivos recientes + Archivos recientes spellcheck - + Could not find either aspell.exe, ispell.exe or hunspell.exe Browse for location? - + No se pudo encontrar aspell.exe, ispell.exe o hunspell.exe +¿Buscar la ubicación? - + Spell Check Error - + Error ortográfico - + Locate aspell.exe, ipsell.exe or hunspell.exe - + Ubique aspell.exe, ipsell.exe o hunspell.exe - + Program (*.exe) - Programa (*.exe) + Programa (*.exe) - + TreeLine Spell Check Error Make sure aspell, ispell or hunspell is installed - + Error de comprobación ortográfica TreeLine +Comprobar que esté instalado aspell, ispell o hunspell - + Finished checking the branch Continue from the root branch? - Fin de la comprobación de la rama + Fin de la comprobación de la rama ¿Continuar desde la rama raíz? - + TreeLine Spell Check - Comprobación ortográfica de TreeLIne + Comprobación ortográfica de TreeLIne - + Finished spell checking - + Comprobación ortográfica finalizada - + Spell Check - Comprobación de la ortografía + Comprobación de la ortografía - + Not in Dictionary - No está en el diccionario + No está en el diccionario - + Word: - Palabra: + Palabra: - + Context: - Contexto: + Contexto: - + Suggestions - Sugerencias + Sugerencias - + Ignor&e - Ignor&ar + Ignor&ar - + &Ignore All - &Ignorar todo + &Ignorar todo - + &Add - &Añadir + &Añadir - + Add &Lowercase - Añadir &minúscula + Añadir &minúscula - + &Replace - &Reemplazar + &Reemplazar - + Re&place All - Reem&plazar todo + Reem&plazar todo - + &Cancel - &Cancelar + &Cancelar + + + + Finished checking the branch +Continue from the top? + Terminada revisión de la rama +Continuar desde la parte superior? treeformats - + DEFAULT - PREDETERMINADO + PREDETERMINADO treelocalcontrol - - Warning: duplicate Unique IDs found. - - - - - - Many Unique IDs were re-assigned. - - - - - - The following IDs were re-assigned: - - - - - - -Internal link targets could be affected. - - - - + Error - could not delete backup file {} - + Error - No se puede borrar el archivo de respaldo {} - + Save changes to {}? - + ¿Guardar cambios en {}? - + Save changes? - ¿Grabar los cambios? + ¿Guardar cambios? - + &Save - &Grabar + &Guardar - + Save File - Grabar archivo + Guardar archivo - + Save the current file - + Guardar el archivo actual - + Save &As... - Grabar &como... + Guardar &Como... - + Save the file with a new name - Grabar el archivo con un nombre nuevo + Guardar el archivo con un nombre nuevo - + &Export... - &Exportar... + &Exportar... - + Export the file in various other formats - + Exportar el archivo en varios formatos - + Prop&erties... - + &Propiedades... - + Set file parameters like compression and encryption - + Establecer parámetros de archivo como compresión y encriptación - + P&rint Setup... - + Config&uración de Impresión... - + Set margins, page size and other printing options - + Establecer márgenes, tamaño de página y otras opciones de impresión - + Print Pre&view... - + &Vista Previa de Impresión... - + Show a preview of printing results - + Vista previa de los resultados de impresión - + &Print... - &Imprimir.... + &Imprimir... - + Print tree output based on current options - + Imprimir salida de árbol en base a las opciones actuales - + Print &to PDF... - + Imprimir a PD&F... - + Export to PDF with current printing options - + Exportar a PDF con las opciones de impresión actuales - + &Undo - &Deshacer + &Deshacer - + Undo the previous action - Deshacer la acción previa + Deshacer la acción anterior - + &Redo - &Rehacer + &Rehacer - + Redo the previous undo - Rehacer la última acción sobre la que se ha aplicado deshacer + Rehacer la última acción sobre la que se ha aplicado deshacer - + Cu&t - C&ortar + Cor&tar - + Cut the branch or text to the clipboard - Cortar la rama o el texto al portapapeles + Cortar la rama o el texto al portapapeles - + &Copy - &Copiar + &Copiar - + Copy the branch or text to the clipboard - Copiar la rama o texto al portapapeles + Copiar la rama o texto al portapapeles - + &Paste - &Pegar + &Pegar - + Paste nodes or text from the clipboard - Pegar nodos o texto desde el portapapeles - - - - P&aste Plain Text - + Pegar nodos o texto desde el portapapeles - + Paste non-formatted text from the clipboard - + Pegar texto sin formato del portapapeles - + &Bold Font - + &Negrita - + Set the current or selected font to bold - + Establecer la fuente actual o seleccionada en negrita - + &Italic Font - + &Cursiva - + Set the current or selected font to italic - + Establecer la fuente actual o seleccionada en cursiva - + U&nderline Font - + &Subrayado - + Set the current or selected font to underline - + Establecer la fuente actual o seleccionada para subrayar - + &Font Size - + &Tamaño de Fuente - + Set size of the current or selected text - + Establecer el tamaño del texto actual o seleccionado - + Small - + Pequeño - + Default - + Predeterminado - + Large - + Grande - + Larger - + Más grande - + Largest - + El más grande - + Set Font Size - + Definir tamaño de fuente - + Font C&olor... - + Color de &Fuente... - + Set the color of the current or selected text - + Establecer el color del texto actual o seleccionado - + &External Link... - + Enlace &Externo... - + Add or modify an extrnal web link - + Añadir o modificar un enlace web externo - + Internal &Link... - + Enlace &Interno... - + Add or modify an internal node link - + Añadir o modificar un enlace interno a nodo - + Clear For&matting - + &Borrar Formato - + Clear current or selected text formatting - + Borrar actual o seleccionado formato de texto - + &Rename - + &Renombrar - + Rename the current tree entry title - + Renombrar título completo del árbol en uso - + Insert Sibling &Before - Insertar hermano a&ntes + Añadir &Hermano Antes - + Insert new sibling before selection - Insertar nuevo hermano antes de la selección + Añadir nuevo hermano antes de la selección - + Insert Sibling &After - Insertar hermano &después + Añadir Hermano &Después - + Insert new sibling after selection - Insertar nuevo hermano despues de la selección + Añadir nuevo hermano despues de la selección - + Add &Child - + &Añadir Hijo - + Add new child to selected parent - + Añadir nuevo hijo al padre seleccionado - + &Delete Node - &Eliminar nodos + &Eliminar Nodo - + Delete the selected nodes - Eliminar los nodos seleccionados + Eliminar los nodos seleccionados - + &Indent Node - Inden&tar Nodos + &Sangrar Nodos - + Indent the selected nodes - Indentar los nodos seleccionados + Sangrar los nodos seleccionados - + &Unindent Node - + Deshacer Sangrado de &Nodo - + Unindent the selected nodes - Desindentar los nodos seleccionados + Eliminar sangrado de los nodos seleccionados - + &Move Up - Mover hacia a&rriba + Mover A&rriba - + Move the selected nodes up - Desplazar hacia arriba los nodos seleccionados + Desplazar hacia arriba los nodos seleccionados - + M&ove Down - Mover hacia a&bajo + Mover Aba&jo - + Move the selected nodes down - Desplazar hacia abajo los nodos seleccionados + Desplazar hacia abajo los nodos seleccionados - + Move &First - + Mover al &Principio - + Move the selected nodes to be the first children - + Mover nodos seleccionados para ser los primeros hijos - + Move &Last - + Mover al &Final - + Move the selected nodes to be the last children - + Mover nodos seleccionados para ser los últimos hijos - + &Set Node Type - + &Establecer Tipo de Nodo - + Set the node type for selected nodes - + Establecer tipo de nodo para los nodos seleccionados - + Set Node Type - + Establecer tipo de nodo - + Copy Types from &File... - + &Copiar Tipos desde Archivo... - + Copy the configuration from another TreeLine file - Copiar la configuración desde otro archivo de TreeLine + Copiar la configuración desde otro archivo de TreeLine - + Flatten &by Category - + &Acoplar por Categoría - + Collapse descendants by merging fields - + Contraer descendientes cuando se fusionen campos - + Add Category &Level... - + Añadir Ni&vel de Categoría... - + Insert category nodes above children - Insertar los nodos con las categorías por encima de los hijos - - - - Flatten b&y Link... - - - - - Collapse descendants and insert parent links - - - - - &Arrange by Link... - - - - - Arrange descendants using parent links - + Añadir los nodos con las categorías por encima de los hijos - + &Spell Check... - + Corrector &Ortográfico... - + Spell check the tree')s text data - + Revisar los datos de texto del árbol - + &New Window - + &Nueva Ventana - + Open a new window for the same file - + Abrir nueva ventana para el mismo archivo - + Error - could not write to {} - + Error - No se pudo escribir en {} - + TreeLine - Save As - + TreeLine - Guardar como - + Error - could not write to file - + Error - no se pudo escribir en el archivo - + TreeLine - Open Configuration File - + TreeLine - Abrir archivo de configuración - + Error - could not read file {0} - + Error: no se pudo leer el archivo {0} - + Cannot expand without common fields - No es posible expandir sin campos comunes + No es posible expandir sin campos comunes - + Category Fields - Campos de las categorías + Campos de las categorías - + Select fields for new level - Elija campos para el nuevo nivel + Seleccionar campos para nuevo nivel - - Flatten by Link - + + File saved + Archivo guardado - - Enter a new field name for parent links - + + Pa&ste Plain Text + Pegar Texto sin &Formato - - Link Field - + + Paste C&hild + Pegar &Hijo - - Select field with links to parents - + + Paste a child node from the clipboard + Pegar un nodo hijo desde el portapapeles - - File saved - + + Paste Sibling &Before + Pegar Hermano &Antes + + + + Paste a sibling before selection + Pegar hermano antes de la selección + + + + Paste Sibling &After + Pegar Hermano Despué&s + + + + Paste a sibling after selection + Pegar un hermano después de la selección + + + + Paste Cl&oned Child + Pegar Hijo C&lonado + + + + Paste a child clone from the clipboard + Pegar hijo clonado desde el portapapeles + + + + Paste Clo&ned Sibling Before + Pegar Hermano Clo&nado Antes + + + + Paste a sibling clone before selection + Pegar un clon hermano antes de la selección + + + + Paste Clone&d Sibling After + Pegar Hermano Clonad&o Después + + + + Paste a sibling clone after selection + Pegar un clon hermano después de la selección + + + + Clone All &Matched Nodes + Clonar &Todos los Nodos Emparejados + + + + Convert all matching nodes into clones + Convertir todos los nodos coincidentes en clones + + + + &Detach Clones + &Separar Clones + + + + Detach all cloned nodes in current branches + Separar todos los nodos clonados en las ramas actuales + + + + S&wap Category Levels + &Intercambiar Niveles de Categoría + + + + Swap child and grandchild category nodes + Intercambiar categoría nodos hijos y nietos + + + + Converted {0} branches into clones + Convierte ramas {0} en clones + + + + No identical nodes found + No existen nodos idénticos + + + + Warning - file corruption! +Skipped bad child references in the following nodes: + Advertencia: ¡archivo corrupto! +Se omitieron referencias de elementos secundarios incorrectos en los siguientes nodos: + + + + Spell check the tree's text data + Revisar los datos de texto del árbol treemaincontrol - + Warning: Could not create local socket - + Advertencia: no se pudo crear el socket local Error - could not write config file to {} - + Error - no se pudo escribir el archivo de configuración en {} - + Error - could not read file {0} - + Error: no se pudo leer el archivo {0} - - Error - {0} is not a valid TreeLine file - - - - + Backup file "{}" exists. A previous session may have crashed - + Existe un archivo de respaldo: "{}". +Una sesión anterior puede haberse bloqueado - + &Restore Backup - &Usar el archivo de recuperación + &Restaurar copia de seguridad - + &Delete Backup - &Eliminar el archivo de recuperación + Eliminar copia de segurida&d - + &Cancel File Open - &Cancelar archivo abierto + &Cancelar archivo abierto - + Error - could not rename "{0}" to "{1}" - + Error: - no se pudo cambiar el nombre de "{0}" a "{1}" - + Error - could not remove backup file {} - - - - - Warning - could not load plugin module {0} - + Error - no se pudo eliminar el archivo de copia de seguridad {} - + &New... - + &Nuevo... - + New File - Archivo nuevo + Archivo nuevo - + Start a new file - Comenzar un archivo nuevo + Comenzar un archivo nuevo - + &Open... - &Abrir... + &Abrir... - + Open File - Abrir archivo + Abrir archivo - + Open a file from disk - Abrir un archivo desde el disco + Abrir un archivo desde el disco - + Open Sa&mple... - Abrir eje&mplo... + Abrir Archivo de Eje&mplo... - + Open Sample - + Abrir ejemplo - + Open a sample file - + Abrir un archivo de ejemplo - + &Import... - + Importa&r... - + Open a non-TreeLine file - + Abriri un arhchivo no-TreeLine - + &Quit - &Salir + &Salir - + Exit the application - Salir de la aplicación + Salir de la aplicación - + &Select All - + Seleccionar T&odo - + Select all text in an editor - + Seleccionar todo el texto en un editor - + &Configure Data Types... - Configurar tipos de &datos... + Configurar Tipos de &Datos... - + Modify data types, fields & output lines - + Modificar tipos de datos, campos y líneas de salida - + Sor&t Nodes... - + &Ordenar Nodos... - + Define node sort operations - + Definir operaciones de ordenación de nodos - + Update &Numbering... - + Actualizar &Numeración... - + Update node numbering fields - + Actualizar campos de numeración de nodos - + &Find Text... - + &Buscar Texto... - + Find text in node titles & data - + Buscar texto en títulos y datos de nodo - + &Conditional Find... - + Búsqueda &Condicional... - + Use field conditions to find nodes - + Use condiciones de campo para encontrar nodos - + Find and &Replace... - + Buscar y &Reemplazar... - + Replace text strings in node data - + Reemplazar cadenas de texto en datos de nodo - + &Text Filter... - + Filtro de &Texto... - + Filter nodes to only show text matches - + Filtrar nodos para mostrar solo coincidencias de texto - + C&onditional Filter... - + &Filtro Condicional... - + Use field conditions to filter nodes - + Usar condiciones de campo para filtrar nodos - + &General Options... - &Opciones generales... + Opciones &Generales... - + Set user preferences for all files - Configurar las preferencias para todos los archivos + Configurar las preferencias para todos los archivos - + Set &Keyboard Shortcuts... - + Establecer &Atajos de Teclado... - + Customize keyboard commands - + Personalizar comandos de teclado - + C&ustomize Toolbars... - + Personalizar Barras de &Herramientas... - + Customize toolbar buttons - + Personalizar botones de la barra de herramientas - + Customize Fo&nts... - + &Personalizar Fuentes... - + Customize fonts in various views - + Personalizar fuentes en varias vistas - + &Basic Usage... - + Uso &Básico... - + Display basic usage instructions - + Mostrar instrucciones de uso básico - + &Full Documentation... - + &Documentación completa... - + Open a TreeLine file with full documentation - + Abrir un archivo TreeLine con documentación completa - + &About TreeLine... - + &Acerca de TreeLine... - + Display version info about this program - - - - - &About &Plugins... - + Mostrar información de versión de esta aplicación - + Show loaded plugin modules - Mostrar las extensiones cargadas + Mostrar las extensiones cargadas - + &Select Template - + &Seleccionar Plantilla - + TreeLine - Open File - + TreeLine - Abrir Archivo - + Open Sample File - + Abrir archivo de ejemplo - + &Select Sample - + &Seleccionar Ejemplo - + Conditional Find - + Búsqueda Condicional - + Conditional Filter - + Filtro Condicional - + ToolsFilterCondition - + ToolsFilterCondition - + General Options - Opciones generales + Opciones generales - + Error - basic help file not found - + Error - archivo de ayuda básica no encontrado - + TreeLine Basic Usage - + TreeLine Uso Básico - + Error - documentation file not found - + Error - archivo de documentación no encontrado + + + + Error - invalid TreeLine file {0} + Error - archivo TreeLine no válido {0} - - TreeLine, Version {0} -by {1} - + + TreeLine version {0} + TreeLine version {0} + + + + written by {0} + escrito por {} + + + + Library versions: + Versiones de la biblioteca: treemodel - + Main - Principal + Principal treenode - + New - Nuevo + Nuevo - treeopener + treestructure - - Invalid XML file - - - - - Bad elememnt - not a valid TreeLine file - + + Main + Principal treeview - + Filtering by "{0}", found {1} nodes - + Filtrando por "{0}", encontrados.{1} nodos - + Conditional filtering, found {0} nodes - + Filtrado condicional, encontrados {0} nodos + + + + Search for: + Buscar: + + + + Search for: {0} + Buscar {0} + + + + Search for: {0} (not found) + Buscar {0} (no encontrado) + + + + Next: {0} + Siguiente: {0} + + + + Next: {0} (not found) + Siguiente: {0} (no encontrado) treewindow - + Data Output - + Salida de Datos Data Edit - + Editor de Datos - + Title List - + Lista de Títulos - + &Expand Full Branch - &Expandir toda la rama + &Expandir Toda la Rama - + Expand all children of the selected nodes - + Expandir todos los hijos de los nodos seleccionados - + &Collapse Full Branch - &Reducir toda la rama + &Reducir Toda la Rama - + Collapse all children of the selected nodes - + Reducir todos los hijos de los nodos seleccionados - + &Previous Selection - + Selección &Previa - + Return to the previous tree selection - + Volver a la selección de árbol anterior - + &Next Selection - + Selección &Siguiente - + Go to the next tree selection in history - + Ir a la siguiente selección de árbol en el historial - + Show Data &Output - + &Mostrar Salida de Datos - + Show data output in right view - Mostrar la salida de datos en la vista de la derecha + Mostrar la salida de datos en la vista derecha - + Show Data &Editor - Mostrar &editor de datos + Mostrar Editor de &Datos - + Show data editor in right view - Mostrar el editor de datos en la vista de la derecha + Mostrar el editor de datos en la vista de la derecha - + Show &Title List - + Mostrar &Lista de Títulos - + Show title list in right view - Mostrar la lista de títulos en la vista de la derecha + Mostrar la lista de títulos en la vista de la derecha - + &Show Child Pane - + Mostrar Panel &Hijo - + Toggle showing right-hand child views - - - - - Show Output &Descedants - + Alternar mostrando resultados - + Toggle showing output view indented descendants - + Alternar mostrando la vista de salida sangría de descendients - + &Close Window - + &Cerrar Ventana - + Close this window - + Cerrar esta ventana - + &File - &Archivo + &Archivo - + &Edit - &Editar + &Editar - + &Node - + &Nodo - + &Data - &Datos + &Datos - + &Tools - &Herramientas + &Herramientas - + &View - &Ver + &Ver - + &Window - + Ven&tana - + &Help - A&yuda + A&yuda + + + + Start Incremental Search + Iniciar búsqueda incremental + + + + Next Incremental Search + Búsqueda incremental siguiente + + + + Previous Incremental Search + Búsqueda incremental anterior + + + + Show &Breadcrumb View + Mostrar &vista panel de navegación + + + + Toggle showing breadcrumb ancestor view + Alternar mostrando vista anterior de navegación + + + + Show Output &Descendants + Mostrar Salida de Datos y Des&cendientes + + + + Fo&rmat + &Formato diff -Nru treeline-2.0.2/i18n/translations/treeline_fr.ts treeline-3.0.1/i18n/translations/treeline_fr.ts --- treeline-2.0.2/i18n/translations/treeline_fr.ts 2018-04-08 20:12:57.000000000 +0000 +++ treeline-3.0.1/i18n/translations/treeline_fr.ts 2018-10-17 14:52:24.000000000 +0000 @@ -3,310 +3,300 @@ conditional - + starts with commence avec - + ends with se termine par - + contains contient - + True Vrai - + False Faux - + and et - + or ou - + [All Types] - + Node Type - + &Add New Rule &Ajouter Nouvelle Règle - + &Remove Rule &Supprimer Règle - + &OK &OK - + &Cancel &Annuler - - R&etrieve Rules... - - - - - &Save Rules... - - - - + Find &Previous Chercher &Précédent - + Find &Next Chercher &Suivant - + &Filter - + &End Filter - + &Close &Fermer - - Save Rules + + No conditional matches were found - - Enter a descriptive name + + Rule {0} - - No conditional matches were found + + Saved Rules - - Rule {0} + + Name: - - Retrieve Rules + + &Load - - Select rule set to retrieve: + + &Save - - Remove Rule + + &Delete configdialog - + Configure Data Types Paramètres du Type de Données - + T&ype List Liste des t&ypes - + Field &List &Liste des champs - + &Field Config Con&figuration des champs - + &Show Advanced Avancé&s - + &OK &OK - + &Apply &Appliquer - + &Reset &Réinitialiser - + &Cancel &Annuler - + Add or Remove Data Types Ajouter ou supprimer des Types de Données - + &New Type... &Nouveau Type... - + Co&py Type... Co&pier le type… - + &Delete Type &Supprimer Type - + Add Type Ajouter Type - + Enter new type name: Entrer un nouveau nom de type: - + Rename Type Renommer Type - + Cannot delete data type being used by nodes Impossible de supprimer un type de données qui est en cours d'utilisation par des nœuds - + &Data Type T&ype de Données - + Icon Icône - + Change &Icon Modifier l'&Icône - + Derived from &Generic Type Dérivé d'un Type &Générique - + Automatic Types Types Automatiques - + Modify Co&nditional Types Modifier un Type co&nditionnel - + Create Co&nditional Types Créer un Type Co&nditionnel - + Set Types Conditionally Configurer Types Sous Conditons - + Name Nom - + Type Type - + Move &Up Déplacer vers le &haut - + Move Do&wn Déplacer vers le &bas - + &New Field... &Nouveau Champ... - + Add Field Ajouter un champ - + Enter new field name: Entrer le nouveau nom du champ : - + Rename Field Renommer le champ - + F&ield C&hamp @@ -341,178 +331,173 @@ Nom&bre de lignes de texte - + File Info Reference Références du Fichier - + Parent Reference Référence du Parent - + Child Reference Réference du Fils - + Child Count Nombre d'enfant - + F&ield List L&iste des champs - + Other Field References Références à d'autres champs - + Copy Type Copier Type - + Set Data Type Icon Configurer Icône Type de Données - + Clear &Select Effacer &Sélection - + Typ&e Config - + O&utput - + &Hide Advanced - + Error - circular reference in math field equations - + Rena&me Type... - + &Derive from original - + Rename from {} to: - + [None] no type set [Aucun] - + Default Child &Type - + Output Options - + Add &blank lines between nodes - + Allow &HTML rich text in format - + Add text bullet&s - + Use a table for field &data - + Combination && Child List Output &Separator - - Uni&que ID Reference Field - - - - + None Aucun - + Modify &Field List - + Sort Key - + Move U&p - + Rena&me Field... - + Dele&te Field - + Sort &Keys... - + fwd - + rev - + &Field Type @@ -537,794 +522,879 @@ - + &Title Format - + Out&put Format - + Reference Le&vel - + Refere&nce Type - + The name cannot be empty - + The name must start with a letter - + The name cannot start with "xml" - + The name cannot contain spaces - + The following characters are not allowed: {} - + The name was already used - + forward - + reverse - + Sort Key Fields - + Available &Fields - + &Sort Criteria - + Field - + Direction Direction - + &Move Down - + Flip &Direction - + Self Reference - + Root Reference - - Numeric Result - - - - + Date Result - + Time Result - + add ajouter - + subtract - + multiply - + divide - + floor divide - + modulus - + power - + sum of items - + maximum - + minimum - + average - + absolute value - + square root - + natural logarithm - + base-10 logarithm - + factorial - + round to num digits - + lower integer - + higher integer - + truncated integer - + floating point - + sine of radians - + cosine of radians - + tangent of radians - + arc sine - + arc cosine - + arc tangent - + radians to degrees - + degrees to radians - + pi constant - + natural log constant - + Define Math Field Equation - + Field References - + Reference &Level - + Reference &Type - + Available &Field List - + &Result Type - + Description - + &Equation - + Equation error: {} - + Boolean Result - + Text Result - + Arithmetic Operators - + Comparison Operators - + Text Operators - + equal to - + less than - + greater than - + less than or equal to - + greater than or equal to - + not equal to - + true value, condition, false value - + true if 1st text arg starts with 2nd arg - + true if 1st text arg ends with 2nd arg - + true if 1st text arg contains 2nd arg - + concatenate text - + join text using 1st arg as separator - + convert text to upper case - + convert text to lower case - + in 1st arg, replace 2nd arg with 3rd arg - + Operations - + O&perator Type - + Oper&ator List - + logical and - + logical or + + + Output HTML + + + + + Evaluate &HTML tags + + + + + Child Type Limits + + + + + [All Types Available] + + + + + &Select All + + + + + Select &None + + + + + Number Result + + dataeditors - + Today's &Date - + &Open Link - + Open &Folder - + External Link - + Scheme - + &Browse for File - + File Path Type - + Absolute - + Relative - + Address - + Display Name - + &OK &OK - + &Cancel &Annuler - + TreeLine - External Link File - + &Go to Target - - Enable click-on-&target + + Internal Link - - Disable click-on-&target + + &Open Picture - - Internal Link + + Picture Link - - Unique ID + + TreeLine - Picture File - - &Open Picture + + Set to &Now - - Picture Link + + Clear &Link - - TreeLine - Picture File + + (Click link target in tree) - - - dataeditview - - Unique ID + + link exports - + Bookmarks Favoris - + TreeLine - Export HTML - + TreeLine - Export Text Titles - + TreeLine - Export Plain Text - + TreeLine - Export Text Tables - + TreeLine - Export Generic XML - + TreeLine - Export TreeLine Subtree - + TreeLine - Export ODF Text - + TreeLine - Export HTML Bookmarks - + TreeLine - Export XBEL Bookmarks - + &HTML - + &Text - - &XML - - - - + &ODF Outline - + Book&marks - + &Single HTML page - + Single &HTML page with navigation pane - + Multiple HTML &pages with navigation pane - + Multiple HTML &data tables - + &Tabbed title text &Texte des titres tabulés - + &Unformatted output of all text - - Tab &delimited table of children - - - - - Generic &XML output - - - - - &Subtree in TreeLine format - - - - + &HTML format bookmarks - + &XBEL format bookmarks - + File Export - + Choose export format type - + Choose export format subtype - + Choose export options - + What to Export Que faut−il exporter - + &Entire tree Arbre &Entier - + Selected &branches &Branches sélectionnées - + Selected &nodes &Nœuds sélectionnés - + Other Options - + &Include root node - &Inclure le nœud racine + &Inclure le nœud racine - + &Only open node children - + Include &print header && footer - + &Columns - + Navigation pane &levels + + + Error - export template files not found. +Check your TreeLine installation. + + + + + Error - cannot link to unsaved TreeLine file. +Save the file and retry. + + + + + Warning - no relative path from "{0}" to "{1}". +Continue with absolute path? + + + + + Parent + + + + + Tree&Line + + + + + &XML (generic) + + + + + Live tree view, linked to TreeLine file (for web server) + + + + + Live tree view, single file (embedded data) + + + + + &Comma delimited (CSV) table of descendants (level numbers) + + + + + Comma &delimited (CSV) table of children (single level) + + + + + Tab &delimited table of children (&single level) + + + + + &Old TreeLine (2.0.x) + + + + + &TreeLine Subtree + + + + + &Include root nodes + + + + + Must select nodes prior to export + + fieldformat - + yes/no oui/non - + true/false vrai/faux - + T/F V/F - + Y/N O/N @@ -1364,7 +1434,7 @@ - + Boolean Booléen @@ -1379,12 +1449,12 @@ Heure - + Choice Choix - + AutoChoice ChoixAuto @@ -1394,12 +1464,12 @@ Combinaison - + AutoCombination - + ExternalLink @@ -1409,308 +1479,313 @@ LienInterne - + Picture Image - + RegularExpression - + Now Maintenant - + Optional Digit# - + Required Digit0 - + Digit or Space (external)<space> - + Decimal Point. - + Decimal Comma, - - Comma Separator, - - - - - Dot Separator. - - - - + Space Separator (internal)<space> - + Optional Sign- - + Required Sign+ - + Exponent (capital)E - + Exponent (small)e - + Number1 - + Capital LetterA - + Small Lettera - + Capital Roman NumeralI - + Small Roman Numerali - + Level Separator/ - + Section Separator. - + "/" Character// - + "." Character.. - + Outline ExampleI../A../1../a)/i) - + Section Example1.1.1.1 - + Separator/ - + Example1/2/3/4 - - Day (1 or 2 digits)d + + Any Character. + + + + + End of Text$ - - Day (2 digits)dd + + 0 Or More Repetitions* - - Weekday Abbreviationddd + + 1 Or More Repetitions+ - - Weekday Namedddd + + 0 Or 1 Repetitions? - - Month (1 or 2 digits)M + + Set of Numbers[0-9] - - Month (2 digits)MM + + Lower Case Letters[a-z] - - Month AbbreviationMMM + + Upper Case Letters[A-Z] - - Month NameMMMM + + Not a Number[^0-9] - - Year (2 digits)yy + + Or| - - Year (4 digits)yyyy + + Escape a Special Character\ - - Hour (0-23, 1 or 2 digits)H + + DateTime - - Hour (00-23, 2 digits)HH + + Day (1 or 2 digits)%-d - - Hour (1-12, 1 or 2 digits)h + + Day (2 digits)%d - - Hour (01-12, 2 digits)hh + + Weekday Abbreviation%a - - Minute (1 or 2 digits)m + + Weekday Name%A - - Minute (2 digits)mm + + Month (1 or 2 digits)%-m - - Second (1 or 2 digits)s + + Month (2 digits)%m - - Second (2 digits)ss + + Month Abbreviation%b - - Milliseconds (1 to 3 digits)z + + Month Name%B - - Milliseconds (3 digits)zzz + + Year (2 digits)%y - - AM/PMAP + + Year (4 digits)%Y - - am/pmap + + Week Number (0 to 53)%-U - - Any Character. + + Day of year (1 to 366)%-j - - End of Text$ + + Hour (0-23, 1 or 2 digits)%-H - - 0 Or More Repetitions* + + Hour (00-23, 2 digits)%H - - 1 Or More Repetitions+ + + Hour (1-12, 1 or 2 digits)%-I - - 0 Or 1 Repetitions? + + Hour (01-12, 2 digits)%I - - Set of Numbers[0-9] + + Minute (1 or 2 digits)%-M - - Lower Case Letters[a-z] + + Minute (2 digits)%M - - Upper Case Letters[A-Z] + + Second (1 or 2 digits)%-S - - Not a Number[^0-9] + + Second (2 digits)%S - - Or| + + Microseconds (6 digits)%f - - Escape a Special Character\ + + AM/PM%p + + + + + Comma Separator\, + + + + + Dot Separator\. @@ -1740,95 +1815,110 @@ globalref - + TreeLine Files Fichiers TreeLine - + TreeLine Files - Compressed Fichiers TreeLine - Compressé - + TreeLine Files - Encrypted Fichiers TreeLine - Encrypté - + All Files - + HTML Files - + Text Files Fichiers Texte - + XML Files Fichiers XML - + ODF Text Files Fichier Texte ODF - + Treepad Files Fichiers Treepad - + PDF Files + + + CSV (Comma Delimited) Files + + + + + All TreeLine Files + + + + + Old TreeLine Files + + helpview - + &Back &Précédent - + &Forward &Suivant - + &Home &Accueil - + Find: Chercher: - + Find &Previous Chercher &Précédent - + Find &Next Chercher &Suivant - + Text string not found Chaîne de caractères non trouvée - + Tools @@ -1836,724 +1926,809 @@ imports - + &Tab indented text, one node per line - + Tab delimited text table with header &row - - Plain text, one node per &line (CR delimited) - - - - + Plain text &paragraphs (blank line delimited) - + Treepad &file (text nodes only) &Fichier Treepad (noeuds texte seulement) - + &Generic XML (non-TreeLine file) - + Open &Document (ODF) outline - + &HTML bookmarks (Mozilla Format) - + &XML bookmarks (XBEL format) &XML favoris (format XBEL) - + FOLDER REPERTOIRE - + BOOKMARK FAVORIS - + SEPARATOR SEPARATEUR - + Link Lien - + Text Texte - + Import File - + Choose Import Method - + Invalid File - + "{0}" is not a valid TreeLine file. Use an import filter? - + TreeLine - Import File - + Error - could not read file {0} - + Error - improper format in {0} - + TABLE - + Bookmarks Favoris - + Too many entries on Line {0} - - - matheval - - Illegal "{}" characters + + Plain text, one &node per line (CR delimited) - - Child references must be combined in a function + + Bad CSV format on Line {0} - - Illegal syntax in equation + + Co&mma delimited (CSV) text table with level column && header row - - Illegal function present: {0} + + Comma delimited (CSV) text table &with header row - - Illegal object type or operator: {0} + + Other - - - miscdialogs - + + Old Tree&Line File (1.x or 2.x) + + + + + Invalid level number on line {0} + + + + + Invalid level structure + + + + + matheval + + + Illegal "{}" characters + + + + + Child references must be combined in a function + + + + + Illegal syntax in equation + + + + + Illegal function present: {0} + + + + + Illegal object type or operator: {0} + + + + + miscdialogs + + &OK &OK - + &Cancel &Annuler - + Fields Champs - + File Properties - + File Storage Fichier - + &Use file compression - + Use file &encryption - + Spell Check Vérification orthographique - + Language code or dictionary (optional) - + Math Fields - + &Treat blank fields as zeros - + Encrypted File Password Mot de passe encrypté - + Type Password for "{0}": - + Type Password: Entrer le mot de passe: - + Re-Type Password: Entrer à nouveau le mot de passe: - + Remember password during this session Se souvenir du mot de passe pendant cette session - + Zero-length passwords are not permitted Les mots de passe de longueur zéro ne sont pas autorisés - + Re-typed password did not match Le mot de passe entré la deuxième fois ne correspond pas - + Default - Single Line Text - + &Search Text - + What to Search - + Full &data - + &Titles only - + How to Search - + &Key words - + Key full &words - + F&ull phrase - + &Regular expression - + Find Chercher - + Find &Previous Chercher &Précédent - + Find &Next Chercher &Suivant - + Filter - + &Filter - + &End Filter - + &Close &Fermer - + Error - invalid regular expression - + Search string "{0}" not found - + Find and Replace - + Replacement &Text - + Any &match - + Full &words - + Re&gular expression - + &Node Type - + N&ode Fields - + &Find Next - + &Replace &Remplacer - + Replace &All - + [All Types] - + [All Fields] - + Search text "{0}" not found - + Error - replacement failed - + Replaced {0} matches - + Sort Nodes - + What to Sort Que trie−t−on - + &Entire tree Arbre &Entier - + Selected &branches &Branches sélectionnées - + Selection's childre&n Les e&nfants de la sélection - + Selection's &siblings Les frères de la &sélection - + Sort Method Méthode de tri - + &Predefined Key Fields - + Node &Titles - + Sort Direction - + &Forward &Suivant - + &Reverse - + &Apply &Appliquer - + Update Node Numbering - + What to Update - + &Selection's children - + Root Node Noeud Racine - + Include top-level nodes - + Handling Nodes without Numbering Fields - + &Ignore and skip - + &Restart numbers for next siblings - + Reserve &numbers - + TreeLine Numbering - + No numbering fields were found in data types - + File Menu - + File - + Edit Menu - + Edit - + Node Menu - + Node - + Data Menu - + Data - + Tools Menu - + Tools - + View Menu - + View - + Window Menu - + Window - + Help Menu - + Help - + Keyboard Shortcuts Raccourcis clavier - + &Restore Defaults - + Key {0} is already used - + Clear &Key - + --Separator-- −Séparateur− - + Customize Toolbars Personnaliser les barres d''outils - + Toolbar &Size &Taille des barres d'outils - + Small Icons Petites icônes - + Large Icons Grandes icônes - + Toolbar Quantity Nombre de barres d'outls - + &Toolbars &Barre d'Outils - + A&vailable Commands Comma&ndes disponibles - + Tool&bar Commands Commandes des &barres d'outils - + Move &Up Déplacer vers le &haut - + Move &Down &Descendre - + Tree View Font - + Output View Font - + Editor View Font - + TreeLine Plugins - Plugins TreeLine + Plugins TreeLine - + Plugin Modules Loaded - Modules Plugins Chargés + Modules Plugins Chargés + + + + No menu + + + + + TreeLine - Serious Error + + + + + A serious error has occurred. TreeLine could be in an unstable state. +Recommend saving any file changes under another filename and restart TreeLine. + +The debugging info shown below can be copied and emailed to doug101@bellz.org along with +an explanation of the circumstances. + + + + + + Format Menu + + + + + Format + + + + + Customize Fonts + + + + + &Use system default font + + + + + App Default Font + + + + + &Use app default font + nodeformat - + Name Nom @@ -2596,7 +2771,7 @@ Dimanche - + Startup Condition Au Démarrage @@ -2606,189 +2781,174 @@ Ouvrir automatiquement le dernier fichier utilisé - - Show child pane in right hand view - - - - + Show descendants in output view Montrer descendants dans l'aperçu sortie - + Restore tree view states of recent files - + Restore previous window geometry - + Features Available Fonctionnalités disponibles - + Open files in new windows Ouverture des fichiers dans de nouvelles fenetres - + Click node to rename - + Rename new nodes when created Renommer les nouveaux noeuds quand ils sont créés - + Tree drag && drop available Glisser && déposer l'arborescence disponible dans le volet d'exploration - + Show icons in the tree view Afficher les icônes dans le volet d'exploration - - Show node unique IDs in the Data Edit view - - - - + Show math fields in the Data Edit view - + Show numbering fields in the Data Edit view - + Undo Memory Mémoire utilisée pour la fonctionnalité "Annuler" - + Number of undo levels Nombre de niveaux d'annulation - + Auto Save Sauvegarde automatique - + Minutes between saves (set to 0 to disable) - + Recent Files Fichiers récents - + Number of recent files in the file menu - + Data Editor Formats Formats de l'Editeur de Données - + Times Heures - + Dates Dates - + First day of week - + Appearance Apparence - + Child indent offset (in font height units) - - Printer Settings - - - - + Spell Check - Vérification orthographique + Vérification orthographique - - Fonts + + Show breadcrumb ancestor view - - Window Geometry + + Show child pane in right hand views - - Toolbar Geometry + + Remove inaccessible recent file entries - - Toolbar Commands + + Activate data editors on mouse hover options - + Choose configuration file location - + User's home directory (recommended) - + Program directory (for portable use) - + &OK &OK - + &Cancel &Annuler @@ -2796,554 +2956,588 @@ printdata - + Error initializing printer Erreur à l'initialisation de l'imprimante - + TreeLine - Export PDF + + + Warning: Page size and margin settings unsupported on current printer. +Save page adjustments? + + + + + Warning: Page size setting unsupported on current printer. +Save adjustment? + + + + + Warning: Margin settings unsupported on current printer. +Save adjustments? + + printdialogs - + Print Preview AperçuAvantImpression - + &Print... &Imprimer.... - + &General Options Options &Générales - + &Font Selection &Sélection Police - + &Header/Footer &En−tête/PiedDePage - + Print Pre&view... Aperçu a&vant impression... - + &OK &OK - + &Cancel &Annuler - + What to print Qu'imprimer  - + &Entire tree Arbre &Entier - + Selected &branches &Branches sélectionnées - + Selected &nodes &Nœuds sélectionnés - + Features Fonctionnalités - + &Include root node &Inclure le nœud racine - + &Keep first child with parent &Garder le premier enfant avec le parent - + Letter (8.5 x 11 in.) Lettre (8.5 × 11 pouces)  - + Legal (8.5 x 14 in.) Légal (8.5 × 14 pouces) - + Tabloid (11 x 17 in.) Tabloïde (11 × 17 pouces) - + A3 (279 x 420 mm) A3 (279 × 420 mm) - + A4 (210 x 297 mm) A4 (210 × 297 mm) - + A5 (148 x 210 mm) A5 (148 × 210 mm) - + Paper &Size &Taille papier - + Orientation Orientation - + &Units &Unités - + Columns Colonnes - + &Number of columns &Nombre de colonnes - + Default Font Police par défaut - + Select Font Sélectionner police - + &Font &Police - + Font st&yle St&yle de police - + Sample Exemple - + &Header Left &En-tête gauche - + Header C&enter En-tête C&entré - + Footer &Left Pied de page &Gauche - + Footer Ce&nter Pied de page Ce&ntré - + Fiel&ds Cham&ps - + Header and Footer En-tête et Pied de pages - + Extra Text Texte Supplémentaire - + &Prefix &Préfixe - + Format &Help Format de &l'Aide - + Fit Width - + Fit Page - + Zoom In - + Zoom Out - + Previous Page - + Next Page - + Single Page - + Facing Pages - + Print Setup - + Print - + Printing Setup - + Page &Setup - + Included Nodes - + Onl&y open node children - + &Draw lines to children - + Indent - + Indent Offse&t (line height units) - + Custom Size - + Inches (in) - + Millimeters (mm) - + Centimeters (cm) - + &Width: - + Height: - + Portra&it - + Lan&dscape - + Margins - + &Left: - + &Top: - + &Right: - + &Bottom: - + He&ader: - + Foot&er: - + Space between colu&mns - - &Use system default font - - - - + &Use TreeLine output view font - + Si&ze - + AaBbCcDdEeFfGg...TtUuVvWvXxYyZz - + Header &Right - + Footer Righ&t - + Field For&mat - + Field Format for "{0}" - + Output &Format - + &Suffix + + + Error: Page size or margins are invalid + + + + + TreeLine PDF Printer + + + + + Select &Printer + + recentfiles Recent Files - Fichiers récents + Fichiers récents spellcheck - + Could not find either aspell.exe, ispell.exe or hunspell.exe Browse for location? - + Spell Check Error Erreur Correction Orthographe - + Locate aspell.exe, ipsell.exe or hunspell.exe - + Program (*.exe) Programme (*.exe) - + TreeLine Spell Check Error Make sure aspell, ispell or hunspell is installed - + Finished checking the branch Continue from the root branch? - Fin de la vérification de la branche + Fin de la vérification de la branche Continuer à partir de la branche racine? - + TreeLine Spell Check Vérification Orthographique TreeLIne - + Finished spell checking - + Spell Check Vérification orthographique - + Not in Dictionary Pas dans le dictionnaire - + Word: Mot: - + Context: Contexte: - + Suggestions Suggestions - + Ignor&e Ignor&er - + &Ignore All &Ignorer Tout - + &Add &Ajouter - + Add &Lowercase Ajouter &Minuscule - + &Replace &Remplacer - + Re&place All Rem&placer Tout - + &Cancel &Annuler + + + Finished checking the branch +Continue from the top? + + treeformats - + DEFAULT DEFAUT @@ -3351,564 +3545,606 @@ treelocalcontrol - - Warning: duplicate Unique IDs found. - - - - - - Many Unique IDs were re-assigned. - - - - - - The following IDs were re-assigned: - - - - - - -Internal link targets could be affected. - - - - + Error - could not delete backup file {} - + Save changes to {}? - + Save changes? Enregistrer les changements? - + &Save &Enregistrer - + Save File Enregistrer Fichier - + Save the current file - + Save &As... Enregistrer &Sous... - + Save the file with a new name Enregistrer le fichier sous un nouveau nom - + &Export... &Exporter... - + Export the file in various other formats - + Prop&erties... - + Set file parameters like compression and encryption - + P&rint Setup... - + Set margins, page size and other printing options - + Print Pre&view... Aperçu a&vant impression... - + Show a preview of printing results Montrer l'aperçu de l'impression - + &Print... &Imprimer.... - + Print tree output based on current options - + Print &to PDF... - + Export to PDF with current printing options - + &Undo &Annuler - + Undo the previous action Annuler l'action précédente - + &Redo &Rétablir - + Redo the previous undo Rétablir l'annulation précédente - + Cu&t Co&uper - + Cut the branch or text to the clipboard Couper la branche ou le texte dans le presse-papier - + &Copy &Copier - + Copy the branch or text to the clipboard Copier la branche ou le texte dans le presse-papier - + &Paste C&oller - + Paste nodes or text from the clipboard Coller les noeuds ou le texte dans le presse-papier - - P&aste Plain Text - - - - + Paste non-formatted text from the clipboard - + &Bold Font - + Set the current or selected font to bold - + &Italic Font - + Set the current or selected font to italic - + U&nderline Font - + Set the current or selected font to underline - + &Font Size - + Set size of the current or selected text - + Small - + Default - + Large - + Larger - + Largest - + Set Font Size - + Font C&olor... - + Set the color of the current or selected text - + &External Link... - + Add or modify an extrnal web link - + Internal &Link... - + Add or modify an internal node link - + Clear For&matting - + Clear current or selected text formatting - + &Rename - + Rename the current tree entry title - + Insert Sibling &Before Insérer Frère A&vant - + Insert new sibling before selection Insérer le nouveau frère avant la sélection - + Insert Sibling &After Insérer Frère Apr&ès - + Insert new sibling after selection Insérer le nouveau frère après la sélection - + Add &Child - + Add new child to selected parent - + &Delete Node &Supprimer Noeud - + Delete the selected nodes Supprimer les noeuds sélectionnés - + &Indent Node Inden&ter Noeud - + Indent the selected nodes Indenter les noeuds sélectionnés - + &Unindent Node - + Unindent the selected nodes Désindenter les noeuds sélectionnés - + &Move Up &Déplacer En &haut - + Move the selected nodes up Déplacer les noeuds sélectionnés vers le haut - + M&ove Down D&éplacer En bas - + Move the selected nodes down Déplacer les noeuds sélectionnés vers le bas - + Move &First Déplacer &premier - + Move the selected nodes to be the first children Déplacer les nœuds sélectionnés comme premier enfant - + Move &Last Déplacer &dernier - + Move the selected nodes to be the last children Déplacer les nœuds sélectionnés comme dernier enfant - + &Set Node Type - + Set the node type for selected nodes - + Set Node Type - + Copy Types from &File... - + Copy the configuration from another TreeLine file Copier la configuration à partir d'un autre fichier TreeLine - + Flatten &by Category - + Collapse descendants by merging fields - + Add Category &Level... - + Insert category nodes above children Insérer les noeuds du classement au-dessus des fils - - Flatten b&y Link... - - - - - Collapse descendants and insert parent links - - - - - &Arrange by Link... - - - - - Arrange descendants using parent links - - - - + &Spell Check... - - Spell check the tree')s text data - - - - + &New Window j&Nouvelle Fenetre - + Open a new window for the same file - + Error - could not write to {} - + TreeLine - Save As - + Error - could not write to file - + TreeLine - Open Configuration File - + Error - could not read file {0} - + Cannot expand without common fields Impossible de développer sans champs communs - + Category Fields Champ du Classement - + Select fields for new level Sélection des champs pour le nouveau niveau - - Flatten by Link + + File saved - - Enter a new field name for parent links + + Pa&ste Plain Text - - Link Field + + Paste C&hild - - Select field with links to parents + + Paste a child node from the clipboard - - File saved + + Paste Sibling &Before + + + + + Paste a sibling before selection + + + + + Paste Sibling &After + + + + + Paste a sibling after selection + + + + + Paste Cl&oned Child + + + + + Paste a child clone from the clipboard + + + + + Paste Clo&ned Sibling Before + + + + + Paste a sibling clone before selection + + + + + Paste Clone&d Sibling After + + + + + Paste a sibling clone after selection + + + + + Clone All &Matched Nodes + + + + + Convert all matching nodes into clones + + + + + &Detach Clones + + + + + Detach all cloned nodes in current branches + + + + + S&wap Category Levels + + + + + Swap child and grandchild category nodes + + + + + Converted {0} branches into clones + + + + + No identical nodes found + + + + + Warning - file corruption! +Skipped bad child references in the following nodes: + + + + + Spell check the tree's text data treemaincontrol - + Warning: Could not create local socket @@ -3918,394 +4154,408 @@ - + Error - could not read file {0} - - Error - {0} is not a valid TreeLine file - - - - + Backup file "{}" exists. A previous session may have crashed - + &Restore Backup &Restaure Sauvegarde - + &Delete Backup &Supprime Sauvegarde - + &Cancel File Open &Annule Fichier Ouvert - + Error - could not rename "{0}" to "{1}" - + Error - could not remove backup file {} - - Warning - could not load plugin module {0} - - - - + &New... &Nouveau… - + New File Nouveau Fichier - + Start a new file Commencer un nouveau fichier - + &Open... &Ouvir... - + Open File Ouvrir Fichier - + Open a file from disk Ouvrir un ficier à partir du disque - + Open Sa&mple... Ouvrir Exa&mple... - + Open Sample - + Open a sample file - + &Import... - + Open a non-TreeLine file - + &Quit &Quitter - + Exit the application Quitter l'application - + &Select All - + Select all text in an editor - + &Configure Data Types... &Paramètres du Type de Données... - + Modify data types, fields & output lines Modifier les types de données, les champs & les lignes en sortie - + Sor&t Nodes... - + Define node sort operations - + Update &Numbering... - + Update node numbering fields - + &Find Text... - + Find text in node titles & data - + &Conditional Find... - + Use field conditions to find nodes - + Find and &Replace... - + Replace text strings in node data - + &Text Filter... - + Filter nodes to only show text matches - + C&onditional Filter... - + Use field conditions to filter nodes - + &General Options... &Options Générales... - + Set user preferences for all files Configurer les préférences utilisateur pour tous les fichiers - + Set &Keyboard Shortcuts... Fixe les raccourcis &claviers... - + Customize keyboard commands Personnalise les commandes au clavier - + C&ustomize Toolbars... - + Customize toolbar buttons Personnalise les boutons des barres d'outils - + Customize Fo&nts... - + Customize fonts in various views - + &Basic Usage... - + Display basic usage instructions - + &Full Documentation... - + Open a TreeLine file with full documentation - + &About TreeLine... - + Display version info about this program - - &About &Plugins... - - - - + Show loaded plugin modules - Afficher les modules plugin chargés + Afficher les modules plugin chargés - + &Select Template &Sélectionner un modèle - + TreeLine - Open File - + Open Sample File - + &Select Sample - + Conditional Find - + Conditional Filter Filtre conditionnel - - ToolsFilterCondition - - - - + General Options Options Générales - + Error - basic help file not found - + TreeLine Basic Usage - + Error - documentation file not found - - TreeLine, Version {0} -by {1} + + Error - invalid TreeLine file {0} + + + + + TreeLine version {0} + + + + + written by {0} + + + + + Library versions: treemodel - + Main - Base + Base treenode - + New Nouveau - treeopener + treestructure - - Invalid XML file - - - - - Bad elememnt - not a valid TreeLine file - + + Main + Base treeview - + Filtering by "{0}", found {1} nodes - + Conditional filtering, found {0} nodes + + + Search for: + + + + + Search for: {0} + + + + + Search for: {0} (not found) + + + + + Next: {0} + + + + + Next: {0} (not found) + + treewindow - + Data Output Sortie des données @@ -4315,149 +4565,179 @@ - + Title List Liste des titres - + &Expand Full Branch &Développer Toute la Branche - + Expand all children of the selected nodes - + &Collapse Full Branch &Réduire Toute la Branche - + Collapse all children of the selected nodes - + &Previous Selection Sélection &Précédente - + Return to the previous tree selection - + &Next Selection &Sélection suivante - + Go to the next tree selection in history - + Show Data &Output Montrer une &sortie des données - + Show data output in right view Afficher le volet de données dans la vue de droite - + Show Data &Editor Afficher l'&Editeur de Données - + Show data editor in right view Afficher l'éditeur de données dans la vue de droite - + Show &Title List - + Show title list in right view Afficher la liste des titres dans la vue de droite - + &Show Child Pane - + Toggle showing right-hand child views - - Show Output &Descedants - - - - + Toggle showing output view indented descendants - + &Close Window &Fermer la fenetre - + Close this window - + &File &Fichier - + &Edit &Editer - + &Node - + &Data &Données - + &Tools &Outils - + &View &Affichage - + &Window &Fenetre - + &Help A&ide + + + Start Incremental Search + + + + + Next Incremental Search + + + + + Previous Incremental Search + + + + + Show &Breadcrumb View + + + + + Toggle showing breadcrumb ancestor view + + + + + Show Output &Descendants + + + + + Fo&rmat + + diff -Nru treeline-2.0.2/i18n/translations/treeline_it.ts treeline-3.0.1/i18n/translations/treeline_it.ts --- treeline-2.0.2/i18n/translations/treeline_it.ts 2018-04-08 20:12:57.000000000 +0000 +++ treeline-3.0.1/i18n/translations/treeline_it.ts 2018-10-17 14:52:24.000000000 +0000 @@ -3,446 +3,431 @@ conditional - + starts with - + ends with - + contains - + True - + False - + and - + or - + [All Types] - + Node Type - + &Add New Rule - + &Remove Rule - + &OK - + &Cancel - - R&etrieve Rules... - - - - - &Save Rules... - - - - + Find &Previous - + Find &Next - + &Filter - + &End Filter - + &Close - - Save Rules + + No conditional matches were found - - Enter a descriptive name + + Rule {0} - - No conditional matches were found + + Saved Rules - - Rule {0} + + Name: - - Retrieve Rules + + &Load - - Select rule set to retrieve: + + &Save - - Remove Rule + + &Delete configdialog - + Configure Data Types - + T&ype List - + Typ&e Config - + Field &List - + &Field Config - + O&utput - + &Show Advanced - + &OK - + &Apply - + &Reset - + &Cancel - + &Hide Advanced - + Error - circular reference in math field equations - + Add or Remove Data Types - + &New Type... - + Co&py Type... - + Rena&me Type... - + &Delete Type - + Add Type - + Enter new type name: - + Copy Type - + &Derive from original - + Rename Type - + Rename from {} to: - + Cannot delete data type being used by nodes - + [None] no type set - + &Data Type - + Default Child &Type - + Icon - + Change &Icon - + Output Options - + Add &blank lines between nodes - + Allow &HTML rich text in format - + Add text bullet&s - + Use a table for field &data - + Combination && Child List Output &Separator - - Uni&que ID Reference Field - - - - + Derived from &Generic Type - + Automatic Types - + None - + Modify Co&nditional Types - + Create Co&nditional Types - + Set Types Conditionally - + Modify &Field List - + Name - + Type - + Sort Key - + Move U&p - + Move Do&wn - + &New Field... - + Rena&me Field... - + Dele&te Field - + Sort &Keys... - + fwd - + rev - + Add Field - + Enter new field name: - + Rename Field - + File Info Reference - + F&ield - + &Field Type @@ -497,814 +482,894 @@ - + F&ield List - + &Title Format - + Out&put Format - + Other Field References - + Reference Le&vel - + Refere&nce Type - + The name cannot be empty - + The name must start with a letter - + The name cannot start with "xml" - + The name cannot contain spaces - + The following characters are not allowed: {} - + The name was already used - + Set Data Type Icon - + Clear &Select - + forward - + reverse - + Sort Key Fields - + Available &Fields - + &Sort Criteria - + Field - + Direction - + Move &Up - + &Move Down - + Flip &Direction - + Self Reference - + Parent Reference - + Root Reference - + Child Reference - + Child Count - - Numeric Result - - - - + Date Result - + Time Result - + Boolean Result - + Text Result - + Arithmetic Operators - + Comparison Operators - + Text Operators - + add - + subtract - + multiply - + divide - + floor divide - + modulus - + power - + sum of items - + maximum - + minimum - + average - + absolute value - + square root - + natural logarithm - + base-10 logarithm - + factorial - + round to num digits - + lower integer - + higher integer - + truncated integer - + floating point - + sine of radians - + cosine of radians - + tangent of radians - + arc sine - + arc cosine - + arc tangent - + radians to degrees - + degrees to radians - + pi constant - + natural log constant - + equal to - + less than - + greater than - + less than or equal to - + greater than or equal to - + not equal to - + true value, condition, false value - + logical and - + logical or - + true if 1st text arg starts with 2nd arg - + true if 1st text arg ends with 2nd arg - + true if 1st text arg contains 2nd arg - + concatenate text - + join text using 1st arg as separator - + convert text to upper case - + convert text to lower case - + in 1st arg, replace 2nd arg with 3rd arg - + Define Math Field Equation - + Field References - + Reference &Level - + Reference &Type - + Available &Field List - + &Result Type - + Operations - + O&perator Type - + Oper&ator List - + Description - + &Equation - + Equation error: {} + + + Output HTML + + + + + Evaluate &HTML tags + + + + + Child Type Limits + + + + + [All Types Available] + + + + + &Select All + + + + + Select &None + + + + + Number Result + + dataeditors - + Today's &Date - + &Open Link - + Open &Folder - + External Link - + Scheme - + &Browse for File - + File Path Type - + Absolute - + Relative - + Address - + Display Name - + &OK - + &Cancel - + TreeLine - External Link File - + &Go to Target - - Enable click-on-&target + + Internal Link - - Disable click-on-&target + + &Open Picture - - Internal Link + + Picture Link - - Unique ID + + TreeLine - Picture File - - &Open Picture + + Set to &Now - - Picture Link + + Clear &Link - - TreeLine - Picture File + + (Click link target in tree) - - - dataeditview - - Unique ID + + link exports - + Bookmarks - + TreeLine - Export HTML - + TreeLine - Export Text Titles - + TreeLine - Export Plain Text - + TreeLine - Export Text Tables - + TreeLine - Export Generic XML - + TreeLine - Export TreeLine Subtree - + TreeLine - Export ODF Text - + TreeLine - Export HTML Bookmarks - + TreeLine - Export XBEL Bookmarks - + &HTML - + &Text - - &XML - - - - + &ODF Outline - + Book&marks - + &Single HTML page - + Single &HTML page with navigation pane - + Multiple HTML &pages with navigation pane - + Multiple HTML &data tables - + &Tabbed title text - + &Unformatted output of all text - - Tab &delimited table of children - - - - - Generic &XML output - - - - - &Subtree in TreeLine format - - - - + &HTML format bookmarks - + &XBEL format bookmarks - + File Export - + Choose export format type - + Choose export format subtype - + Choose export options - + What to Export - + &Entire tree - + Selected &branches - + Selected &nodes - + Other Options - - &Include root node - - - - + &Only open node children - + Include &print header && footer - + &Columns - + Navigation pane &levels + + + Error - export template files not found. +Check your TreeLine installation. + + + + + Error - cannot link to unsaved TreeLine file. +Save the file and retry. + + + + + Warning - no relative path from "{0}" to "{1}". +Continue with absolute path? + + + + + Parent + + + + + Tree&Line + + + + + &XML (generic) + + + + + Live tree view, linked to TreeLine file (for web server) + + + + + Live tree view, single file (embedded data) + + + + + &Comma delimited (CSV) table of descendants (level numbers) + + + + + Comma &delimited (CSV) table of children (single level) + + + + + Tab &delimited table of children (&single level) + + + + + &Old TreeLine (2.0.x) + + + + + &TreeLine Subtree + + + + + &Include root nodes + + + + + Must select nodes prior to export + + fieldformat @@ -1344,7 +1409,7 @@ - + Boolean @@ -1359,12 +1424,12 @@ - + Choice - + AutoChoice @@ -1374,12 +1439,12 @@ - + AutoCombination - + ExternalLink @@ -1389,328 +1454,333 @@ - + Picture - + RegularExpression - + Now - + Optional Digit# - + Required Digit0 - + Digit or Space (external)<space> - + Decimal Point. - + Decimal Comma, - - Comma Separator, - - - - - Dot Separator. - - - - + Space Separator (internal)<space> - + Optional Sign- - + Required Sign+ - + Exponent (capital)E - + Exponent (small)e - + Number1 - + Capital LetterA - + Small Lettera - + Capital Roman NumeralI - + Small Roman Numerali - + Level Separator/ - + Section Separator. - + "/" Character// - + "." Character.. - + Outline ExampleI../A../1../a)/i) - + Section Example1.1.1.1 - + Separator/ - + Example1/2/3/4 - + yes/no - + true/false - + T/F - + Y/N - - Day (1 or 2 digits)d + + Any Character. - - Day (2 digits)dd + + End of Text$ - - Weekday Abbreviationddd + + 0 Or More Repetitions* - - Weekday Namedddd + + 1 Or More Repetitions+ - - Month (1 or 2 digits)M + + 0 Or 1 Repetitions? - - Month (2 digits)MM + + Set of Numbers[0-9] - - Month AbbreviationMMM + + Lower Case Letters[a-z] - - Month NameMMMM + + Upper Case Letters[A-Z] - - Year (2 digits)yy + + Not a Number[^0-9] - - Year (4 digits)yyyy + + Or| - - Hour (0-23, 1 or 2 digits)H + + Escape a Special Character\ - - Hour (00-23, 2 digits)HH + + DateTime - - Hour (1-12, 1 or 2 digits)h + + Day (1 or 2 digits)%-d - - Hour (01-12, 2 digits)hh + + Day (2 digits)%d - - Minute (1 or 2 digits)m + + Weekday Abbreviation%a - - Minute (2 digits)mm + + Weekday Name%A - - Second (1 or 2 digits)s + + Month (1 or 2 digits)%-m - - Second (2 digits)ss + + Month (2 digits)%m - - Milliseconds (1 to 3 digits)z + + Month Abbreviation%b - - Milliseconds (3 digits)zzz + + Month Name%B - - AM/PMAP + + Year (2 digits)%y - - am/pmap + + Year (4 digits)%Y - - Any Character. + + Week Number (0 to 53)%-U - - End of Text$ + + Day of year (1 to 366)%-j - - 0 Or More Repetitions* + + Hour (0-23, 1 or 2 digits)%-H - - 1 Or More Repetitions+ + + Hour (00-23, 2 digits)%H - - 0 Or 1 Repetitions? + + Hour (1-12, 1 or 2 digits)%-I - - Set of Numbers[0-9] + + Hour (01-12, 2 digits)%I - - Lower Case Letters[a-z] + + Minute (1 or 2 digits)%-M - - Upper Case Letters[A-Z] + + Minute (2 digits)%M - - Not a Number[^0-9] + + Second (1 or 2 digits)%-S - - Or| + + Second (2 digits)%S - - Escape a Special Character\ + + Microseconds (6 digits)%f + + + + + AM/PM%p + + + + + Comma Separator\, + + + + + Dot Separator\. @@ -1740,95 +1810,110 @@ globalref - + TreeLine Files - + TreeLine Files - Compressed - + TreeLine Files - Encrypted - + All Files - + HTML Files - + Text Files - + XML Files - + ODF Text Files - + Treepad Files - + PDF Files + + + CSV (Comma Delimited) Files + + + + + All TreeLine Files + + + + + Old TreeLine Files + + helpview - + Tools - + &Back - + &Forward - + &Home - + Find: - + Find &Previous - + Find &Next - + Text string not found @@ -1836,724 +1921,799 @@ imports - + &Tab indented text, one node per line - + Tab delimited text table with header &row - - Plain text, one node per &line (CR delimited) - - - - + Plain text &paragraphs (blank line delimited) - + Treepad &file (text nodes only) - + &Generic XML (non-TreeLine file) - + Open &Document (ODF) outline - + &HTML bookmarks (Mozilla Format) - + &XML bookmarks (XBEL format) - + FOLDER - + BOOKMARK - + SEPARATOR - + Link - + Text - + Import File - + Choose Import Method - + Invalid File - + "{0}" is not a valid TreeLine file. Use an import filter? - + TreeLine - Import File - + Error - could not read file {0} - + Error - improper format in {0} - + TABLE - + Bookmarks - + Too many entries on Line {0} - - - matheval - - Illegal "{}" characters + + Plain text, one &node per line (CR delimited) - - Child references must be combined in a function + + Bad CSV format on Line {0} - - Illegal syntax in equation + + Co&mma delimited (CSV) text table with level column && header row - - Illegal function present: {0} + + Comma delimited (CSV) text table &with header row - - Illegal object type or operator: {0} + + Other + + + + + Old Tree&Line File (1.x or 2.x) + + + + + Invalid level number on line {0} + + + + + Invalid level structure + + + + + matheval + + + Illegal "{}" characters + + + + + Child references must be combined in a function + + + + + Illegal syntax in equation + + + + + Illegal function present: {0} + + + + + Illegal object type or operator: {0} miscdialogs - + &OK - + &Cancel - + Fields - + File Properties - + File Storage - + &Use file compression - + Use file &encryption - + Spell Check - + Language code or dictionary (optional) - + Math Fields - + &Treat blank fields as zeros - + Encrypted File Password - + Type Password for "{0}": - + Type Password: - + Re-Type Password: - + Remember password during this session - + Zero-length passwords are not permitted - + Re-typed password did not match - + Default - Single Line Text - + &Search Text - + What to Search - + Full &data - + &Titles only - + How to Search - + &Key words - + Key full &words - + F&ull phrase - + &Regular expression - + Find - + Find &Previous - + Find &Next - + Filter - + &Filter - + &End Filter - + &Close - + Error - invalid regular expression - + Search string "{0}" not found - + Find and Replace - + Replacement &Text - + Any &match - + Full &words - + Re&gular expression - + &Node Type - + N&ode Fields - + &Find Next - + &Replace - + Replace &All - + [All Types] - + [All Fields] - + Search text "{0}" not found - + Error - replacement failed - + Replaced {0} matches - + Sort Nodes - + What to Sort - + &Entire tree - + Selected &branches - + Selection's childre&n - + Selection's &siblings - + Sort Method - + &Predefined Key Fields - + Node &Titles - + Sort Direction - + &Forward - + &Reverse - + &Apply - + Update Node Numbering - + What to Update - + &Selection's children - + Root Node - + Include top-level nodes - + Handling Nodes without Numbering Fields - + &Ignore and skip - + &Restart numbers for next siblings - + Reserve &numbers - + TreeLine Numbering - + No numbering fields were found in data types - + File Menu - + File - + Edit Menu - + Edit - + Node Menu - + Node - + Data Menu - + Data - + Tools Menu - + Tools - + View Menu - + View - + Window Menu - + Window - + Help Menu - + Help - + Keyboard Shortcuts - + &Restore Defaults - + Key {0} is already used - + Clear &Key - + --Separator-- - + Customize Toolbars - + Toolbar &Size - + Small Icons - + Large Icons - + Toolbar Quantity - + &Toolbars - + A&vailable Commands - + Tool&bar Commands - + Move &Up - + Move &Down - + Tree View Font - + Output View Font - + Editor View Font - - TreeLine Plugins + + No menu + + + + + TreeLine - Serious Error - - Plugin Modules Loaded + + A serious error has occurred. TreeLine could be in an unstable state. +Recommend saving any file changes under another filename and restart TreeLine. + +The debugging info shown below can be copied and emailed to doug101@bellz.org along with +an explanation of the circumstances. + + + + + + Format Menu + + + + + Format + + + + + Customize Fonts + + + + + &Use system default font + + + + + App Default Font + + + + + &Use app default font nodeformat - + Name @@ -2596,7 +2756,7 @@ - + Startup Condition @@ -2606,189 +2766,169 @@ - - Show child pane in right hand view - - - - + Show descendants in output view - + Restore tree view states of recent files - + Restore previous window geometry - + Features Available - + Open files in new windows - + Click node to rename - + Rename new nodes when created - + Tree drag && drop available - + Show icons in the tree view - - Show node unique IDs in the Data Edit view - - - - + Show math fields in the Data Edit view - + Show numbering fields in the Data Edit view - + Undo Memory - + Number of undo levels - + Auto Save - + Minutes between saves (set to 0 to disable) - + Recent Files - + Number of recent files in the file menu - + Data Editor Formats - + Times - + Dates - + First day of week - + Appearance - + Child indent offset (in font height units) - - Printer Settings - - - - - Spell Check - - - - - Fonts + + Show breadcrumb ancestor view - - Window Geometry + + Show child pane in right hand views - - Toolbar Geometry + + Remove inaccessible recent file entries - - Toolbar Commands + + Activate data editors on mouse hover options - + Choose configuration file location - + User's home directory (recommended) - + Program directory (for portable use) - + &OK - + &Cancel @@ -2796,553 +2936,573 @@ printdata - + Error initializing printer - + TreeLine - Export PDF + + + Warning: Page size and margin settings unsupported on current printer. +Save page adjustments? + + + + + Warning: Page size setting unsupported on current printer. +Save adjustment? + + + + + Warning: Margin settings unsupported on current printer. +Save adjustments? + + printdialogs - + Print Preview - + Fit Width - + Fit Page - + Zoom In - + Zoom Out - + Previous Page - + Next Page - + Single Page - + Facing Pages - + Print Setup - + Print - + Printing Setup - + &General Options - + Page &Setup - + &Font Selection - + &Header/Footer - + Print Pre&view... - + &Print... - + &OK - + &Cancel - + What to print - + &Entire tree - + Selected &branches - + Selected &nodes - + Included Nodes - + &Include root node - + Onl&y open node children - + Features - + &Draw lines to children - + &Keep first child with parent - + Indent - + Indent Offse&t (line height units) - + Letter (8.5 x 11 in.) - + Legal (8.5 x 14 in.) - + Tabloid (11 x 17 in.) - + A3 (279 x 420 mm) - + A4 (210 x 297 mm) - + A5 (148 x 210 mm) - + Custom Size - + Inches (in) - + Millimeters (mm) - + Centimeters (cm) - + &Units - + Paper &Size - + &Width: - + Height: - + Orientation - + Portra&it - + Lan&dscape - + Margins - + &Left: - + &Top: - + &Right: - + &Bottom: - + He&ader: - + Foot&er: - + Columns - + &Number of columns - + Space between colu&mns - + Default Font - - &Use system default font - - - - + &Use TreeLine output view font - + Select Font - + &Font - + Font st&yle - + Si&ze - + Sample - + AaBbCcDdEeFfGg...TtUuVvWvXxYyZz - + &Header Left - + Header C&enter - + Header &Right - + Footer &Left - + Footer Ce&nter - + Footer Righ&t - + Fiel&ds - + Field For&mat - + Header and Footer - + Field Format for "{0}" - + Output &Format - + Format &Help - + Extra Text - + &Prefix - + &Suffix - - - recentfiles - - Recent Files + + Error: Page size or margins are invalid + + + + + TreeLine PDF Printer + + + + + Select &Printer spellcheck - + Could not find either aspell.exe, ispell.exe or hunspell.exe Browse for location? - + Spell Check Error - + Locate aspell.exe, ipsell.exe or hunspell.exe - + Program (*.exe) - + TreeLine Spell Check Error Make sure aspell, ispell or hunspell is installed - - Finished checking the branch -Continue from the root branch? - - - - + TreeLine Spell Check - + Finished spell checking - + Spell Check - + Not in Dictionary - + Word: - + Context: - + Suggestions - + Ignor&e - + &Ignore All - + &Add - + Add &Lowercase - + &Replace - + Re&place All - + &Cancel + + + Finished checking the branch +Continue from the top? + + treeformats - + DEFAULT @@ -3350,564 +3510,606 @@ treelocalcontrol - - Warning: duplicate Unique IDs found. - - - - - - Many Unique IDs were re-assigned. - - - - - - The following IDs were re-assigned: - - - - - - -Internal link targets could be affected. - - - - + Error - could not delete backup file {} - + Save changes to {}? - + Save changes? - + &Save - + Save File - + Save the current file - + Save &As... - + Save the file with a new name - + &Export... - + Export the file in various other formats - + Prop&erties... - + Set file parameters like compression and encryption - + P&rint Setup... - + Set margins, page size and other printing options - + Print Pre&view... - + Show a preview of printing results - + &Print... - + Print tree output based on current options - + Print &to PDF... - + Export to PDF with current printing options - + &Undo - + Undo the previous action - + &Redo - + Redo the previous undo - + Cu&t - + Cut the branch or text to the clipboard - + &Copy - + Copy the branch or text to the clipboard - + &Paste - + Paste nodes or text from the clipboard - - P&aste Plain Text - - - - + Paste non-formatted text from the clipboard - + &Bold Font - + Set the current or selected font to bold - + &Italic Font - + Set the current or selected font to italic - + U&nderline Font - + Set the current or selected font to underline - + &Font Size - + Set size of the current or selected text - + Small - + Default - + Large - + Larger - + Largest - + Set Font Size - + Font C&olor... - + Set the color of the current or selected text - + &External Link... - + Add or modify an extrnal web link - + Internal &Link... - + Add or modify an internal node link - + Clear For&matting - + Clear current or selected text formatting - + &Rename - + Rename the current tree entry title - + Insert Sibling &Before - + Insert new sibling before selection - + Insert Sibling &After - + Insert new sibling after selection - + Add &Child - + Add new child to selected parent - + &Delete Node - + Delete the selected nodes - + &Indent Node - + Indent the selected nodes - + &Unindent Node - + Unindent the selected nodes - + &Move Up - + Move the selected nodes up - + M&ove Down - + Move the selected nodes down - + Move &First - + Move the selected nodes to be the first children - + Move &Last - + Move the selected nodes to be the last children - + &Set Node Type - + Set the node type for selected nodes - + Set Node Type - + Copy Types from &File... - + Copy the configuration from another TreeLine file - + Flatten &by Category - + Collapse descendants by merging fields - + Add Category &Level... - + Insert category nodes above children - - Flatten b&y Link... + + &Spell Check... - - Collapse descendants and insert parent links + + &New Window - - &Arrange by Link... + + Open a new window for the same file - - Arrange descendants using parent links + + Error - could not write to {} - - &Spell Check... + + File saved - - Spell check the tree')s text data + + TreeLine - Save As - - &New Window + + Error - could not write to file - - Open a new window for the same file + + TreeLine - Open Configuration File - - Error - could not write to {} + + Error - could not read file {0} - - File saved + + Cannot expand without common fields - - TreeLine - Save As + + Category Fields - - Error - could not write to file + + Select fields for new level - - TreeLine - Open Configuration File + + Pa&ste Plain Text - - Error - could not read file {0} + + Paste C&hild - - Cannot expand without common fields + + Paste a child node from the clipboard - - Category Fields + + Paste Sibling &Before - - Select fields for new level + + Paste a sibling before selection + + + + + Paste Sibling &After + + + + + Paste a sibling after selection + + + + + Paste Cl&oned Child + + + + + Paste a child clone from the clipboard + + + + + Paste Clo&ned Sibling Before + + + + + Paste a sibling clone before selection + + + + + Paste Clone&d Sibling After - - Flatten by Link + + Paste a sibling clone after selection + + + + + Clone All &Matched Nodes + + + + + Convert all matching nodes into clones + + + + + &Detach Clones + + + + + Detach all cloned nodes in current branches - - Enter a new field name for parent links + + S&wap Category Levels - - Link Field + + Swap child and grandchild category nodes - - Select field with links to parents + + Converted {0} branches into clones + + + + + No identical nodes found + + + + + Warning - file corruption! +Skipped bad child references in the following nodes: + + + + + Spell check the tree's text data treemaincontrol - + Warning: Could not create local socket @@ -3917,394 +4119,395 @@ - + Error - could not read file {0} - - Error - {0} is not a valid TreeLine file - - - - + Backup file "{}" exists. A previous session may have crashed - + &Restore Backup - + &Delete Backup - + &Cancel File Open - + Error - could not rename "{0}" to "{1}" - + Error - could not remove backup file {} - - Warning - could not load plugin module {0} - - - - + &New... - + New File - + Start a new file - + &Open... - + Open File - + Open a file from disk - + Open Sa&mple... - + Open Sample - + Open a sample file - + &Import... - + Open a non-TreeLine file - + &Quit - + Exit the application - + &Select All - + Select all text in an editor - + &Configure Data Types... - + Modify data types, fields & output lines - + Sor&t Nodes... - + Define node sort operations - + Update &Numbering... - + Update node numbering fields - + &Find Text... - + Find text in node titles & data - + &Conditional Find... - + Use field conditions to find nodes - + Find and &Replace... - + Replace text strings in node data - + &Text Filter... - + Filter nodes to only show text matches - + C&onditional Filter... - + Use field conditions to filter nodes - + &General Options... - + Set user preferences for all files - + Set &Keyboard Shortcuts... - + Customize keyboard commands - + C&ustomize Toolbars... - + Customize toolbar buttons - + Customize Fo&nts... - + Customize fonts in various views - + &Basic Usage... - + Display basic usage instructions - + &Full Documentation... - + Open a TreeLine file with full documentation - + &About TreeLine... - + Display version info about this program - - &About &Plugins... - - - - - Show loaded plugin modules - - - - + &Select Template - + TreeLine - Open File - + Open Sample File - + &Select Sample - + Conditional Find - + Conditional Filter - - ToolsFilterCondition - - - - + General Options - + Error - basic help file not found - + TreeLine Basic Usage - + Error - documentation file not found - - TreeLine, Version {0} -by {1} + + Error - invalid TreeLine file {0} - - - treemodel - - Main + + TreeLine version {0} + + + + + written by {0} + + + + + Library versions: treenode - + New - treeopener + treestructure - - Invalid XML file - - - - - Bad elememnt - not a valid TreeLine file + + Main treeview - + Filtering by "{0}", found {1} nodes - + Conditional filtering, found {0} nodes + + + Search for: + + + + + Search for: {0} + + + + + Search for: {0} (not found) + + + + + Next: {0} + + + + + Next: {0} (not found) + + treewindow - + Data Output @@ -4314,149 +4517,179 @@ - + Title List - + &Expand Full Branch - + Expand all children of the selected nodes - + &Collapse Full Branch - + Collapse all children of the selected nodes - + &Previous Selection - + Return to the previous tree selection - + &Next Selection - + Go to the next tree selection in history - + Show Data &Output - + Show data output in right view - + Show Data &Editor - + Show data editor in right view - + Show &Title List - + Show title list in right view - + &Show Child Pane - + Toggle showing right-hand child views - - Show Output &Descedants - - - - + Toggle showing output view indented descendants - + &Close Window - + Close this window - + &File - + &Edit - + &Node - + &Data - + &Tools - + &View - + &Window - + &Help + + + Start Incremental Search + + + + + Next Incremental Search + + + + + Previous Incremental Search + + + + + Show &Breadcrumb View + + + + + Toggle showing breadcrumb ancestor view + + + + + Show Output &Descendants + + + + + Fo&rmat + + diff -Nru treeline-2.0.2/i18n/translations/treeline_pt.ts treeline-3.0.1/i18n/translations/treeline_pt.ts --- treeline-2.0.2/i18n/translations/treeline_pt.ts 2018-04-08 20:12:57.000000000 +0000 +++ treeline-3.0.1/i18n/translations/treeline_pt.ts 2018-10-17 14:52:24.000000000 +0000 @@ -3,446 +3,471 @@ conditional - + starts with começa com - + ends with acaba com - + contains contém - + True Verdadeiro - + False Falso - + and e - + or ou - + Node Type Tipo de ramo - + &Add New Rule Adicionar &Nova Regra - + &Remove Rule &Eliminar Regra - + &OK &OK - + &Cancel &Cancelar R&etrieve Rules... - &Recuperar regras... + &Recuperar regras... &Save Rules... - &Guardar Regras... + &Guardar Regras... - + Find &Previous Encontrar &Anterior - + Find &Next Encontrar &Seguinte - + &Filter F&iltrar - + &End Filter &Terminar Filtragem - + &Close &Fechar Save Rules - Guardar Regras + Guardar Regras Enter a descriptive name - Introduza um nome descritivo + Introduza um nome descritivo - + No conditional matches were found Nenhum resultado satisfaz as condições - + Rule {0} Regra {0} Retrieve Rules - Reutilizar Regras + Reutilizar Regras Select rule set to retrieve: - Seleccione conjunto de regras para reutilizar: + Seleccione conjunto de regras para reutilizar: Remove Rule - Remover Regra + Remover Regra - + [All Types] [Todos os Tipos] + + + Saved Rules + + + + + Name: + + + + + &Load + + + + + &Save + &Guardar + + + + &Delete + + configdialog - + Configure Data Types Configurar Tipos de Dados - + T&ype List &Lista de Tipos - + Typ&e Config Configuração de &Tipos - + Field &List Lista de &Campos - + &Field Config C&onfiguração de Campos - + O&utput &Apresentação - + &Show Advanced &Mostrar Avançadas - + &OK &OK - + &Apply &Aplicar - + &Reset &Repor - + &Cancel &Cancelar - + &Hide Advanced &Ocultar Avançadas - + Error - circular reference in math field equations Erro - referência cíclica em equações de campos matemáticos - + Add or Remove Data Types Adicionar ou Remover Tipos de Dados - + &New Type... &Novo Tipo... - + Co&py Type... Co&piar Tipo... - + Rena&me Type... &Mudar o Nome... - + &Delete Type &Eliminar Tipo - + Add Type Adicionar Tipo - + Enter new type name: Introduza novo nome para o tipo: - + Copy Type Copiar Tipo - + &Derive from original &Derivadar de Original - + Rename Type Mudar Nome a Tipo - + Rename from {} to: Mudar nome de {} para: - + Cannot delete data type being used by nodes Não é possivel apagar tipo de dados em utilização por ramos - + [None] no type set [Nenhum] - + &Data Type Tipo de &Dados - + Default Child &Type Tipo de &descendente pré definido - + Icon Ícone - + Change &Icon Mudar &Ícone - + Output Options Opções de apresentação - + Add &blank lines between nodes Adicionar linhas &vazias entre ramos - + Allow &HTML rich text in format Permitir texto formatado em &HTML - + Add text bullet&s Adicionar &pontos de tópico - + Use a table for field &data &Utilizar uma tabela para dados dos campos - + Combination && Child List Output &Separator Carácter de &Separação de Combinações e Lista de Descendentes - + Uni&que ID Reference Field - Campo de Indentificador de Referência &Único + Campo de Indentificador de Referência &Único - + Derived from &Generic Type &Derivado de Tipo Genérico - + Automatic Types Tipos Automáticos - + None Nenhum - + Modify Co&nditional Types &Modificar Tipos Condicionais - + Create Co&nditional Types &Criar Tipos Condicionais - + Set Types Conditionally Definir tipos condicionalmente - + Modify &Field List &Modificar Lista de Campos - + Name Nome - + Type Tipo - + Sort Key Prioridade de Ordenamento - + Move U&p Mover para &cima - + Move Do&wn Mover para &Baixo - + &New Field... &Novo Campo... - + Rena&me Field... M&udar Nome... - + Dele&te Field &Eliminar Campo - + Sort &Keys... &Prioridade de Ordenamento... - + fwd Descendente - + rev Ascendente - + Add Field Adicionar Campo - + Enter new field name: Introduza o nome do novo campo: - + Rename Field Mudar Nome - + File Info Reference Informação sobre o ficheiro - + F&ield Ca&mpo - + &Field Type T&ipo de Campo @@ -497,814 +522,947 @@ Definir Equação - + F&ield List Li&sta de Campos - + &Title Format &Formatação do Título - + Out&put Format &Formatação de Apresentação - + Other Field References Referências a outros campo - + Reference Le&vel &Nível de Referência - + Refere&nce Type &Tipo de Referência - + The name cannot be empty O nome não pode ser vazio - + The name must start with a letter O nome deve começar com uma letra - + The name cannot start with "xml" O nome não pode começar com "xml" - + The name cannot contain spaces O nome não pode conter espaços - + The following characters are not allowed: {} Os seguintes carácteres não são permitidos: {} - + The name was already used Nome já em utilização - + Set Data Type Icon Definir Ícone do Tipo de Dados - + Clear &Select &Limpar Selecção - + forward Descendente - + reverse Ascendente - + Sort Key Fields Campos de Ordenamento - + Available &Fields Campos &Disponíveis - + &Sort Criteria &Critério de Ordenamento - + Field Campo - + Direction Direcção - + Move &Up Mover para &Cima - + &Move Down Mover para &Baixo - + Flip &Direction &Inverter Direcção - + Self Reference Auto Referência - + Parent Reference Referência a Ascendentes - + Child Reference Referência a Descendentes - + Child Count Número de Descendentes - + add adicionar - + subtract subtrair - + multiply multiplicar - + divide dividir - + floor divide Divisão arredondada - + modulus resto - + power potência - + sum of items sumatório - + maximum máximo - + minimum mínimo - + average média - + absolute value valor absoluto - + square root raiz quadrada - + natural logarithm logarítmo natural - + base-10 logarithm logarítmo de base 10 - + factorial factorial - + round to num digits arredondar para x dígitos - + lower integer inteiro inferior - + higher integer inteiro superior - + truncated integer inteiro truncado - + floating point ponto flutuante - + sine of radians seno de radianos - + cosine of radians coseno de radianos - + tangent of radians tangente de radianos - + arc sine arco seno - + arc cosine arco coseno - + arc tangent arco tangente - + radians to degrees radianos para graus - + degrees to radians graus para radianos - + pi constant constante pi - + natural log constant constante logarítmo natural - + Define Math Field Equation Definir equação do campo matemático - + Field References Referências a Campos - + Reference &Level &Nível de Referência - + Reference &Type &Tipo de Referência - + Description Descrição - + &Equation &Equação - + Equation error: {} Erro na equação: {} - + Root Reference Referência à Raiz - + Numeric Result - Resultado Numérico + Resultado Numérico - + Date Result Resultado de Data - + Time Result Resultado de Tempo - + Available &Field List Lista de &Campos Disponíveis - + &Result Type &Tipo de Resultados - + Boolean Result Resultado Boleano - + Text Result Resultado Textual - + Arithmetic Operators Operadores Aritméticos - + Comparison Operators Operadores de Comparação - + Text Operators Operadores de Texto - + equal to igual a - + less than menor que - + greater than maior que - + less than or equal to menor ou igual a - + greater than or equal to maior ou igual - + not equal to diferente de - + true value, condition, false value valor verdadeiro, condição, valor falso - + true if 1st text arg starts with 2nd arg verdadeiro se o 1º argumento de texto começar com o 2º argumento - + true if 1st text arg ends with 2nd arg verdadeiro se o 1º argumento de texto terminar com o 2º argumento - + true if 1st text arg contains 2nd arg verdadeiro se o 1º argumento de texto contiver o 2º argumento - + concatenate text concatenar texto - + join text using 1st arg as separator juntar texto utilizando o primeiro argumento como separador - + convert text to upper case converter texto para maiúsculas - + convert text to lower case converter texto para minúsculas - + in 1st arg, replace 2nd arg with 3rd arg no 1º argumento, substituir o 2º argumento pelo 3º argumento - + Operations Operações - + O&perator Type &Tipo de Operador - + Oper&ator List &Lista de Operadores - + logical and e lógico - + logical or ou lógico + + + Output HTML + + + + + Evaluate &HTML tags + + + + + Child Type Limits + + + + + [All Types Available] + + + + + &Select All + &Seleccionar Tudo + + + + Select &None + + + + + Number Result + + dataeditors - + Today's &Date Data de &Hoje - + &Open Link &Abrir Ligação - + External Link Ligação Externa - + Scheme Protocolo - + &Browse for File Procurar &Ficheiro - + File Path Type Tipo de Caminho para o Ficheiro - + Absolute Absoluto - + Relative Relativo - + Address Endereço - + Display Name Texto de Apresentação - + &OK &OK - + &Cancel &Cancelar - + TreeLine - External Link File TreeLine - Ligação para Ficheiro Externo - + &Go to Target &Ir para Destino - + Enable click-on-&target - &Activar escolha-de-destino + &Activar escolha-de-destino - + Disable click-on-&target - &Desctivar escolha-de-destino + &Desctivar escolha-de-destino - + Internal Link Ligação Interna - + Unique ID - Identificador Único + Identificador Único - + &Open Picture &Abrir Imagem - + Picture Link Ligação para imagem - + TreeLine - Picture File TreeLine - Ficheiro de Imagem - + Open &Folder Abrir &Pasta + + + Set to &Now + + + + + Clear &Link + + + + + (Click link target in tree) + + + + + link + + dataeditview - + Unique ID - Identificador Único + Identificador Único exports - + Bookmarks Favoritos - + TreeLine - Export HTML TreeLine - Exportar HTML - + TreeLine - Export Text Titles TreeLine - Exportar Texto de Títulos - + TreeLine - Export Plain Text TreeLine - Exportar Texto Simples - + TreeLine - Export Text Tables TreeLine - Exportar Tabelas de Texto - + TreeLine - Export Generic XML TreeLine - Exportar XML Genérico - + TreeLine - Export TreeLine Subtree TreeLine - Exportar Sub Árvore TreeLine - + TreeLine - Export ODF Text TreeLine - Exportar Texto ODF - + TreeLine - Export HTML Bookmarks TreeLine - Exportar Favoritos em HTML - + TreeLine - Export XBEL Bookmarks TreeLine - Exportar Favoritos em XBEL - + &HTML &HTML - + &Text &Texto - + &XML - &XML + &XML - + &ODF Outline Índice &ODF - + Book&marks &Favoritos - + &Single HTML page HTML Página &Única - + Single &HTML page with navigation pane HTML página única com barra de &navegação - + Multiple HTML &pages with navigation pane &Multiplas páginas HTML com barra de navegação - + Multiple HTML &data tables Multiplas páginas HTML com &tabelas de informação - + &Tabbed title text &Títulos separados por tabulação - + &Unformatted output of all text Conteúdo não &formatado para todos os textos - + Tab &delimited table of children - &Tabela separada por tabulação de todos os descendentes + &Tabela separada por tabulação de todos os descendentes - + Generic &XML output - Exportação em &XML genérico + Exportação em &XML genérico - + &Subtree in TreeLine format - &Sub árvore em formato Treeline + &Sub árvore em formato Treeline - + &HTML format bookmarks Favoritos em formato &HTML - + &XBEL format bookmarks Favoritos em formato &XBEL - + File Export Exportação de ficheiros - + Choose export format type Escolha o formato de exportação - + Choose export format subtype Escolha o subtipo do formato de exportação - + Choose export options Escolha as opções de exportação - + What to Export O que exportar - + &Entire tree Árvore &Inteira - + Selected &branches &Subramos Seleccionados - + Selected &nodes &Ramos Seleccionados - + Other Options Outras Opções - + &Include root node - I&ncluir raiz + I&ncluir raiz - + &Only open node children &Apenas descendentes expandidos - + Include &print header && footer Incluir &cabeçalho e rodapé de impressão - + &Columns &Colunas - + Navigation pane &levels &Níveis da barra de navegação + + + Error - export template files not found. +Check your TreeLine installation. + + + + + Error - cannot link to unsaved TreeLine file. +Save the file and retry. + + + + + Warning - no relative path from "{0}" to "{1}". +Continue with absolute path? + + + + + Parent + + + + + Tree&Line + + + + + &XML (generic) + + + + + Live tree view, linked to TreeLine file (for web server) + + + + + Live tree view, single file (embedded data) + + + + + &Comma delimited (CSV) table of descendants (level numbers) + + + + + Comma &delimited (CSV) table of children (single level) + + + + + Tab &delimited table of children (&single level) + + + + + &Old TreeLine (2.0.x) + + + + + &TreeLine Subtree + + + + + &Include root nodes + + + + + Must select nodes prior to export + + fieldformat @@ -1339,7 +1497,7 @@ Numeração - + Boolean Boleano @@ -1354,12 +1512,12 @@ Tempo - + Choice Escolha - + AutoChoice AutoEscolha @@ -1369,12 +1527,12 @@ Combinação - + AutoCombination AutoCombinação - + ExternalLink LigaçãoExterna @@ -1384,327 +1542,327 @@ LigaçãoInterna - + Picture Imagem - + RegularExpression ExpressãoRegular - + Now Agora - + Optional Digit# Dígito Opcional# - + Required Digit0 Dígito Obrigatório0 - + Digit or Space (external)<space> Dígito ou espaço (externo)<space> - + Decimal Point. Ponto Decimal. - + Decimal Comma, Vírgula Decimal, - + Comma Separator, - Vírgula de Separação, + Vírgula de Separação, - + Dot Separator. - Ponto de Separação. + Ponto de Separação. - + Space Separator (internal)<space> Espaço de Separação (interno)<space> - + Optional Sign- Sinal Opcional- - + Required Sign+ Sinal Obrigatório+ - + Exponent (capital)E Exponente (maiúscula)E - + Exponent (small)e Exponente (minúscula)e - + Number1 Número1 - + Capital LetterA Letra MaiúsculaA - + Small Lettera Letra Minúsculaa - + Capital Roman NumeralI Numeral Romano MaiúsculoI - + Small Roman Numerali Numeral Romano Minúsculoi - + Level Separator/ Separador de Nível/ - + Section Separator. Separador de Secção. - + "/" Character// Carácter "/"// - + "." Character.. Carácter ".".. - + Outline ExampleI../A../1../a)/i) Exemplo de ÍndiceI../A../1../a)/i) - + Section Example1.1.1.1 Exemplo de Secção1.1.1.1 - + Separator/ Separador/ - + Example1/2/3/4 Exemplo1/2/3/4 - + yes/no sim/não - + true/false verdadeiro/falso - + T/F V/F - + Y/N S/N - + Day (1 or 2 digits)d - Dia (1 ou 2 dígitos)d + Dia (1 ou 2 dígitos)d - + Day (2 digits)dd - Dia ( 2 dígitos)dd + Dia ( 2 dígitos)dd - + Weekday Abbreviationddd - Abreviatura de Dia da Semanaddd + Abreviatura de Dia da Semanaddd - + Weekday Namedddd - Nome do Dia da Semanadddd + Nome do Dia da Semanadddd - + Month (1 or 2 digits)M - Mês (1 ou 2 dígitos)M + Mês (1 ou 2 dígitos)M - + Month (2 digits)MM - Mês (2 dígitos)MM + Mês (2 dígitos)MM - + Month AbbreviationMMM - Abreviatura de MêsMMM + Abreviatura de MêsMMM - + Month NameMMMM - Nome do MêsMMMM + Nome do MêsMMMM - + Year (2 digits)yy - Ano (2 dígitos)yy + Ano (2 dígitos)yy - + Year (4 digits)yyyy - Ano (4 dígitos)yyyy + Ano (4 dígitos)yyyy - + Hour (0-23, 1 or 2 digits)H - Hora (0-23, 1 ou 2 dígitos)H + Hora (0-23, 1 ou 2 dígitos)H - + Hour (00-23, 2 digits)HH - Hora (00-23, 2 dígitos)HH + Hora (00-23, 2 dígitos)HH - + Hour (1-12, 1 or 2 digits)h - Hora (1-12, 1 ou 2 dígitos)h + Hora (1-12, 1 ou 2 dígitos)h - + Hour (01-12, 2 digits)hh - Hora (01-12, 2 dígitos)hh + Hora (01-12, 2 dígitos)hh - + Minute (1 or 2 digits)m - Minuto (1 ou 2 dígitos)m + Minuto (1 ou 2 dígitos)m - + Minute (2 digits)mm - Minuto (2 dígitos)mm + Minuto (2 dígitos)mm - + Second (1 or 2 digits)s - Segundo (1 ou 2 dígitos)s + Segundo (1 ou 2 dígitos)s - + Second (2 digits)ss - Segundo (2 dígitos)ss + Segundo (2 dígitos)ss - + Milliseconds (1 to 3 digits)z - Milisegundo (1 ou 3 dígitos)z + Milisegundo (1 ou 3 dígitos)z - + Milliseconds (3 digits)zzz - Milisegundo (3 dígitos)zzz + Milisegundo (3 dígitos)zzz - + AM/PMAP - AM/PMAP + AM/PMAP - + am/pmap - am/pmap + am/pmap - + Any Character. Qualquer carácter. - + End of Text$ Fim de texto$ - + 0 Or More Repetitions* 0 ou Mais Repetições* - + 1 Or More Repetitions+ 1 Ou Mais Repetições+ - + 0 Or 1 Repetitions? 0 Ou 1 Repetições? - + Set of Numbers[0-9] Conjunto de Números[0-9] - + Lower Case Letters[a-z] Letras Minúsculas[a-z] - + Upper Case Letters[A-Z] Letras Maiúsculas[A-Z] - + Not a Number[^0-9] Carácter não numérico[^0-9] - + Or| Ou| - + Escape a Special Character\ Terminar Carácter Especial\ @@ -1713,6 +1871,131 @@ OneLineText TextoLinhaUnica + + + DateTime + + + + + Day (1 or 2 digits)%-d + + + + + Day (2 digits)%d + + + + + Weekday Abbreviation%a + + + + + Weekday Name%A + + + + + Month (1 or 2 digits)%-m + + + + + Month (2 digits)%m + + + + + Month Abbreviation%b + + + + + Month Name%B + + + + + Year (2 digits)%y + + + + + Year (4 digits)%Y + + + + + Week Number (0 to 53)%-U + + + + + Day of year (1 to 366)%-j + + + + + Hour (0-23, 1 or 2 digits)%-H + + + + + Hour (00-23, 2 digits)%H + + + + + Hour (1-12, 1 or 2 digits)%-I + + + + + Hour (01-12, 2 digits)%I + + + + + Minute (1 or 2 digits)%-M + + + + + Minute (2 digits)%M + + + + + Second (1 or 2 digits)%-S + + + + + Second (2 digits)%S + + + + + Microseconds (6 digits)%f + + + + + AM/PM%p + + + + + Comma Separator\, + + + + + Dot Separator\. + + genboolean @@ -1740,95 +2023,110 @@ globalref - + TreeLine Files Ficheiros TreeLine - + TreeLine Files - Compressed Ficheiros TreeLine - Comprimidos - + TreeLine Files - Encrypted Ficheiros TreeLine - Encriptados - + All Files Todos os Ficheiros - + HTML Files Ficheiros HTML - + Text Files Ficheiros de Texto - + XML Files Ficheiros XML - + ODF Text Files Ficheiros ODF - + Treepad Files Ficheiros Treepad - + PDF Files Ficheiros PDF + + + CSV (Comma Delimited) Files + + + + + All TreeLine Files + + + + + Old TreeLine Files + + helpview - + Tools Ferramentas - + &Back &Retroceder - + &Forward &Avançar - + &Home &Início - + Find: Encontrar: - + Find &Previous Encontrar &Anterior - + Find &Next Encontrar &Seguinte - + Text string not found Texto não encontrado @@ -1836,92 +2134,92 @@ imports - + &Tab indented text, one node per line &Texto recuado por tabulação, um ramo por linha - + Tab delimited text table with header &row Tabela de texto delimitada por tabulação com &cabeçalhos e colunas Plain text, one node per &line (CR delimited) - Texto simples, um ramo por &linha (delimitado por parágrafos) + Texto simples, um ramo por &linha (delimitado por parágrafos) - + Plain text &paragraphs (blank line delimited) &Parágrafos de texto simples (delimitados por linha em branco) - + Treepad &file (text nodes only) &Ficheiro Treepad (ramos de texto apenas) - + &Generic XML (non-TreeLine file) &XML Genérico (ficheiro não específico do TreeLine) - + Open &Document (ODF) outline Índice &Open Document (ODF) - + &HTML bookmarks (Mozilla Format) Favoritos em &HTML (Formato Mozilla) - + &XML bookmarks (XBEL format) &Favoritos XML (Formato XBEL) - + FOLDER PASTA - + BOOKMARK FAVORITO - + SEPARATOR SEPARADOR - + Link Ligação - + Text Texto - + Import File Importar Ficheiro - + Choose Import Method Escolher Método de Importação - + Invalid File Ficheiro Inválido - + "{0}" is not a valid TreeLine file. Use an import filter? @@ -1930,60 +2228,100 @@ Utilizar um filtro de importação? - + TreeLine - Import File TreeLine - Importar Ficheiro - + Error - could not read file {0} Erro - não foi possível ler o ficheiro {0} - + Error - improper format in {0} Erro - formato de ficheiro impróprio {0} - + TABLE TABELA - + Bookmarks Favoritos - + Too many entries on Line {0} + + + Plain text, one &node per line (CR delimited) + + + + + Bad CSV format on Line {0} + + + + + Co&mma delimited (CSV) text table with level column && header row + + + + + Comma delimited (CSV) text table &with header row + + + + + Other + + + + + Old Tree&Line File (1.x or 2.x) + + + + + Invalid level number on line {0} + + + + + Invalid level structure + + matheval - + Illegal "{}" characters Carácteres "{0}" ilegais - + Child references must be combined in a function Referências a descendentes devem ser combinadas numa função - + Illegal syntax in equation Sintaxe ilegal na equação - + Illegal function present: {0} Função ilegal presente: {0} - + Illegal object type or operator: {0} Operador ou tipo de objecto ilegal: {0} @@ -1991,571 +2329,621 @@ miscdialogs - + &OK &OK - + &Cancel &Cancelar - + Fields Campos - + File Properties Propriedades do Ficheiro - + File Storage Armazenamento do Ficheiro - + &Use file compression Utilizar &compressão de ficheiro - + Use file &encryption Utilizar &encriptação de ficheiro - + Spell Check Verificar Ortografia - + Language code or dictionary (optional) Código de linguagem ou dicionário (opcional) - + Math Fields Campos Matemáticos - + &Treat blank fields as zeros &Tratar campos vazios como zeros - + Encrypted File Password Palavra Passe de Ficheiro Encriptado - + Type Password for "{0}": Introduza a palavra passe para "{0}": - + Type Password: Introduza a Palavra Passe: - + Re-Type Password: Re-Introduza a Palavra Passe: - + Remember password during this session Lembrar a palavra passe durante esta sessão - + Zero-length passwords are not permitted Palavras passe vazias não são permitidas - + Re-typed password did not match Palavras passe introduzidas são diferentes - + Default - Single Line Text Pré definido - Texto em linha única - + &Search Text &Pesquisar Texto - + What to Search Onde pesquisar - + Full &data Toda a &informação - + &Titles only Apenas &Títulos - + How to Search Como pesquisar - + &Key words &Palavras chave parciais - + Key full &words Palavras chave &completas - + F&ull phrase &Frase completa - + &Regular expression &Expressão regular - + Find Encontrar - + Find &Previous Encontrar &Anterior - + Find &Next Encontrar &Seguinte - + Filter Filtrar - + &Filter &Filtrar - + &End Filter &Terminar Filtro - + &Close &Fechar - + Error - invalid regular expression Erro - Expressão Regular inválida - + Search string "{0}" not found Termos de pesquisa "{0}" não encontrados - + Find and Replace Encontrar e Substituir - + Replacement &Text Texto de &Substituição - + Any &match &Qualquer ocorrência - + Full &words Palavras &Completas - + Re&gular expression &Expressão regular - + &Node Type &Tipo de ramo - + N&ode Fields &Campos dos Ramos - + &Find Next Encontrar &Seguinte - + &Replace &Substituir - + Replace &All Substituir &Todos - + [All Types] [Todos os Tipos] - + [All Fields] [Todos os Campos] - + Search text "{0}" not found Termo de pesquisa "{0}" não encontrado - + Error - replacement failed Erro - substituição falhou - + Replaced {0} matches Foram substituidas {0} ocorrências - + Sort Nodes Ordenar Ramos - + What to Sort O que Ordenar - + &Entire tree &Toda a árvore - + Selected &branches Ramos &Seleccionados - + Selection's childre&n &Descendentes da selecção - + Selection's &siblings &Irmãos da selecção - + Sort Method Método de Ordenamento - + &Predefined Key Fields &Campos de ordenamento pré definidos - + Node &Titles Títulos dos &Ramos - + Sort Direction Direcção de Ordenamento - + &Forward &Ascendente - + &Reverse &Descendente - + &Apply &Aplicar - + Update Node Numbering Actualizar Numeração dos Ramos - + What to Update O que Actualizar - + &Selection's children &Descendentes da Selecção - + Root Node Raiz - + Include top-level nodes Icluir ramos dos níveis superiores - + Handling Nodes without Numbering Fields Critério para Ramos sem Campos de Numeração - + &Ignore and skip &Ignorar e saltar - + &Restart numbers for next siblings &Reiniciar contagem para próximos irmãos - + Reserve &numbers &Reservar números - + TreeLine Numbering Numeração TreeLine - + No numbering fields were found in data types Não foram encontrados campos de numeração nos tipos de dados - + File Menu Menu Ficheiro - + File Ficheiro - + Edit Menu Menu Editar - + Edit Editar - + Node Menu Menu Ramo - + Node Ramo - + Data Menu Menu Informação - + Data Informação - + Tools Menu Menu Ferramentas - + Tools Ferramentas - + View Menu Menu Ver - + View Ver - + Window Menu Menu Janela - + Window Janela - + Help Menu Menu Ajuda - + Help Ajuda - + Keyboard Shortcuts Atalhos de Teclado - + &Restore Defaults &Restaurar pré definições - + Key {0} is already used Tecla {0} já em utilização - + Clear &Key &Remover Atalho - + --Separator-- --Separador-- - + Customize Toolbars Personalizar Barra de Ferramentas - + Toolbar &Size &Tamanho da Barra de Ferramentas - + Small Icons Ícones Pequenos - + Large Icons Ícones Grandes - + Toolbar Quantity Número de Barras de Ferramentas - + &Toolbars &Barras de Ferramentas - + A&vailable Commands Comandos &Disponíveis - + Tool&bar Commands Comandos da &Barra de Ferramentas - + Move &Up Mover para &Cima - + Move &Down Mover para &Baixo - + Tree View Font Tipo de Letra da Árvore - + Output View Font Tipo de Letra de Apresentação - + Editor View Font Tipo de Letra da Vista de Edição - + TreeLine Plugins - Extensões Treeline + Extensões Treeline - + Plugin Modules Loaded - Módulos de Extensões Carregados + Módulos de Extensões Carregados + + + + No menu + + + + + TreeLine - Serious Error + + + + + A serious error has occurred. TreeLine could be in an unstable state. +Recommend saving any file changes under another filename and restart TreeLine. + +The debugging info shown below can be copied and emailed to doug101@bellz.org along with +an explanation of the circumstances. + + + + + + Format Menu + + + + + Format + + + + + Customize Fonts + + + + + &Use system default font + Utilizar fonte pré definida do &sistema + + + + App Default Font + + + + + &Use app default font + nodeformat - + Name Nome @@ -2598,7 +2986,7 @@ Domingo - + Startup Condition Opções de Arranque @@ -2610,131 +2998,131 @@ Show child pane in right hand view - Mostrar painel de descendentes na vista direita + Mostrar painel de descendentes na vista direita - + Show descendants in output view Mostrar descendentes na vista de apresentação - + Restore tree view states of recent files Restaurar o estado de visualização dos ficheiros recentes - + Restore previous window geometry Repor a anterior disposição de janelas - + Features Available Funcionalidades Disponíveis - + Open files in new windows Abrir ficheiros em novas janelas - + Click node to rename Clicar nos ramos para alterar nome - + Rename new nodes when created Editar nome de ramos após criação - + Tree drag && drop available Permitir arrastar e largar na vista da árvore - + Show icons in the tree view Mostrar ícones na vista em árvore Show node unique IDs in the Data Edit view - Mostrar o identificador único de um ramo na vista de edição + Mostrar o identificador único de um ramo na vista de edição - + Show math fields in the Data Edit view Mostrar campos matemáticos na Vista de Edição - + Show numbering fields in the Data Edit view Mostrar campos de numeração na Vista de Edição - + Undo Memory Memória de operações - + Number of undo levels Número de passos a desfazer - + Auto Save Gravação Automática - + Minutes between saves (set to 0 to disable) Minutos entre gravações (0 desactiva) - + Recent Files Ficheiros Recentes - + Number of recent files in the file menu Número de ficheiros recentes no menu ficheiro - + Data Editor Formats Formatos do Editor de Dados - + Times Tempo - + Dates Datas - + First day of week Primeiro dia da semana - + Appearance Aparência - + Child indent offset (in font height units) Recuo dos descendentes @@ -2743,58 +3131,78 @@ Printer Settings - Definições da Impressora + Definições da Impressora - + Spell Check - Verificar Ortografia + Verificar Ortografia - + Fonts - Fontes + Fontes Window Geometry - Posição de Janelas + Posição de Janelas Toolbar Geometry - Configuração das Barras de Ferramentas + Configuração das Barras de Ferramentas Toolbar Commands - Comandos da Barra de Ferramentas + Comandos da Barra de Ferramentas + + + + Show breadcrumb ancestor view + + + + + Show child pane in right hand views + + + + + Remove inaccessible recent file entries + + + + + Activate data editors on mouse hover + options - + Choose configuration file location Escolher localização do ficheiro de configuração - + User's home directory (recommended) Pasta do utilizador (recomendado) - + Program directory (for portable use) Pasta do programa (para utilização portátil) - + &OK &OK - + &Cancel &Cancelar @@ -2802,556 +3210,595 @@ printdata - + Error initializing printer Erro ao iniciar a impressora - + TreeLine - Export PDF TreeLine - Exportar PDF + + + Warning: Page size and margin settings unsupported on current printer. +Save page adjustments? + + + + + Warning: Page size setting unsupported on current printer. +Save adjustment? + + + + + Warning: Margin settings unsupported on current printer. +Save adjustments? + + printdialogs - + Print Preview Previsualização de Impressão - + Fit Width Ajustar à Largura - + Fit Page Ajustar à Página - + Zoom In Aproximar - + Zoom Out Afastar - + Previous Page Página Anterior - + Next Page Página Seguinte - + Single Page Página única - + Facing Pages Páginas Consecutivas - + Print Setup Definições de Impressão - + Print Imprimir - + Printing Setup Opções de impressão - + &General Options &Opções Gerais - + Page &Setup Configuração de &Página - + &Font Selection Selecção de &Fonte - + &Header/Footer &Cabeçalho/Rodapé - + Print Pre&view... &Previsualização de Impressão... - + &Print... &Imprimir... - + &OK &OK - + &Cancel &Cancelar - + What to print O que imprimir - + &Entire tree &Árvore completa - + Selected &branches &Ramos e descendentes seleccionados - + Selected &nodes Ramos &Seleccioandos - + Included Nodes Ramos Incluídos - + &Include root node &Incluir Raiz - + Onl&y open node children Apenas descendentes de ramos &expandidos - + Features Opções - + &Draw lines to children &Desenhar linhas até descendentes - + &Keep first child with parent &Manter primeiro descendente com o ascendente - + Indent Avanço - + Indent Offse&t (line height units) &Distancia do Avanço (em unidades de altura de linha) - + Letter (8.5 x 11 in.) Carta (8.5 x 11 pol.) - + Legal (8.5 x 14 in.) Legal (8.5 x 14 pol.) - + Tabloid (11 x 17 in.) Tablóide (11 x 17 pol.) - + A3 (279 x 420 mm) A3 (279 x 420 mm) - + A4 (210 x 297 mm) A4 (210 x 297 mm) - + A5 (148 x 210 mm) A5 (148 x 210 mm) - + Custom Size Tamanho personalizado - + Inches (in) Polegadas (pol) - + Millimeters (mm) Milímetros (mm) - + Centimeters (cm) Centímetros (cm) - + &Units &Unidades - + Paper &Size &Tamanho do Papel - + &Width: &Largura: - + Height: Altura: - + Orientation Orientação - + Portra&it Ao &baixo - + Lan&dscape Ao &alto - + Margins Margens - + &Left: &Esquerda: - + &Top: &Topo: - + &Right: &Direita: - + &Bottom: &Base: - + He&ader: &Cabeçalho: - + Foot&er: &Rodapé: - + Columns Colunas - + &Number of columns &Número de colunas - + Space between colu&mns &Espaçamento entre colunas - + Default Font Fonte Pré Definida - + &Use system default font - Utilizar fonte pré definida do &sistema + Utilizar fonte pré definida do &sistema - + &Use TreeLine output view font Utilizar a fonte de apresentação do &TreeLine - + Select Font Seleccionar Fonte - + &Font &Fonte - + Font st&yle &Estilo de Fonte - + Si&ze &Tamanho - + Sample Amostra - + AaBbCcDdEeFfGg...TtUuVvWvXxYyZz AaBbCcDdEeFfGg...TtUuVvWvXxYyZz - + &Header Left &Esquerda do Cabeçalho - + Header C&enter &Centro do Cabeçalho - + Header &Right &Direita do Cabeçalho - + Footer &Left E&squerda do Rodapé - + Footer Ce&nter Ce&ntro do Rodapé - + Footer Righ&t Direi&ta do Rodapé - + Fiel&ds &Campos - + Field For&mat &Formato dos Campos - + Header and Footer Cabeçalho e Rodapé - + Field Format for "{0}" Formato de campo para "{0}" - + Output &Format Formato de &Apresentação - + Format &Help &Ajuda de Formato - + Extra Text Texto Extra - + &Prefix &Prefixo - + &Suffix &Sufixo + + + Error: Page size or margins are invalid + + + + + TreeLine PDF Printer + + + + + Select &Printer + + recentfiles Recent Files - Ficheiros Recentes + Ficheiros Recentes spellcheck - + Could not find either aspell.exe, ispell.exe or hunspell.exe Browse for location? Não foi possível encontrar aspell.exe, ispell.exe ou hunspell.exe Procurar localização? - + Spell Check Error Erro de Verificação Ortográfica - + Locate aspell.exe, ipsell.exe or hunspell.exe Localizar aspell.exe, ispell.exe ou hunspell.exe - + Program (*.exe) Programa (*.exe) - + TreeLine Spell Check Error Make sure aspell, ispell or hunspell is installed Erro de verificação Ortográfica do TreeLine Cerifique-se que aspell, ispell ou hunspell estão instalados - + Finished checking the branch Continue from the root branch? - Terminada verificação no ramo actual + Terminada verificação no ramo actual Continuar da raiz? - + TreeLine Spell Check Verificação Ortográfica TreeLine - + Finished spell checking Terminada verificação ortográfica - + Spell Check Verificação Ortográfica - + Not in Dictionary Ausente no Dicionário - + Word: Palavra: - + Context: Contexto: - + Suggestions Sugestões - + Ignor&e &Ignorar - + &Ignore All Ignorar &Todas - + &Add &Adicionar - + Add &Lowercase Adicionar em &Minúsculas - + &Replace &Substituir - + Re&place All S&ubstituir Todas - + &Cancel &Cancelar + + + Finished checking the branch +Continue from the top? + + treeformats - + DEFAULT PRÉ DEFINIDO @@ -3359,567 +3806,683 @@ treelocalcontrol - + Error - could not delete backup file {} Erro - não foi possível apagar cópia de segurança {} - + Save changes to {}? Guardar alterações em {}? - + Save changes? Guardar alterações? - + &Save &Guardar - + Save File Guardar Ficheiro - + Save the current file Guardar o ficheiro actual - + Save &As... Guardar &Como... - + Save the file with a new name Guardar o ficheiro com um novo nome - + &Export... &Exportar... - + Export the file in various other formats Exportar o ficheiro em diversos outros formatos - + Prop&erties... &Propriedades... - + Set file parameters like compression and encryption Definir parâmetros do ficheiro como compressão e encriptação - + P&rint Setup... &Definições de Impressão... - + Set margins, page size and other printing options Definir margens, tamanho de papel e outras opções de impressão - + Print Pre&view... Pré &visualizar impressão... - + Show a preview of printing results Mostrar uma pré visualização do resultado da impressão - + &Print... &Imprimir... - + Print tree output based on current options Imprimir apresentação da árvore baseada nas opções actuais - + Print &to PDF... Imprimir para &PDF... - + Export to PDF with current printing options Exportar para PDF com as actuais opções de impressão - + &Undo &Desfazer - + Undo the previous action Reverte a última acção - + &Redo &Refazer - + Redo the previous undo Repõe a última acção desfeita - + Cu&t Cor&tar - + Cut the branch or text to the clipboard Cortar o ramo ou texto para a memória de trabalho - + &Copy &Copiar - + Copy the branch or text to the clipboard Copiar o ramo ou texto para a memória de trabalho - + &Paste C&olar - + Paste nodes or text from the clipboard Colar ramos ou texto da memória de trabalho - + P&aste Plain Text - Colar como texto &simples + Colar como texto &simples - + Paste non-formatted text from the clipboard Colar texto sem formatação da memória de trabalho - + &Bold Font &Negrito - + Set the current or selected font to bold Definir o texto actual ou seleccionado como negrito - + &Italic Font &Itálico - + Set the current or selected font to italic Definir o texto actual ou seleccionado como itálico - + U&nderline Font &Sublinhado - + Set the current or selected font to underline Definir o texto actual ou seleccionado como sublinhado - + &Font Size &Tamanho do texto - + Set size of the current or selected text Definir o tamanho do texto actual ou seleccionado - + Small Pequeno - + Default Normal - + Large Grande - + Larger Maior - + Largest Muito Grande - + Set Font Size Definir tamanho do texto - + Font C&olor... Co&r do Texto... - + Set the color of the current or selected text Definir a cor do texto actual ou seleccionado - + &External Link... &Ligação Externa... - + Add or modify an extrnal web link Adicionar ou modificar uma ligação externa para a internet - + Internal &Link... Ligação &Interna... - + Add or modify an internal node link Adicionar ou modificar uma ligação interna para outro ramo - + Clear current or selected text formatting Remover a formatação do texto actual ou seleccionado - + &Rename &Mudar Nome - + Rename the current tree entry title Alterar o nome da entrada actual na árvore - + Insert Sibling &Before Inserir Irmão &Antes - + Insert new sibling before selection Inserir novo ramo acima da selecção - + Insert Sibling &After Inserir Irmão &Depois - + Insert new sibling after selection Inserir novo ramo abaixo da selecção - + Add &Child Adicionar De&scendente - + Add new child to selected parent Adicionar sub ramo ao ramo seleccionado - + &Delete Node &Eliminar Ramo - + Delete the selected nodes Eliminar os ramos seleccionados - + &Indent Node &Recuar Ramo - + Indent the selected nodes Avançar os ramos seleccionados - + &Unindent Node A&vançar Ramo - + Unindent the selected nodes Recuar os ramos seleccionados - + &Move Up Mover para &Cima - + Move the selected nodes up Mover para cima os ramos seleccionados - + M&ove Down Mover para &Baixo - + Move the selected nodes down Mover para baixo os ramos seleccionados - + Move &First Colocar em &Primeiro - + Move the selected nodes to be the first children Mover os ramos seleccionados para que sejam os primeiros descendentes - + Move &Last Colocar em &Último - + Move the selected nodes to be the last children Mover os ramos seleccionados para que sejam os últimos descendentes - + &Set Node Type Definir &Tipo de Ramo - + Set the node type for selected nodes Definir o tipo de dados para os ramos seleccionados - + Set Node Type Definir Tipo de Ramo - + Copy Types from &File... Copiar Tipos de Outro &Ficheiro... - + Copy the configuration from another TreeLine file Copiar configuração de outro ficheiro TreeLine - + Flatten &by Category &Nivelar por categoria - + Collapse descendants by merging fields Colapsar descendentes juntando campos - + Add Category &Level... &Adicionar Nível por Categoria... - + Insert category nodes above children Insere ramos sobre descendentes classificanado segundo um campo - + Flatten b&y Link... - Nivelar por &Ligação... + Nivelar por &Ligação... - + Collapse descendants and insert parent links - Colapsar descendentes e inserir ligações aos ascendentes + Colapsar descendentes e inserir ligações aos ascendentes - + &Arrange by Link... - &Distribuir por ligação... + &Distribuir por ligação... - + Arrange descendants using parent links - Distribuir descendentes utilizando ligações aos parentes + Distribuir descendentes utilizando ligações aos parentes - + &Spell Check... &Verificar Ortografia... - + Spell check the tree')s text data - Verificar a informação textual da árvore + Verificar a informação textual da árvore - + &New Window &Nova Janela - + Open a new window for the same file Abrir nova vista do mesmo ficheiro - + Error - could not write to {} Erro - não é possível escrever para {} - + TreeLine - Save As TreeLine - Guardar Como - + Error - could not write to file Erro - não foi possível escrever para o ficheiro - + TreeLine - Open Configuration File TreeLine - Abrir Ficheiro de Configuração - + Error - could not read file {0} Erro - não foi possível ler o ficheiro {0} - + Cannot expand without common fields Não é possível expandir sem campos em comum - + Category Fields Campos de Categorias - + Select fields for new level Seleccione campo para novo nível - + Flatten by Link - Nivelar por ligação + Nivelar por ligação - + Enter a new field name for parent links - Introduza o nome do novo campo para as ligações aos ascendentes + Introduza o nome do novo campo para as ligações aos ascendentes - + Link Field - Campo das Ligações + Campo das Ligações - + Select field with links to parents - Seleccione campo com as ligações aos ascendentes + Seleccione campo com as ligações aos ascendentes - + Warning: duplicate Unique IDs found. - Aviso: Identificadores Únicos duplicados encontrados. + Aviso: Identificadores Únicos duplicados encontrados. - + Many Unique IDs were re-assigned. - Muitos Identificadores Únicos foram re-atribuídos. + Muitos Identificadores Únicos foram re-atribuídos. - + The following IDs were re-assigned: - Os seguintes Identificadores Únicos foram re-atribuídos: + Os seguintes Identificadores Únicos foram re-atribuídos: - + Internal link targets could be affected. - + O destino de ligações internas pode ter sido afectado. - + Clear For&matting Limpar &Formatação - + File saved Ficheiro gravado + + + Pa&ste Plain Text + + + + + Paste C&hild + + + + + Paste a child node from the clipboard + + + + + Paste Sibling &Before + + + + + Paste a sibling before selection + + + + + Paste Sibling &After + + + + + Paste a sibling after selection + + + + + Paste Cl&oned Child + + + + + Paste a child clone from the clipboard + + + + + Paste Clo&ned Sibling Before + + + + + Paste a sibling clone before selection + + + + + Paste Clone&d Sibling After + + + + + Paste a sibling clone after selection + + + + + Clone All &Matched Nodes + + + + + Convert all matching nodes into clones + + + + + &Detach Clones + + + + + Detach all cloned nodes in current branches + + + + + S&wap Category Levels + + + + + Swap child and grandchild category nodes + + + + + Converted {0} branches into clones + + + + + No identical nodes found + + + + + Warning - file corruption! +Skipped bad child references in the following nodes: + + + + + Spell check the tree's text data + + treemaincontrol - + Warning: Could not create local socket Aviso: Não foi possível criar ligação local @@ -3929,361 +4492,381 @@ Erro - não foi possível gravar configuração no ficheiro {0} - + Error - could not read file {0} Erro - não foi possível ler o ficheiro {0} - + Error - {0} is not a valid TreeLine file - Erro - {0} não é um ficheiro TreeLine válido + Erro - {0} não é um ficheiro TreeLine válido - + Backup file "{}" exists. A previous session may have crashed Cópia de segurança {0} existente. Uma sessão anterior pode ter terminado inesperadamente - + &Restore Backup &Restaurar Cópia de Segurança - + &Delete Backup &Apagar Cópia de Segurança - + &Cancel File Open &Cancelar Abertura de Ficheiro - + Error - could not rename "{0}" to "{1}" Erro - não foi possível alterar o nome de "{0}" para"{1}" - + Error - could not remove backup file {} Erro - não foi possível remover a cópia de segurança {} - + &New... &Novo... - + New File Novo Ficheiro - + Start a new file Criar um novo ficheiro - + &Open... &Abrir... - + Open File Abrir um Ficheiro - + Open a file from disk Abrir um ficheiro do disco - + Open Sa&mple... A&brir Exemplo... - + Open Sample Abrir um Exemplo - + Open a sample file Abrir um ficheiro de exemplo - + &Import... &Importar... - + Open a non-TreeLine file Abrir um ficheiro alheio ao TreeLine - + &Quit &Sair - + Exit the application Sair da aplicação - + &Configure Data Types... &Configurar Tipos de Dados... - + Modify data types, fields & output lines Modificar tipos de dados, campos e opções de apresentação - + Sor&t Nodes... &Ordenar Ramos... - + Define node sort operations Definir operações de ordenamento - + Update &Numbering... &Actualizar Numeração... - + Update node numbering fields Actualizar os campos de numeração - + &Find Text... &Encontrar Texto... - + Find text in node titles & data Encontrar texto nos títulos e conteúdo dos ramos - + &Conditional Find... &Procura Condicional... - + Use field conditions to find nodes Utilizar condições para encontrar ramos - + Find and &Replace... Encontar e &Substituir... - + Replace text strings in node data Substituir texto no conteúdo dos ramos - + &Text Filter... Filtro de &Texto... - + Filter nodes to only show text matches Filtrar ramos para mostrar apenas textos correspondentes - + C&onditional Filter... Filtro &Condicional... - + Use field conditions to filter nodes Utilizar condições para filtrar ramos - + &General Options... &Opções Gerais... - + Set user preferences for all files Definir opções do utilizador para todos os ficheiros - + Set &Keyboard Shortcuts... Definir &Atalhos de Teclado... - + Customize keyboard commands Personalizar comandos de teclado - + C&ustomize Toolbars... Personalizar &Barras de Ferramentas... - + Customize toolbar buttons Personalizar os botões das barras de ferramentas - + Customize Fo&nts... Personalizar &Fontes... - + Customize fonts in various views Personalizar as fontes utilizadas nas diversas vistas - + &Basic Usage... Utilização &Básica... - + Display basic usage instructions Mostrar instruções de utilização básica - + &Full Documentation... &Documentação Completa... - + Open a TreeLine file with full documentation Abrir um ficheiro TreeLine com a documentação completa - + &About TreeLine... &Acerca do TreeLine... - + Display version info about this program Mostrar informção sobre a versão deste programa - + &Select Template Seleccionar &Modelo - + TreeLine - Open File TreeLine -Abrir Ficheiro - + Open Sample File Abrir Ficheiro de Exemplo - + &Select Sample &Seleccione um exemplo - + Conditional Find Procura Condicional - + Conditional Filter Filtro Condicional - + ToolsFilterCondition - FerramentasFiltroCondicional + FerramentasFiltroCondicional - + General Options Opções Gerais - + Error - basic help file not found Erro -Ficheiro de ajuda básica não encontrado - + TreeLine Basic Usage Utilização Básica TreeLine - + Error - documentation file not found Erro - Ficheiro de documentação não encontrado - + TreeLine, Version {0} by {1} - TreeLine, Versão {0} por {1} + TreeLine, Versão {0} por {1} - + Warning - could not load plugin module {0} - Aviso - não foi possível carregar módulo de extenção {0} + Aviso - não foi possível carregar módulo de extenção {0} - + &Select All &Seleccionar Tudo - + Select all text in an editor Seleccionar todo o texto num editor - + &About &Plugins... - &Acerca das Extensões... + &Acerca das Extensões... - + Show loaded plugin modules - Mostrar módulos de extensões carregados + Mostrar módulos de extensões carregados + + + + Error - invalid TreeLine file {0} + + + + + TreeLine version {0} + + + + + written by {0} + + + + + Library versions: + treemodel - + Main - Principal + Principal treenode - + New Novo @@ -4293,31 +4876,64 @@ Invalid XML file - Ficheiro XML inválido + Ficheiro XML inválido Bad elememnt - not a valid TreeLine file - Elemento incorrecto- ficheiro Treeline inválido + Elemento incorrecto- ficheiro Treeline inválido + + + + treestructure + + + Main + Principal treeview - + Filtering by "{0}", found {1} nodes Filtragem por "{0}", encontrados {1} ramos - + Conditional filtering, found {0} nodes Filtragem condicional, encontrados {0} ramos + + + Search for: + + + + + Search for: {0} + + + + + Search for: {0} (not found) + + + + + Next: {0} + + + + + Next: {0} (not found) + + treewindow - + Data Output Apresentação de Informação @@ -4327,149 +4943,184 @@ Edição de Informação - + Title List Lista de Títulos - + &Expand Full Branch &Expandir Ramo Completo - + Expand all children of the selected nodes Expandir todos os descendentes dos ramos seleccionados - + &Collapse Full Branch &Colapsar Ramo Completo - + Collapse all children of the selected nodes Colapsar todos os descendentos dos ramos seleccionados - + &Previous Selection Selecção &Anterior - + Return to the previous tree selection Repor a anterior selecção da árvore - + &Next Selection Selecção &Seguinte - + Go to the next tree selection in history Ir para a selecção da árvore seguinte no histórico - + Show Data &Output M&ostrar Apresentação de Informação - + Show data output in right view Mostrar Apresentação de Dados na vista direita - + Show Data &Editor Mostrar Editor de &Informação - + Show data editor in right view Mostrar o editor de informação na vista direita - + Show &Title List Mostrar &Lista de Títulos - + Show title list in right view Mostra lista de títulos na vista direita - + &Show Child Pane Mostrar Painel dos &Descendentes - + Toggle showing right-hand child views Alternar mostrar vista de descendentes na vista direita - + Show Output &Descedants - Mostrar A&presentação e Descendentes + Mostrar A&presentação e Descendentes - + Toggle showing output view indented descendants Alternar mostrar a apresentação de descendentes - + &Close Window &Fechar Janela - + Close this window Fechar esta janela - + &File &Ficheiro - + &Edit &Editar - + &Node &Ramo - + &Data &Informação - + &Tools Fe&rramentas - + &View &Ver - + &Window &Janela - + &Help &Ajuda + + + Start Incremental Search + + + + + Next Incremental Search + + + + + Previous Incremental Search + + + + + Show &Breadcrumb View + + + + + Toggle showing breadcrumb ancestor view + + + + + Show Output &Descendants + + + + + Fo&rmat + + diff -Nru treeline-2.0.2/i18n/translations/treeline_ru.ts treeline-3.0.1/i18n/translations/treeline_ru.ts --- treeline-2.0.2/i18n/translations/treeline_ru.ts 2018-04-08 20:12:57.000000000 +0000 +++ treeline-3.0.1/i18n/translations/treeline_ru.ts 2018-10-17 14:52:24.000000000 +0000 @@ -3,310 +3,300 @@ conditional - + starts with начинается с - + ends with заканчивается на - + contains содержит - + True Верно - + False Ложно - + and и - + or или - + [All Types] - + Node Type - + &Add New Rule &Добавить новое правило - + &Remove Rule &Удалить правило - + &OK &ОК - + &Cancel О&тмена - - R&etrieve Rules... - - - - - &Save Rules... - - - - + Find &Previous Найти &предидущее - + Find &Next Найти &следующее - + &Filter - + &End Filter - + &Close &Закрыть - - Save Rules + + No conditional matches were found - - Enter a descriptive name + + Rule {0} - - No conditional matches were found + + Saved Rules - - Rule {0} + + Name: - - Retrieve Rules + + &Load - - Select rule set to retrieve: + + &Save - - Remove Rule + + &Delete configdialog - + Configure Data Types Настроить типы данных - + T&ype List - + Field &List - + &Field Config - + &Show Advanced - + &OK &ОК - + &Apply - + &Reset - + &Cancel О&тмена - + Add or Remove Data Types - + &New Type... - + Co&py Type... - + &Delete Type &Удалить тип - + Add Type Добавить тип - + Enter new type name: Введите новое имя типа: - + Rename Type Переименовать тип - + Cannot delete data type being used by nodes Нельзя удалять типы, уже используемые узлами - + &Data Type Тип &Данных - + Icon - + Change &Icon Изменить &пиктограмму - + Derived from &Generic Type - + Automatic Types Автоматические типы - + Modify Co&nditional Types - + Create Co&nditional Types - + Set Types Conditionally Задать типы условий - + Name Имя - + Type Тип - + Move &Up Переместить &вверх - + Move Do&wn Переместить в&низ - + &New Field... &Новое поле... - + Add Field Добавить поле - + Enter new field name: Введите новое имя поля: - + Rename Field Переименовать поле - + F&ield @@ -341,178 +331,173 @@ - + File Info Reference Ссылка на информацию о файле - + Parent Reference Ссылка на родителя - + Child Reference Ссылка на потомка - + Child Count - + F&ield List - + Other Field References - + Copy Type Копировать тип - + Set Data Type Icon Установить пиктограмму для типа данных - + Clear &Select &Очистить выделение - + Typ&e Config - + O&utput - + &Hide Advanced - + Error - circular reference in math field equations - + Rena&me Type... - + &Derive from original - + Rename from {} to: - + [None] no type set [Нет] - + Default Child &Type - + Output Options - + Add &blank lines between nodes - + Allow &HTML rich text in format - + Add text bullet&s - + Use a table for field &data - + Combination && Child List Output &Separator - - Uni&que ID Reference Field - - - - + None Нет - + Modify &Field List - + Sort Key - + Move U&p - + Rena&me Field... - + Dele&te Field - + Sort &Keys... - + fwd - + rev - + &Field Type @@ -537,794 +522,879 @@ - + &Title Format - + Out&put Format - + Reference Le&vel - + Refere&nce Type - + The name cannot be empty - + The name must start with a letter - + The name cannot start with "xml" - + The name cannot contain spaces - + The following characters are not allowed: {} - + The name was already used - + forward - + reverse - + Sort Key Fields - + Available &Fields - + &Sort Criteria - + Field - + Direction Направление - + &Move Down - + Flip &Direction - + Self Reference - + Root Reference - - Numeric Result - - - - + Date Result - + Time Result - + add - + subtract - + multiply - + divide - + floor divide - + modulus - + power - + sum of items - + maximum - + minimum - + average - + absolute value - + square root - + natural logarithm - + base-10 logarithm - + factorial - + round to num digits - + lower integer - + higher integer - + truncated integer - + floating point - + sine of radians - + cosine of radians - + tangent of radians - + arc sine - + arc cosine - + arc tangent - + radians to degrees - + degrees to radians - + pi constant - + natural log constant - + Define Math Field Equation - + Field References - + Reference &Level - + Reference &Type - + Available &Field List - + &Result Type - + Description - + &Equation - + Equation error: {} - + Boolean Result - + Text Result - + Arithmetic Operators - + Comparison Operators - + Text Operators - + equal to - + less than - + greater than - + less than or equal to - + greater than or equal to - + not equal to - + true value, condition, false value - + true if 1st text arg starts with 2nd arg - + true if 1st text arg ends with 2nd arg - + true if 1st text arg contains 2nd arg - + concatenate text - + join text using 1st arg as separator - + convert text to upper case - + convert text to lower case - + in 1st arg, replace 2nd arg with 3rd arg - + Operations - + O&perator Type - + Oper&ator List - + logical and - + logical or + + + Output HTML + + + + + Evaluate &HTML tags + + + + + Child Type Limits + + + + + [All Types Available] + + + + + &Select All + + + + + Select &None + + + + + Number Result + + dataeditors - + Today's &Date - + &Open Link - + Open &Folder - + External Link - + Scheme - + &Browse for File - + File Path Type - + Absolute - + Relative - + Address - + Display Name - + &OK &ОК - + &Cancel О&тмена - + TreeLine - External Link File - + &Go to Target - - Enable click-on-&target + + Internal Link - - Disable click-on-&target + + &Open Picture - - Internal Link + + Picture Link - - Unique ID + + TreeLine - Picture File - - &Open Picture + + Set to &Now - - Picture Link + + Clear &Link - - TreeLine - Picture File + + (Click link target in tree) - - - dataeditview - - Unique ID + + link exports - + Bookmarks Закладки - + TreeLine - Export HTML - + TreeLine - Export Text Titles - + TreeLine - Export Plain Text - + TreeLine - Export Text Tables - + TreeLine - Export Generic XML - + TreeLine - Export TreeLine Subtree - + TreeLine - Export ODF Text - + TreeLine - Export HTML Bookmarks - + TreeLine - Export XBEL Bookmarks - + &HTML - + &Text - - &XML - - - - + &ODF Outline - + Book&marks - + &Single HTML page - + Single &HTML page with navigation pane - + Multiple HTML &pages with navigation pane - + Multiple HTML &data tables - + &Tabbed title text С &табуляцией заголовков - + &Unformatted output of all text - - Tab &delimited table of children - - - - - Generic &XML output - - - - - &Subtree in TreeLine format - - - - + &HTML format bookmarks - + &XBEL format bookmarks - + File Export - + Choose export format type - + Choose export format subtype - + Choose export options - + What to Export - + &Entire tree - + Selected &branches - + Selected &nodes - + Other Options - + &Include root node - Включая &корень + Включая &корень - + &Only open node children - + Include &print header && footer - + &Columns - + Navigation pane &levels + + + Error - export template files not found. +Check your TreeLine installation. + + + + + Error - cannot link to unsaved TreeLine file. +Save the file and retry. + + + + + Warning - no relative path from "{0}" to "{1}". +Continue with absolute path? + + + + + Parent + + + + + Tree&Line + + + + + &XML (generic) + + + + + Live tree view, linked to TreeLine file (for web server) + + + + + Live tree view, single file (embedded data) + + + + + &Comma delimited (CSV) table of descendants (level numbers) + + + + + Comma &delimited (CSV) table of children (single level) + + + + + Tab &delimited table of children (&single level) + + + + + &Old TreeLine (2.0.x) + + + + + &TreeLine Subtree + + + + + &Include root nodes + + + + + Must select nodes prior to export + + fieldformat - + yes/no да/нет - + true/false верно/ложно - + T/F В/Л - + Y/N Д/Н @@ -1364,7 +1434,7 @@ - + Boolean Булевый @@ -1379,12 +1449,12 @@ Время - + Choice Выбор - + AutoChoice Автовыбор @@ -1394,12 +1464,12 @@ Комбинация - + AutoCombination - + ExternalLink @@ -1409,308 +1479,313 @@ Внутренняя ссылка - + Picture Изображение - + RegularExpression - + Now Сейчас - + Optional Digit# - + Required Digit0 - + Digit or Space (external)<space> - + Decimal Point. - + Decimal Comma, - - Comma Separator, - - - - - Dot Separator. - - - - + Space Separator (internal)<space> - + Optional Sign- - + Required Sign+ - + Exponent (capital)E - + Exponent (small)e - + Number1 - + Capital LetterA - + Small Lettera - + Capital Roman NumeralI - + Small Roman Numerali - + Level Separator/ - + Section Separator. - + "/" Character// - + "." Character.. - + Outline ExampleI../A../1../a)/i) - + Section Example1.1.1.1 - + Separator/ - + Example1/2/3/4 - - Day (1 or 2 digits)d + + Any Character. - - Day (2 digits)dd + + End of Text$ - - Weekday Abbreviationddd + + 0 Or More Repetitions* - - Weekday Namedddd + + 1 Or More Repetitions+ - - Month (1 or 2 digits)M + + 0 Or 1 Repetitions? - - Month (2 digits)MM + + Set of Numbers[0-9] - - Month AbbreviationMMM + + Lower Case Letters[a-z] - - Month NameMMMM + + Upper Case Letters[A-Z] - - Year (2 digits)yy + + Not a Number[^0-9] - - Year (4 digits)yyyy + + Or| - - Hour (0-23, 1 or 2 digits)H + + Escape a Special Character\ - - Hour (00-23, 2 digits)HH + + DateTime - - Hour (1-12, 1 or 2 digits)h + + Day (1 or 2 digits)%-d - - Hour (01-12, 2 digits)hh + + Day (2 digits)%d - - Minute (1 or 2 digits)m + + Weekday Abbreviation%a - - Minute (2 digits)mm + + Weekday Name%A - - Second (1 or 2 digits)s + + Month (1 or 2 digits)%-m - - Second (2 digits)ss + + Month (2 digits)%m - - Milliseconds (1 to 3 digits)z + + Month Abbreviation%b - - Milliseconds (3 digits)zzz + + Month Name%B - - AM/PMAP + + Year (2 digits)%y - - am/pmap + + Year (4 digits)%Y - - Any Character. + + Week Number (0 to 53)%-U - - End of Text$ + + Day of year (1 to 366)%-j - - 0 Or More Repetitions* + + Hour (0-23, 1 or 2 digits)%-H - - 1 Or More Repetitions+ + + Hour (00-23, 2 digits)%H - - 0 Or 1 Repetitions? + + Hour (1-12, 1 or 2 digits)%-I - - Set of Numbers[0-9] + + Hour (01-12, 2 digits)%I - - Lower Case Letters[a-z] + + Minute (1 or 2 digits)%-M - - Upper Case Letters[A-Z] + + Minute (2 digits)%M - - Not a Number[^0-9] + + Second (1 or 2 digits)%-S - - Or| + + Second (2 digits)%S - - Escape a Special Character\ + + Microseconds (6 digits)%f + + + + + AM/PM%p + + + + + Comma Separator\, + + + + + Dot Separator\. @@ -1740,95 +1815,110 @@ globalref - + TreeLine Files Файлы TreeLine - + TreeLine Files - Compressed Файлы TreeLine - сжатые - + TreeLine Files - Encrypted Файлы TreeLine - зашифрованные - + All Files Все файлы - + HTML Files - + Text Files Текстовые файлы - + XML Files Файлы XML - + ODF Text Files - + Treepad Files Файлы Treepad - + PDF Files + + + CSV (Comma Delimited) Files + + + + + All TreeLine Files + + + + + Old TreeLine Files + + helpview - + &Back &Назад - + &Forward &Вперед - + &Home &Домой - + Find: - + Find &Previous Найти &предидущее - + Find &Next Найти &следующее - + Text string not found Строка не найдена - + Tools @@ -1836,724 +1926,809 @@ imports - + &Tab indented text, one node per line - + Tab delimited text table with header &row - - Plain text, one node per &line (CR delimited) - - - - + Plain text &paragraphs (blank line delimited) - + Treepad &file (text nodes only) &Файл Treepad (только текстовые узлы) - + &Generic XML (non-TreeLine file) - + Open &Document (ODF) outline - + &HTML bookmarks (Mozilla Format) - + &XML bookmarks (XBEL format) За&кладки XML (формат XBEL) - + FOLDER ПАПКА - + BOOKMARK ЗАКЛАДКА - + SEPARATOR РАЗДЕЛИТЕЛЬ - + Link Ссылка - + Text Текст - + Import File - + Choose Import Method - + Invalid File - + "{0}" is not a valid TreeLine file. Use an import filter? - + TreeLine - Import File - + Error - could not read file {0} - + Error - improper format in {0} - + TABLE - + Bookmarks Закладки - + Too many entries on Line {0} - - - matheval - - Illegal "{}" characters + + Plain text, one &node per line (CR delimited) - - Child references must be combined in a function + + Bad CSV format on Line {0} - - Illegal syntax in equation + + Co&mma delimited (CSV) text table with level column && header row - - Illegal function present: {0} + + Comma delimited (CSV) text table &with header row - - Illegal object type or operator: {0} + + Other - - + + + Old Tree&Line File (1.x or 2.x) + + + + + Invalid level number on line {0} + + + + + Invalid level structure + + + + + matheval + + + Illegal "{}" characters + + + + + Child references must be combined in a function + + + + + Illegal syntax in equation + + + + + Illegal function present: {0} + + + + + Illegal object type or operator: {0} + + + + miscdialogs - + &OK &ОК - + &Cancel О&тмена - + Fields Поля - + File Properties - + File Storage Устройство хранение - + &Use file compression - + Use file &encryption - + Spell Check Проверка орфографии - + Language code or dictionary (optional) - + Math Fields - + &Treat blank fields as zeros - + Encrypted File Password Пароль зашифрованного файла - + Type Password for "{0}": - + Type Password: Введите пароль: - + Re-Type Password: Повторите ввод пароля: - + Remember password during this session Помнить пароль на протяжении этой сессии - + Zero-length passwords are not permitted Пароль нулевой длины не допускается - + Re-typed password did not match Повторный ввод пароля не удался - + Default - Single Line Text - + &Search Text - + What to Search - + Full &data - + &Titles only - + How to Search - + &Key words - + Key full &words - + F&ull phrase - + &Regular expression - + Find Найти - + Find &Previous Найти &предидущее - + Find &Next Найти &следующее - + Filter - + &Filter - + &End Filter - + &Close &Закрыть - + Error - invalid regular expression - + Search string "{0}" not found - + Find and Replace - + Replacement &Text - + Any &match - + Full &words - + Re&gular expression - + &Node Type - + N&ode Fields - + &Find Next - + &Replace &Заменить - + Replace &All - + [All Types] - + [All Fields] - + Search text "{0}" not found - + Error - replacement failed - + Replaced {0} matches - + Sort Nodes - + What to Sort - + &Entire tree - + Selected &branches - + Selection's childre&n - + Selection's &siblings - + Sort Method - + &Predefined Key Fields - + Node &Titles - + Sort Direction - + &Forward &Вперед - + &Reverse - + &Apply - + Update Node Numbering - + What to Update - + &Selection's children - + Root Node Коренной узел - + Include top-level nodes - + Handling Nodes without Numbering Fields - + &Ignore and skip - + &Restart numbers for next siblings - + Reserve &numbers - + TreeLine Numbering - + No numbering fields were found in data types - + File Menu - + File - + Edit Menu - + Edit - + Node Menu - + Node - + Data Menu - + Data - + Tools Menu - + Tools - + View Menu - + View - + Window Menu - + Window - + Help Menu - + Help - + Keyboard Shortcuts - + &Restore Defaults - + Key {0} is already used - + Clear &Key - + --Separator-- - + Customize Toolbars - + Toolbar &Size - + Small Icons - + Large Icons - + Toolbar Quantity - + &Toolbars &Панели инструментов - + A&vailable Commands - + Tool&bar Commands - + Move &Up Переместить &вверх - + Move &Down - + Tree View Font - + Output View Font - + Editor View Font - + TreeLine Plugins - Подключаемые модули TreeLine + Подключаемые модули TreeLine - + Plugin Modules Loaded - Подключаемые модули загружены + Подключаемые модули загружены + + + + No menu + + + + + TreeLine - Serious Error + + + + + A serious error has occurred. TreeLine could be in an unstable state. +Recommend saving any file changes under another filename and restart TreeLine. + +The debugging info shown below can be copied and emailed to doug101@bellz.org along with +an explanation of the circumstances. + + + + + + Format Menu + + + + + Format + + + + + Customize Fonts + + + + + &Use system default font + + + + + App Default Font + + + + + &Use app default font + nodeformat - + Name Имя @@ -2596,7 +2771,7 @@ Воскресенье - + Startup Condition При запуске @@ -2606,189 +2781,174 @@ Автоматически открывать последний файл - - Show child pane in right hand view - - - - + Show descendants in output view - + Restore tree view states of recent files - + Restore previous window geometry - + Features Available Доступные возможности - + Open files in new windows - + Click node to rename - + Rename new nodes when created Переименовывать узлы при создании - + Tree drag && drop available Поддержка drag && drop в дереве - + Show icons in the tree view Показывать пиктограммы - - Show node unique IDs in the Data Edit view - - - - + Show math fields in the Data Edit view - + Show numbering fields in the Data Edit view - + Undo Memory Память отмен - + Number of undo levels Количество уровней отмены - + Auto Save Автосохранение - + Minutes between saves (set to 0 to disable) - + Recent Files Недавние файлы - + Number of recent files in the file menu - + Data Editor Formats Форматы редактора данных - + Times Время - + Dates Дата - + First day of week - + Appearance Внешний вид - + Child indent offset (in font height units) - - Printer Settings - - - - + Spell Check - Проверка орфографии + Проверка орфографии - - Fonts + + Show breadcrumb ancestor view - - Window Geometry + + Show child pane in right hand views - - Toolbar Geometry + + Remove inaccessible recent file entries - - Toolbar Commands + + Activate data editors on mouse hover options - + Choose configuration file location - + User's home directory (recommended) - + Program directory (for portable use) - + &OK &ОК - + &Cancel О&тмена @@ -2796,554 +2956,588 @@ printdata - + Error initializing printer - + TreeLine - Export PDF + + + Warning: Page size and margin settings unsupported on current printer. +Save page adjustments? + + + + + Warning: Page size setting unsupported on current printer. +Save adjustment? + + + + + Warning: Margin settings unsupported on current printer. +Save adjustments? + + printdialogs - + Print Preview Предпросмотр печати - + &Print... Пе&чать... - + &General Options - + &Font Selection - + &Header/Footer - + Print Pre&view... - + &OK &ОК - + &Cancel О&тмена - + What to print - + &Entire tree - + Selected &branches - + Selected &nodes - + Features Дополнительные возможности - + &Include root node Включая &корень - + &Keep first child with parent - + Letter (8.5 x 11 in.) - + Legal (8.5 x 14 in.) - + Tabloid (11 x 17 in.) - + A3 (279 x 420 mm) - + A4 (210 x 297 mm) - + A5 (148 x 210 mm) - + Paper &Size - + Orientation - + &Units - + Columns Колонки - + &Number of columns - + Default Font - + Select Font - + &Font - + Font st&yle - + Sample - + &Header Left Заголовок в&лево - + Header C&enter Заголовок по &центру - + Footer &Left - + Footer Ce&nter Сноска по цент&ру - + Fiel&ds &Поля - + Header and Footer Заголовок и сноска - + Extra Text Дополнительный текст - + &Prefix - + Format &Help - + Fit Width - + Fit Page - + Zoom In - + Zoom Out - + Previous Page - + Next Page - + Single Page - + Facing Pages - + Print Setup - + Print - + Printing Setup - + Page &Setup - + Included Nodes - + Onl&y open node children - + &Draw lines to children - + Indent - + Indent Offse&t (line height units) - + Custom Size - + Inches (in) - + Millimeters (mm) - + Centimeters (cm) - + &Width: - + Height: - + Portra&it - + Lan&dscape - + Margins - + &Left: - + &Top: - + &Right: - + &Bottom: - + He&ader: - + Foot&er: - + Space between colu&mns - - &Use system default font - - - - + &Use TreeLine output view font - + Si&ze - + AaBbCcDdEeFfGg...TtUuVvWvXxYyZz - + Header &Right - + Footer Righ&t - + Field For&mat - + Field Format for "{0}" - + Output &Format - + &Suffix + + + Error: Page size or margins are invalid + + + + + TreeLine PDF Printer + + + + + Select &Printer + + recentfiles Recent Files - Недавние файлы + Недавние файлы spellcheck - + Could not find either aspell.exe, ispell.exe or hunspell.exe Browse for location? - + Spell Check Error - + Locate aspell.exe, ipsell.exe or hunspell.exe - + Program (*.exe) Программа (*.exe) - + TreeLine Spell Check Error Make sure aspell, ispell or hunspell is installed - + Finished checking the branch Continue from the root branch? - Проверка ветви закончена + Проверка ветви закончена Продолжить от корня? - + TreeLine Spell Check Проверка орфографии - + Finished spell checking - + Spell Check Проверка орфографии - + Not in Dictionary Не в словаре - + Word: Слово: - + Context: Контекст: - + Suggestions Предложение - + Ignor&e &Игнорировать - + &Ignore All Игнорировать &все - + &Add &Добавить - + Add &Lowercase Добавить &прописные - + &Replace &Заменить - + Re&place All Заменить &все - + &Cancel О&тмена + + + Finished checking the branch +Continue from the top? + + treeformats - + DEFAULT СТАНДАРТ @@ -3351,564 +3545,606 @@ treelocalcontrol - - Warning: duplicate Unique IDs found. - - - - - - Many Unique IDs were re-assigned. - - - - - - The following IDs were re-assigned: - - - - - - -Internal link targets could be affected. - - - - + Error - could not delete backup file {} - + Save changes to {}? - + Save changes? Сохранить изменения? - + &Save &Сохранить - + Save File Сохранить файл - + Save the current file - + Save &As... Сохранить &как... - + Save the file with a new name Сохранить файл с новым именем - + &Export... &Экспорт... - + Export the file in various other formats - + Prop&erties... - + Set file parameters like compression and encryption - + P&rint Setup... - + Set margins, page size and other printing options - + Print Pre&view... - + Show a preview of printing results - + &Print... Пе&чать... - + Print tree output based on current options - + Print &to PDF... - + Export to PDF with current printing options - + &Undo О&тменить - + Undo the previous action Отменить последнее действие - + &Redo &Повторить - + Redo the previous undo Повторить последнее действие - + Cu&t Вы&резать - + Cut the branch or text to the clipboard Вырезать ветвь или текст в буфер обмена - + &Copy &Копировать - + Copy the branch or text to the clipboard Копировать ветвь или текст в буфер обмена - + &Paste &Вставить - + Paste nodes or text from the clipboard Вставить узлы или текст из буфера обмена - - P&aste Plain Text - - - - + Paste non-formatted text from the clipboard - + &Bold Font - + Set the current or selected font to bold - + &Italic Font - + Set the current or selected font to italic - + U&nderline Font - + Set the current or selected font to underline - + &Font Size - + Set size of the current or selected text - + Small - + Default - + Large - + Larger - + Largest - + Set Font Size - + Font C&olor... - + Set the color of the current or selected text - + &External Link... - + Add or modify an extrnal web link - + Internal &Link... - + Add or modify an internal node link - + Clear For&matting - + Clear current or selected text formatting - + &Rename - + Rename the current tree entry title - + Insert Sibling &Before Вставить родственника &перед - + Insert new sibling before selection Вставить нового родственника перед выделением - + Insert Sibling &After Вставить родственника &после - + Insert new sibling after selection Вставить нового родственника после выделения - + Add &Child - + Add new child to selected parent - + &Delete Node &Удалить узлы - + Delete the selected nodes Удалить выбранные узлы - + &Indent Node Сдвинуть узел в&право - + Indent the selected nodes Сдвинуть выделенные узлы вправо - + &Unindent Node - + Unindent the selected nodes Сдвинуть выделенные узлы влево - + &Move Up Переместить &вверх - + Move the selected nodes up Переместить выделенные узлы вверх - + M&ove Down Переместить в&низ - + Move the selected nodes down Переместить выделенные узлы вниз - + Move &First - + Move the selected nodes to be the first children - + Move &Last - + Move the selected nodes to be the last children - + &Set Node Type - + Set the node type for selected nodes - + Set Node Type - + Copy Types from &File... - + Copy the configuration from another TreeLine file Копировать конфигурацию из другого файла TreeLine - + Flatten &by Category - + Collapse descendants by merging fields - + Add Category &Level... - + Insert category nodes above children Добавить узлы категорий перед потомками - - Flatten b&y Link... + + &Spell Check... - - Collapse descendants and insert parent links + + &New Window - - &Arrange by Link... + + Open a new window for the same file - - Arrange descendants using parent links + + Error - could not write to {} - - &Spell Check... + + TreeLine - Save As - - Spell check the tree')s text data + + Error - could not write to file - - &New Window + + TreeLine - Open Configuration File - - Open a new window for the same file + + Error - could not read file {0} - - Error - could not write to {} + + Cannot expand without common fields + Невозможно развернуть без общих полей + + + + Category Fields + Категории полей + + + + Select fields for new level + Выбор полей для нового уровня + + + + File saved - - TreeLine - Save As + + Pa&ste Plain Text - - Error - could not write to file + + Paste C&hild - - TreeLine - Open Configuration File + + Paste a child node from the clipboard - - Error - could not read file {0} + + Paste Sibling &Before - - Cannot expand without common fields - Невозможно развернуть без общих полей + + Paste a sibling before selection + - - Category Fields - Категории полей + + Paste Sibling &After + - - Select fields for new level - Выбор полей для нового уровня + + Paste a sibling after selection + - - Flatten by Link + + Paste Cl&oned Child - - Enter a new field name for parent links + + Paste a child clone from the clipboard - - Link Field + + Paste Clo&ned Sibling Before - - Select field with links to parents + + Paste a sibling clone before selection - - File saved + + Paste Clone&d Sibling After + + + + + Paste a sibling clone after selection + + + + + Clone All &Matched Nodes + + + + + Convert all matching nodes into clones + + + + + &Detach Clones + + + + + Detach all cloned nodes in current branches + + + + + S&wap Category Levels + + + + + Swap child and grandchild category nodes + + + + + Converted {0} branches into clones + + + + + No identical nodes found + + + + + Warning - file corruption! +Skipped bad child references in the following nodes: + + + + + Spell check the tree's text data treemaincontrol - + Warning: Could not create local socket @@ -3918,394 +4154,408 @@ - + Error - could not read file {0} - - Error - {0} is not a valid TreeLine file - - - - + Backup file "{}" exists. A previous session may have crashed - + &Restore Backup Восстановить из &резервной копии - + &Delete Backup &Удалить резервную копию - + &Cancel File Open О&тменить открытие файла - + Error - could not rename "{0}" to "{1}" - + Error - could not remove backup file {} - - Warning - could not load plugin module {0} - - - - + &New... - + New File Новый файл - + Start a new file Создать новый файл - + &Open... &Открыть... - + Open File Открыть файл - + Open a file from disk Открыть файл с диска - + Open Sa&mple... Открыть &пример... - + Open Sample - + Open a sample file - + &Import... - + Open a non-TreeLine file - + &Quit В&ыход - + Exit the application Выйти из приложения - + &Select All - + Select all text in an editor - + &Configure Data Types... Наст&роить типы данных... - + Modify data types, fields & output lines - + Sor&t Nodes... - + Define node sort operations - + Update &Numbering... - + Update node numbering fields - + &Find Text... - + Find text in node titles & data - + &Conditional Find... - + Use field conditions to find nodes - + Find and &Replace... - + Replace text strings in node data - + &Text Filter... - + Filter nodes to only show text matches - + C&onditional Filter... - + Use field conditions to filter nodes - + &General Options... Основные наст&ройки... - + Set user preferences for all files Задать пользовательские парметры для всех файлов - + Set &Keyboard Shortcuts... - + Customize keyboard commands - + C&ustomize Toolbars... - + Customize toolbar buttons - + Customize Fo&nts... - + Customize fonts in various views - + &Basic Usage... - + Display basic usage instructions - + &Full Documentation... - + Open a TreeLine file with full documentation - + &About TreeLine... - + Display version info about this program - - &About &Plugins... - - - - + Show loaded plugin modules - Показать загруженные модули + Показать загруженные модули - + &Select Template - + TreeLine - Open File - + Open Sample File - + &Select Sample - + Conditional Find - + Conditional Filter - - ToolsFilterCondition - - - - + General Options Основные настройки - + Error - basic help file not found - + TreeLine Basic Usage - + Error - documentation file not found - - TreeLine, Version {0} -by {1} + + Error - invalid TreeLine file {0} + + + + + TreeLine version {0} + + + + + written by {0} + + + + + Library versions: treemodel - + Main - Корень + Корень treenode - + New Новый - treeopener + treestructure - - Invalid XML file - - - - - Bad elememnt - not a valid TreeLine file - + + Main + Корень treeview - + Filtering by "{0}", found {1} nodes - + Conditional filtering, found {0} nodes + + + Search for: + + + + + Search for: {0} + + + + + Search for: {0} (not found) + + + + + Next: {0} + + + + + Next: {0} (not found) + + treewindow - + Data Output @@ -4315,149 +4565,179 @@ - + Title List - + &Expand Full Branch &Развернуть всю ветвь - + Expand all children of the selected nodes - + &Collapse Full Branch &Свернуть всю ветвь - + Collapse all children of the selected nodes - + &Previous Selection - + Return to the previous tree selection - + &Next Selection - + Go to the next tree selection in history - + Show Data &Output - + Show data output in right view Показать вывод данных в правом виде - + Show Data &Editor Показать &редактор данных - + Show data editor in right view Показать редактор данных в правом виде - + Show &Title List - + Show title list in right view Показать заголовков в правом виде - + &Show Child Pane - + Toggle showing right-hand child views - - Show Output &Descedants - - - - + Toggle showing output view indented descendants - + &Close Window - + Close this window - + &File &Файл - + &Edit &Правка - + &Node - + &Data &Данные - + &Tools &Инструменты - + &View &Вид - + &Window - + &Help &Справка + + + Start Incremental Search + + + + + Next Incremental Search + + + + + Previous Incremental Search + + + + + Show &Breadcrumb View + + + + + Toggle showing breadcrumb ancestor view + + + + + Show Output &Descendants + + + + + Fo&rmat + + diff -Nru treeline-2.0.2/i18n/translations/treeline_xx.ts treeline-3.0.1/i18n/translations/treeline_xx.ts --- treeline-2.0.2/i18n/translations/treeline_xx.ts 2018-04-08 20:12:57.000000000 +0000 +++ treeline-3.0.1/i18n/translations/treeline_xx.ts 2018-10-17 14:52:24.000000000 +0000 @@ -3,446 +3,431 @@ conditional - + starts with - + ends with - + contains - + True - + False - + and - + or - + [All Types] - + Node Type - + &Add New Rule - + &Remove Rule - + &OK - + &Cancel - - R&etrieve Rules... - - - - - &Save Rules... - - - - + Find &Previous - + Find &Next - + &Filter - + &End Filter - + &Close - - Save Rules + + No conditional matches were found - - Enter a descriptive name + + Rule {0} - - No conditional matches were found + + Saved Rules - - Rule {0} + + Name: - - Retrieve Rules + + &Load - - Select rule set to retrieve: + + &Save - - Remove Rule + + &Delete configdialog - + Configure Data Types - + T&ype List - + Typ&e Config - + Field &List - + &Field Config - + O&utput - + &Show Advanced - + &OK - + &Apply - + &Reset - + &Cancel - + &Hide Advanced - + Error - circular reference in math field equations - + Add or Remove Data Types - + &New Type... - + Co&py Type... - + Rena&me Type... - + &Delete Type - + Add Type - + Enter new type name: - + Copy Type - + &Derive from original - + Rename Type - + Rename from {} to: - + Cannot delete data type being used by nodes - + [None] no type set - + &Data Type - + Default Child &Type - + Icon - + Change &Icon - + Output Options - + Add &blank lines between nodes - + Allow &HTML rich text in format - + Add text bullet&s - + Use a table for field &data - + Combination && Child List Output &Separator - - Uni&que ID Reference Field - - - - + Derived from &Generic Type - + Automatic Types - + None - + Modify Co&nditional Types - + Create Co&nditional Types - + Set Types Conditionally - + Modify &Field List - + Name - + Type - + Sort Key - + Move U&p - + Move Do&wn - + &New Field... - + Rena&me Field... - + Dele&te Field - + Sort &Keys... - + fwd - + rev - + Add Field - + Enter new field name: - + Rename Field - + File Info Reference - + F&ield - + &Field Type @@ -497,814 +482,894 @@ - + F&ield List - + &Title Format - + Out&put Format - + Other Field References - + Reference Le&vel - + Refere&nce Type - + The name cannot be empty - + The name must start with a letter - + The name cannot start with "xml" - + The name cannot contain spaces - + The following characters are not allowed: {} - + The name was already used - + Set Data Type Icon - + Clear &Select - + forward - + reverse - + Sort Key Fields - + Available &Fields - + &Sort Criteria - + Field - + Direction - + Move &Up - + &Move Down - + Flip &Direction - + Self Reference - + Parent Reference - + Root Reference - + Child Reference - + Child Count - - Numeric Result - - - - + Date Result - + Time Result - + add - + subtract - + multiply - + divide - + floor divide - + modulus - + power - + sum of items - + maximum - + minimum - + average - + absolute value - + square root - + natural logarithm - + base-10 logarithm - + factorial - + round to num digits - + lower integer - + higher integer - + truncated integer - + floating point - + sine of radians - + cosine of radians - + tangent of radians - + arc sine - + arc cosine - + arc tangent - + radians to degrees - + degrees to radians - + pi constant - + natural log constant - + Define Math Field Equation - + Field References - + Reference &Level - + Reference &Type - + Available &Field List - + &Result Type - + Description - + &Equation - + Equation error: {} - + Boolean Result - + Text Result - + Arithmetic Operators - + Comparison Operators - + Text Operators - + equal to - + less than - + greater than - + less than or equal to - + greater than or equal to - + not equal to - + true value, condition, false value - + true if 1st text arg starts with 2nd arg - + true if 1st text arg ends with 2nd arg - + true if 1st text arg contains 2nd arg - + concatenate text - + join text using 1st arg as separator - + convert text to upper case - + convert text to lower case - + in 1st arg, replace 2nd arg with 3rd arg - + Operations - + O&perator Type - + Oper&ator List - + logical and - + logical or + + + Output HTML + + + + + Evaluate &HTML tags + + + + + Child Type Limits + + + + + [All Types Available] + + + + + &Select All + + + + + Select &None + + + + + Number Result + + dataeditors - + Today's &Date - + &Open Link - + Open &Folder - + External Link - + Scheme - + &Browse for File - + File Path Type - + Absolute - + Relative - + Address - + Display Name - + &OK - + &Cancel - + TreeLine - External Link File - + &Go to Target - - Enable click-on-&target + + Internal Link - - Disable click-on-&target + + &Open Picture - - Internal Link + + Picture Link - - Unique ID + + TreeLine - Picture File - - &Open Picture + + Set to &Now - - Picture Link + + Clear &Link - - TreeLine - Picture File + + (Click link target in tree) - - - dataeditview - - Unique ID + + link exports - + Bookmarks - + TreeLine - Export HTML - + TreeLine - Export Text Titles - + TreeLine - Export Plain Text - + TreeLine - Export Text Tables - + TreeLine - Export Generic XML - + TreeLine - Export TreeLine Subtree - + TreeLine - Export ODF Text - + TreeLine - Export HTML Bookmarks - + TreeLine - Export XBEL Bookmarks - + &HTML - + &Text - - &XML - - - - + &ODF Outline - + Book&marks - + &Single HTML page - + Single &HTML page with navigation pane - + Multiple HTML &pages with navigation pane - + Multiple HTML &data tables - + &Tabbed title text - + &Unformatted output of all text - - Tab &delimited table of children - - - - - Generic &XML output - - - - - &Subtree in TreeLine format - - - - + &HTML format bookmarks - + &XBEL format bookmarks - + File Export - + Choose export format type - + Choose export format subtype - + Choose export options - + What to Export - + &Entire tree - + Selected &branches - + Selected &nodes - + Other Options - - &Include root node - - - - + &Only open node children - + Include &print header && footer - + &Columns - + Navigation pane &levels + + + Error - export template files not found. +Check your TreeLine installation. + + + + + Error - cannot link to unsaved TreeLine file. +Save the file and retry. + + + + + Warning - no relative path from "{0}" to "{1}". +Continue with absolute path? + + + + + Parent + + + + + Tree&Line + + + + + &XML (generic) + + + + + Live tree view, linked to TreeLine file (for web server) + + + + + Live tree view, single file (embedded data) + + + + + &Comma delimited (CSV) table of descendants (level numbers) + + + + + Comma &delimited (CSV) table of children (single level) + + + + + Tab &delimited table of children (&single level) + + + + + &Old TreeLine (2.0.x) + + + + + &TreeLine Subtree + + + + + &Include root nodes + + + + + Must select nodes prior to export + + fieldformat @@ -1344,7 +1409,7 @@ - + Boolean @@ -1359,12 +1424,12 @@ - + Choice - + AutoChoice @@ -1374,12 +1439,12 @@ - + AutoCombination - + ExternalLink @@ -1389,328 +1454,333 @@ - + Picture - + RegularExpression - + Now - + Optional Digit# - + Required Digit0 - + Digit or Space (external)<space> - + Decimal Point. - + Decimal Comma, - - Comma Separator, - - - - - Dot Separator. - - - - + Space Separator (internal)<space> - + Optional Sign- - + Required Sign+ - + Exponent (capital)E - + Exponent (small)e - + Number1 - + Capital LetterA - + Small Lettera - + Capital Roman NumeralI - + Small Roman Numerali - + Level Separator/ - + Section Separator. - + "/" Character// - + "." Character.. - + Outline ExampleI../A../1../a)/i) - + Section Example1.1.1.1 - + Separator/ - + Example1/2/3/4 - + yes/no - + true/false - + T/F - + Y/N - - Day (1 or 2 digits)d + + Any Character. - - Day (2 digits)dd + + End of Text$ - - Weekday Abbreviationddd + + 0 Or More Repetitions* - - Weekday Namedddd + + 1 Or More Repetitions+ - - Month (1 or 2 digits)M + + 0 Or 1 Repetitions? - - Month (2 digits)MM + + Set of Numbers[0-9] - - Month AbbreviationMMM + + Lower Case Letters[a-z] - - Month NameMMMM + + Upper Case Letters[A-Z] - - Year (2 digits)yy + + Not a Number[^0-9] - - Year (4 digits)yyyy + + Or| - - Hour (0-23, 1 or 2 digits)H + + Escape a Special Character\ - - Hour (00-23, 2 digits)HH + + DateTime - - Hour (1-12, 1 or 2 digits)h + + Day (1 or 2 digits)%-d - - Hour (01-12, 2 digits)hh + + Day (2 digits)%d - - Minute (1 or 2 digits)m + + Weekday Abbreviation%a - - Minute (2 digits)mm + + Weekday Name%A - - Second (1 or 2 digits)s + + Month (1 or 2 digits)%-m - - Second (2 digits)ss + + Month (2 digits)%m - - Milliseconds (1 to 3 digits)z + + Month Abbreviation%b - - Milliseconds (3 digits)zzz + + Month Name%B - - AM/PMAP + + Year (2 digits)%y - - am/pmap + + Year (4 digits)%Y - - Any Character. + + Week Number (0 to 53)%-U - - End of Text$ + + Day of year (1 to 366)%-j - - 0 Or More Repetitions* + + Hour (0-23, 1 or 2 digits)%-H - - 1 Or More Repetitions+ + + Hour (00-23, 2 digits)%H - - 0 Or 1 Repetitions? + + Hour (1-12, 1 or 2 digits)%-I - - Set of Numbers[0-9] + + Hour (01-12, 2 digits)%I - - Lower Case Letters[a-z] + + Minute (1 or 2 digits)%-M - - Upper Case Letters[A-Z] + + Minute (2 digits)%M - - Not a Number[^0-9] + + Second (1 or 2 digits)%-S - - Or| + + Second (2 digits)%S - - Escape a Special Character\ + + Microseconds (6 digits)%f + + + + + AM/PM%p + + + + + Comma Separator\, + + + + + Dot Separator\. @@ -1740,95 +1810,110 @@ globalref - + TreeLine Files - + TreeLine Files - Compressed - + TreeLine Files - Encrypted - + All Files - + HTML Files - + Text Files - + XML Files - + ODF Text Files - + Treepad Files - + PDF Files + + + CSV (Comma Delimited) Files + + + + + All TreeLine Files + + + + + Old TreeLine Files + + helpview - + Tools - + &Back - + &Forward - + &Home - + Find: - + Find &Previous - + Find &Next - + Text string not found @@ -1836,724 +1921,799 @@ imports - + &Tab indented text, one node per line - + Tab delimited text table with header &row - - Plain text, one node per &line (CR delimited) - - - - + Plain text &paragraphs (blank line delimited) - + Treepad &file (text nodes only) - + &Generic XML (non-TreeLine file) - + Open &Document (ODF) outline - + &HTML bookmarks (Mozilla Format) - + &XML bookmarks (XBEL format) - + FOLDER - + BOOKMARK - + SEPARATOR - + Link - + Text - + Import File - + Choose Import Method - + Invalid File - + "{0}" is not a valid TreeLine file. Use an import filter? - + TreeLine - Import File - + Error - could not read file {0} - + Error - improper format in {0} - + TABLE - + Bookmarks - + Too many entries on Line {0} - - - matheval - - Illegal "{}" characters + + Plain text, one &node per line (CR delimited) - - Child references must be combined in a function + + Bad CSV format on Line {0} - - Illegal syntax in equation + + Co&mma delimited (CSV) text table with level column && header row - - Illegal function present: {0} + + Comma delimited (CSV) text table &with header row - - Illegal object type or operator: {0} + + Other + + + + + Old Tree&Line File (1.x or 2.x) + + + + + Invalid level number on line {0} + + + + + Invalid level structure + + + + + matheval + + + Illegal "{}" characters + + + + + Child references must be combined in a function + + + + + Illegal syntax in equation + + + + + Illegal function present: {0} + + + + + Illegal object type or operator: {0} miscdialogs - + &OK - + &Cancel - + Fields - + File Properties - + File Storage - + &Use file compression - + Use file &encryption - + Spell Check - + Language code or dictionary (optional) - + Math Fields - + &Treat blank fields as zeros - + Encrypted File Password - + Type Password for "{0}": - + Type Password: - + Re-Type Password: - + Remember password during this session - + Zero-length passwords are not permitted - + Re-typed password did not match - + Default - Single Line Text - + &Search Text - + What to Search - + Full &data - + &Titles only - + How to Search - + &Key words - + Key full &words - + F&ull phrase - + &Regular expression - + Find - + Find &Previous - + Find &Next - + Filter - + &Filter - + &End Filter - + &Close - + Error - invalid regular expression - + Search string "{0}" not found - + Find and Replace - + Replacement &Text - + Any &match - + Full &words - + Re&gular expression - + &Node Type - + N&ode Fields - + &Find Next - + &Replace - + Replace &All - + [All Types] - + [All Fields] - + Search text "{0}" not found - + Error - replacement failed - + Replaced {0} matches - + Sort Nodes - + What to Sort - + &Entire tree - + Selected &branches - + Selection's childre&n - + Selection's &siblings - + Sort Method - + &Predefined Key Fields - + Node &Titles - + Sort Direction - + &Forward - + &Reverse - + &Apply - + Update Node Numbering - + What to Update - + &Selection's children - + Root Node - + Include top-level nodes - + Handling Nodes without Numbering Fields - + &Ignore and skip - + &Restart numbers for next siblings - + Reserve &numbers - + TreeLine Numbering - + No numbering fields were found in data types - + File Menu - + File - + Edit Menu - + Edit - + Node Menu - + Node - + Data Menu - + Data - + Tools Menu - + Tools - + View Menu - + View - + Window Menu - + Window - + Help Menu - + Help - + Keyboard Shortcuts - + &Restore Defaults - + Key {0} is already used - + Clear &Key - + --Separator-- - + Customize Toolbars - + Toolbar &Size - + Small Icons - + Large Icons - + Toolbar Quantity - + &Toolbars - + A&vailable Commands - + Tool&bar Commands - + Move &Up - + Move &Down - + Tree View Font - + Output View Font - + Editor View Font - - TreeLine Plugins + + No menu + + + + + TreeLine - Serious Error - - Plugin Modules Loaded + + A serious error has occurred. TreeLine could be in an unstable state. +Recommend saving any file changes under another filename and restart TreeLine. + +The debugging info shown below can be copied and emailed to doug101@bellz.org along with +an explanation of the circumstances. + + + + + + Format Menu + + + + + Format + + + + + Customize Fonts + + + + + &Use system default font + + + + + App Default Font + + + + + &Use app default font nodeformat - + Name @@ -2596,7 +2756,7 @@ - + Startup Condition @@ -2606,189 +2766,169 @@ - - Show child pane in right hand view - - - - + Show descendants in output view - + Restore tree view states of recent files - + Restore previous window geometry - + Features Available - + Open files in new windows - + Click node to rename - + Rename new nodes when created - + Tree drag && drop available - + Show icons in the tree view - - Show node unique IDs in the Data Edit view - - - - + Show math fields in the Data Edit view - + Show numbering fields in the Data Edit view - + Undo Memory - + Number of undo levels - + Auto Save - + Minutes between saves (set to 0 to disable) - + Recent Files - + Number of recent files in the file menu - + Data Editor Formats - + Times - + Dates - + First day of week - + Appearance - + Child indent offset (in font height units) - - Printer Settings - - - - - Spell Check - - - - - Fonts + + Show breadcrumb ancestor view - - Window Geometry + + Show child pane in right hand views - - Toolbar Geometry + + Remove inaccessible recent file entries - - Toolbar Commands + + Activate data editors on mouse hover options - + Choose configuration file location - + User's home directory (recommended) - + Program directory (for portable use) - + &OK - + &Cancel @@ -2796,553 +2936,573 @@ printdata - + Error initializing printer - + TreeLine - Export PDF + + + Warning: Page size and margin settings unsupported on current printer. +Save page adjustments? + + + + + Warning: Page size setting unsupported on current printer. +Save adjustment? + + + + + Warning: Margin settings unsupported on current printer. +Save adjustments? + + printdialogs - + Print Preview - + Fit Width - + Fit Page - + Zoom In - + Zoom Out - + Previous Page - + Next Page - + Single Page - + Facing Pages - + Print Setup - + Print - + Printing Setup - + &General Options - + Page &Setup - + &Font Selection - + &Header/Footer - + Print Pre&view... - + &Print... - + &OK - + &Cancel - + What to print - + &Entire tree - + Selected &branches - + Selected &nodes - + Included Nodes - + &Include root node - + Onl&y open node children - + Features - + &Draw lines to children - + &Keep first child with parent - + Indent - + Indent Offse&t (line height units) - + Letter (8.5 x 11 in.) - + Legal (8.5 x 14 in.) - + Tabloid (11 x 17 in.) - + A3 (279 x 420 mm) - + A4 (210 x 297 mm) - + A5 (148 x 210 mm) - + Custom Size - + Inches (in) - + Millimeters (mm) - + Centimeters (cm) - + &Units - + Paper &Size - + &Width: - + Height: - + Orientation - + Portra&it - + Lan&dscape - + Margins - + &Left: - + &Top: - + &Right: - + &Bottom: - + He&ader: - + Foot&er: - + Columns - + &Number of columns - + Space between colu&mns - + Default Font - - &Use system default font - - - - + &Use TreeLine output view font - + Select Font - + &Font - + Font st&yle - + Si&ze - + Sample - + AaBbCcDdEeFfGg...TtUuVvWvXxYyZz - + &Header Left - + Header C&enter - + Header &Right - + Footer &Left - + Footer Ce&nter - + Footer Righ&t - + Fiel&ds - + Field For&mat - + Header and Footer - + Field Format for "{0}" - + Output &Format - + Format &Help - + Extra Text - + &Prefix - + &Suffix - - - recentfiles - - Recent Files + + Error: Page size or margins are invalid + + + + + TreeLine PDF Printer + + + + + Select &Printer spellcheck - + Could not find either aspell.exe, ispell.exe or hunspell.exe Browse for location? - + Spell Check Error - + Locate aspell.exe, ipsell.exe or hunspell.exe - + Program (*.exe) - + TreeLine Spell Check Error Make sure aspell, ispell or hunspell is installed - - Finished checking the branch -Continue from the root branch? - - - - + TreeLine Spell Check - + Finished spell checking - + Spell Check - + Not in Dictionary - + Word: - + Context: - + Suggestions - + Ignor&e - + &Ignore All - + &Add - + Add &Lowercase - + &Replace - + Re&place All - + &Cancel + + + Finished checking the branch +Continue from the top? + + treeformats - + DEFAULT @@ -3350,564 +3510,606 @@ treelocalcontrol - - Warning: duplicate Unique IDs found. - - - - - - Many Unique IDs were re-assigned. - - - - - - The following IDs were re-assigned: - - - - - - -Internal link targets could be affected. - - - - + Error - could not delete backup file {} - + Save changes to {}? - + Save changes? - + &Save - + Save File - + Save the current file - + Save &As... - + Save the file with a new name - + &Export... - + Export the file in various other formats - + Prop&erties... - + Set file parameters like compression and encryption - + P&rint Setup... - + Set margins, page size and other printing options - + Print Pre&view... - + Show a preview of printing results - + &Print... - + Print tree output based on current options - + Print &to PDF... - + Export to PDF with current printing options - + &Undo - + Undo the previous action - + &Redo - + Redo the previous undo - + Cu&t - + Cut the branch or text to the clipboard - + &Copy - + Copy the branch or text to the clipboard - + &Paste - + Paste nodes or text from the clipboard - - P&aste Plain Text - - - - + Paste non-formatted text from the clipboard - + &Bold Font - + Set the current or selected font to bold - + &Italic Font - + Set the current or selected font to italic - + U&nderline Font - + Set the current or selected font to underline - + &Font Size - + Set size of the current or selected text - + Small - + Default - + Large - + Larger - + Largest - + Set Font Size - + Font C&olor... - + Set the color of the current or selected text - + &External Link... - + Add or modify an extrnal web link - + Internal &Link... - + Add or modify an internal node link - + Clear For&matting - + Clear current or selected text formatting - + &Rename - + Rename the current tree entry title - + Insert Sibling &Before - + Insert new sibling before selection - + Insert Sibling &After - + Insert new sibling after selection - + Add &Child - + Add new child to selected parent - + &Delete Node - + Delete the selected nodes - + &Indent Node - + Indent the selected nodes - + &Unindent Node - + Unindent the selected nodes - + &Move Up - + Move the selected nodes up - + M&ove Down - + Move the selected nodes down - + Move &First - + Move the selected nodes to be the first children - + Move &Last - + Move the selected nodes to be the last children - + &Set Node Type - + Set the node type for selected nodes - + Set Node Type - + Copy Types from &File... - + Copy the configuration from another TreeLine file - + Flatten &by Category - + Collapse descendants by merging fields - + Add Category &Level... - + Insert category nodes above children - - Flatten b&y Link... + + &Spell Check... - - Collapse descendants and insert parent links + + &New Window - - &Arrange by Link... + + Open a new window for the same file - - Arrange descendants using parent links + + Error - could not write to {} - - &Spell Check... + + TreeLine - Save As - - Spell check the tree')s text data + + Error - could not write to file - - &New Window + + TreeLine - Open Configuration File - - Open a new window for the same file + + Error - could not read file {0} - - Error - could not write to {} + + Cannot expand without common fields - - TreeLine - Save As + + Category Fields - - Error - could not write to file + + Select fields for new level - - TreeLine - Open Configuration File + + File saved - - Error - could not read file {0} + + Pa&ste Plain Text - - Cannot expand without common fields + + Paste C&hild - - Category Fields + + Paste a child node from the clipboard - - Select fields for new level + + Paste Sibling &Before - - Flatten by Link + + Paste a sibling before selection - - Enter a new field name for parent links + + Paste Sibling &After - - Link Field + + Paste a sibling after selection - - Select field with links to parents + + Paste Cl&oned Child - - File saved + + Paste a child clone from the clipboard + + + + + Paste Clo&ned Sibling Before + + + + + Paste a sibling clone before selection + + + + + Paste Clone&d Sibling After + + + + + Paste a sibling clone after selection + + + + + Clone All &Matched Nodes + + + + + Convert all matching nodes into clones + + + + + &Detach Clones + + + + + Detach all cloned nodes in current branches + + + + + S&wap Category Levels + + + + + Swap child and grandchild category nodes + + + + + Converted {0} branches into clones + + + + + No identical nodes found + + + + + Warning - file corruption! +Skipped bad child references in the following nodes: + + + + + Spell check the tree's text data treemaincontrol - + Warning: Could not create local socket @@ -3917,394 +4119,395 @@ - + Error - could not read file {0} - - Error - {0} is not a valid TreeLine file - - - - + Backup file "{}" exists. A previous session may have crashed - + &Restore Backup - + &Delete Backup - + &Cancel File Open - + Error - could not rename "{0}" to "{1}" - + Error - could not remove backup file {} - - Warning - could not load plugin module {0} - - - - + &New... - + New File - + Start a new file - + &Open... - + Open File - + Open a file from disk - + Open Sa&mple... - + Open Sample - + Open a sample file - + &Import... - + Open a non-TreeLine file - + &Quit - + Exit the application - + &Select All - + Select all text in an editor - + &Configure Data Types... - + Modify data types, fields & output lines - + Sor&t Nodes... - + Define node sort operations - + Update &Numbering... - + Update node numbering fields - + &Find Text... - + Find text in node titles & data - + &Conditional Find... - + Use field conditions to find nodes - + Find and &Replace... - + Replace text strings in node data - + &Text Filter... - + Filter nodes to only show text matches - + C&onditional Filter... - + Use field conditions to filter nodes - + &General Options... - + Set user preferences for all files - + Set &Keyboard Shortcuts... - + Customize keyboard commands - + C&ustomize Toolbars... - + Customize toolbar buttons - + Customize Fo&nts... - + Customize fonts in various views - + &Basic Usage... - + Display basic usage instructions - + &Full Documentation... - + Open a TreeLine file with full documentation - + &About TreeLine... - + Display version info about this program - - &About &Plugins... - - - - - Show loaded plugin modules - - - - + &Select Template - + TreeLine - Open File - + Open Sample File - + &Select Sample - + Conditional Find - + Conditional Filter - - ToolsFilterCondition - - - - + General Options - + Error - basic help file not found - + TreeLine Basic Usage - + Error - documentation file not found - - TreeLine, Version {0} -by {1} + + Error - invalid TreeLine file {0} - - - treemodel - - Main + + TreeLine version {0} + + + + + written by {0} + + + + + Library versions: treenode - + New - treeopener + treestructure - - Invalid XML file - - - - - Bad elememnt - not a valid TreeLine file + + Main treeview - + Filtering by "{0}", found {1} nodes - + Conditional filtering, found {0} nodes + + + Search for: + + + + + Search for: {0} + + + + + Search for: {0} (not found) + + + + + Next: {0} + + + + + Next: {0} (not found) + + treewindow - + Data Output @@ -4314,149 +4517,179 @@ - + Title List - + &Expand Full Branch - + Expand all children of the selected nodes - + &Collapse Full Branch - + Collapse all children of the selected nodes - + &Previous Selection - + Return to the previous tree selection - + &Next Selection - + Go to the next tree selection in history - + Show Data &Output - + Show data output in right view - + Show Data &Editor - + Show data editor in right view - + Show &Title List - + Show title list in right view - + &Show Child Pane - + Toggle showing right-hand child views - - Show Output &Descedants - - - - + Toggle showing output view indented descendants - + &Close Window - + Close this window - + &File - + &Edit - + &Node - + &Data - + &Tools - + &View - + &Window - + &Help + + + Start Incremental Search + + + + + Next Incremental Search + + + + + Previous Incremental Search + + + + + Show &Breadcrumb View + + + + + Toggle showing breadcrumb ancestor view + + + + + Show Output &Descendants + + + + + Fo&rmat + + Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/dataaddcategory.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/dataaddcategory.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/dataclonematches.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/dataclonematches.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/dataconfigtype.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/dataconfigtype.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/datacopytype.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/datacopytype.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/datadetachclones.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/datadetachclones.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/dataflatcategory.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/dataflatcategory.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/datanodetype.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/datanodetype.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/datanumbering.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/datanumbering.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/dataswapcategory.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/dataswapcategory.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/editboldfont.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/editboldfont.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/editcopy.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/editcopy.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/editcut.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/editcut.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/editextlink.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/editextlink.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/editintlink.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/editintlink.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/edititalicfont.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/edititalicfont.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/editpasteafter.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/editpasteafter.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/editpastebefore.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/editpastebefore.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/editpastechild.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/editpastechild.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/editpastecloneafter.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/editpastecloneafter.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/editpasteclonebefore.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/editpasteclonebefore.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/editpasteclonechild.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/editpasteclonechild.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/editpasteplain.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/editpasteplain.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/editpaste.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/editpaste.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/editunderlinefont.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/editunderlinefont.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/fileexport.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/fileexport.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/fileimport.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/fileimport.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/fileopensample.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/fileopensample.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/fileprintpdf.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/fileprintpdf.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/fileproperties.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/fileproperties.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/formatboldfont.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/formatboldfont.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/formatclearformat.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/formatclearformat.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/formatextlink.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/formatextlink.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/formatfontcolor.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/formatfontcolor.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/formatfontsize.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/formatfontsize.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/formatintlink.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/formatintlink.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/formatitalicfont.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/formatitalicfont.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/formatselectall.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/formatselectall.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/formatunderlinefont.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/formatunderlinefont.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/helpbasic.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/helpbasic.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/helpfull.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/helpfull.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/nodeaddchild.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/nodeaddchild.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/nodeindent.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/nodeindent.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/nodeinsertafter.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/nodeinsertafter.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/nodeinsertbefore.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/nodeinsertbefore.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/nodeunindent.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/nodeunindent.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/toolsfiltercondition.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/toolsfiltercondition.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/toolsfindcondition.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/toolsfindcondition.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/toolsfindreplace.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/toolsfindreplace.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/toolsfonts.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/toolsfonts.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/toolsgenoptions.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/toolsgenoptions.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/toolsshortcuts.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/toolsshortcuts.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/toolstoolbars.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/toolstoolbars.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/viewbreadcrumb.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/viewbreadcrumb.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/viewdataeditor.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/viewdataeditor.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/viewdataoutput.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/viewdataoutput.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/toolbar/32x32/viewtitlelist.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/toolbar/32x32/viewtitlelist.png differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/icons/treeline-icon.png and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/icons/treeline-icon.png differ diff -Nru treeline-2.0.2/icons/treeline-icon.svg treeline-3.0.1/icons/treeline-icon.svg --- treeline-2.0.2/icons/treeline-icon.svg 1970-01-01 00:00:00.000000000 +0000 +++ treeline-3.0.1/icons/treeline-icon.svg 2018-04-08 19:19:50.000000000 +0000 @@ -0,0 +1,124 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + +   + + + + + + + + diff -Nru treeline-2.0.2/install.py treeline-3.0.1/install.py --- treeline-2.0.2/install.py 2015-03-07 19:15:35.000000000 +0000 +++ treeline-3.0.1/install.py 2018-04-08 19:19:50.000000000 +0000 @@ -4,7 +4,7 @@ **************************************************************************** install.py, Linux install script for TreeLine - Copyright (C) 2015, Douglas W. Bell + Copyright (C) 2018, 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 @@ -28,7 +28,7 @@ progName = 'treeline' docDir = 'share/doc/{0}'.format(progName) templateDir = 'share/{0}/templates'.format(progName) -iconDir = 'share/icons/{0}'.format(progName) +iconToolDir = 'share/icons/{0}'.format(progName) testSpell = True def usage(exitCode=2): @@ -49,7 +49,8 @@ .format(docDir)) print(' -t dir template dir [default: /{0}]' .format(templateDir)) - print(' -i dir icon dir [default: /{0}]'.format(iconDir)) + print(' -i dir tool icon dir [default: /{0}]' + .format(iconToolDir)) print(' -b dir temporary build root for packagers [default: {0}]' .format(buildRoot)) print(' -s skip language translation files') @@ -187,7 +188,7 @@ global prefixDir global docDir global templateDir - global iconDir + global iconToolDir global buildRoot global progName depCheck = True @@ -202,7 +203,7 @@ elif opt == '-t': templateDir = val elif opt == '-i': - iconDir = val + iconToolDir = val elif opt == '-b': buildRoot = val elif opt == '-s': @@ -222,31 +223,30 @@ print('Checking dependencies...') pyVersion = sys.version_info[:3] pyVersion = '.'.join([str(num) for num in pyVersion]) - if cmpVersions(pyVersion, (3, 2)): + if cmpVersions(pyVersion, (3, 5)): print(' Python Version {0} -> OK'.format(pyVersion)) else: - print(' Python Version {0} -> Sorry, 3.2 or higher is required' + print(' Python Version {0} -> Sorry, 3.5 or higher is required' .format(pyVersion)) sys.exit(3) try: - from PyQt4 import QtCore, QtGui + from PyQt5 import QtCore, QtWidgets except: - print(' PyQt not found -> Sorry, PyQt 4.x is required' + print(' PyQt not found -> Sorry, PyQt 5.8 or higher is required' ' and must be built for Python 3') sys.exit(3) qtVersion = QtCore.qVersion() - if cmpVersions(qtVersion, (4, 6)) and not qtVersion.startswith('5'): + if cmpVersions(qtVersion, (5, 8)): print(' Qt Version {0} -> OK'.format(qtVersion)) else: - print(' Qt Version {0} -> Sorry, 4.6, 4.7 or 4.8 is required' + print(' Qt Version {0} -> Sorry, 5.8 or higher is required' .format(qtVersion)) sys.exit(3) pyqtVersion = QtCore.PYQT_VERSION_STR - if (cmpVersions(pyqtVersion, (4, 8)) and - not pyqtVersion.startswith('5')): + if cmpVersions(pyqtVersion, (5, 8)): print(' PyQt Version {0} -> OK'.format(pyqtVersion)) else: - print(' PyQt Version {0} -> Sorry, 4.8 through 4.11 is required' + print(' PyQt Version {0} -> Sorry, 5.8 or higher is required' .format(pyqtVersion)) sys.exit(3) global testSpell @@ -257,15 +257,10 @@ pythonBuildDir = os.path.join(buildRoot, pythonPrefixDir[1:]) if os.path.isdir('source'): - compileall.compile_dir('source', - ddir=os.path.join(prefixDir, 'source')) print('Installing files...') print(' Copying python files to {0}'.format(pythonBuildDir)) removeDir(pythonBuildDir) # remove old? copyDir('source', pythonBuildDir) - if os.path.isdir('source/__pycache__'): - copyDir('source/__pycache__', - os.path.join(pythonBuildDir, '__pycache__')) if os.path.isfile('source/plugininterface.py'): pluginBuildDir = os.path.join(pythonBuildDir, 'plugins') print(' Creating plugins directory if necessary') @@ -295,7 +290,7 @@ sampleBuildDir = os.path.join(docBuildDir, 'samples') print(' Copying sample files to {0}'.format(sampleBuildDir)) copyDir('samples', sampleBuildDir) - # update help file location in main python script + # update sample file location in main python script replaceLine(os.path.join(pythonBuildDir, '{0}.py'.format(progName)), 'samplePath = None', @@ -312,11 +307,14 @@ for name in glob.glob(os.path.join(templateBuildDir, '*.trl')): if 'en_' not in os.path.basename(name): os.remove(name) - # update help file location in main python script + # update template file location in main python script replaceLine(os.path.join(pythonBuildDir, '{0}.py'.format(progName)), 'templatePath = None', 'templatePath = \'{0}\' # modified by install script\n' .format(templatePrefixDir)) + if os.path.isdir('templates/exports'): + exportsBuildDir = os.path.join(templateBuildDir, 'exports') + copyDir('templates/exports', exportsBuildDir) if os.path.isdir('data'): dataPrefixDir = os.path.join(prefixDir, 'share', progName, 'data') dataBuildDir = os.path.join(buildRoot, dataPrefixDir[1:]) @@ -333,11 +331,11 @@ 'dataFilePath = \'{0}\' # modified by install script\n' .format(dataPrefixDir)) if os.path.isdir('icons'): - iconPrefixDir = iconDir.replace('/', '') + iconPrefixDir = iconToolDir.replace('/', '') if not os.path.isabs(iconPrefixDir): iconPrefixDir = os.path.join(prefixDir, iconPrefixDir) iconBuildDir = os.path.join(buildRoot, iconPrefixDir[1:]) - print(' Copying icon files to {0}'.format(iconBuildDir)) + print(' Copying tool icon files to {0}'.format(iconBuildDir)) copyDir('icons', iconBuildDir) # update icon location in main python script replaceLine(os.path.join(pythonBuildDir, '{0}.py'.format(progName)), @@ -355,6 +353,31 @@ os.path.join(iconToolBuildDir, '32x32')) if os.path.isdir('icons/tree'): copyDir('icons/tree', os.path.join(iconBuildDir, 'tree')) + if os.path.isfile(os.path.join('icons', progName + '-icon.png')): + pngIconPrefixDir = os.path.join(prefixDir, 'share', 'icons', + 'hicolor', '48x48', 'apps') + pngIconBuildDir = os.path.join(buildRoot, pngIconPrefixDir[1:]) + print(' Copying app icon files to {0}'.format(pngIconBuildDir)) + if not os.path.isdir(pngIconBuildDir): + os.makedirs(pngIconBuildDir) + shutil.copy2(os.path.join('icons', progName + '-icon.png'), + pngIconBuildDir) + if os.path.isfile(os.path.join('icons', progName + '-icon.svg')): + svgIconPrefixDir = os.path.join(prefixDir, 'share', 'icons', + 'hicolor', 'scalable', 'apps') + svgIconBuildDir = os.path.join(buildRoot, svgIconPrefixDir[1:]) + print(' Copying app icon files to {0}'.format(svgIconBuildDir)) + if not os.path.isdir(svgIconBuildDir): + os.makedirs(svgIconBuildDir) + shutil.copy2(os.path.join('icons', progName + '-icon.svg'), + svgIconBuildDir) + if os.path.isfile(progName + '.desktop'): + desktopPrefixDir = os.path.join(prefixDir, 'share', 'applications') + desktopBuildDir = os.path.join(buildRoot, desktopPrefixDir[1:]) + print(' Copying desktop file to {0}'.format(desktopBuildDir)) + if not os.path.isdir(desktopBuildDir): + os.makedirs(desktopBuildDir) + shutil.copy2(progName + '.desktop', desktopBuildDir) if os.path.isdir('source'): createWrapper(pythonPrefixDir, progName) @@ -364,6 +387,7 @@ if not os.path.isdir(binBuildDir): os.makedirs(binBuildDir) shutil.copy2(progName, binBuildDir) + compileall.compile_dir(pythonBuildDir, ddir=prefixDir) cleanSource() print('Install complete.') diff -Nru treeline-2.0.2/samples/110en_sample_basic_longtext.trl treeline-3.0.1/samples/110en_sample_basic_longtext.trl --- treeline-2.0.2/samples/110en_sample_basic_longtext.trl 2015-05-10 18:31:10.000000000 +0000 +++ treeline-3.0.1/samples/110en_sample_basic_longtext.trl 1970-01-01 00:00:00.000000000 +0000 @@ -1,28 +0,0 @@ - - -Text Fields - -Similar to Treepad -This file provides a single long text field for each node. This is similar to how the Treepad program on windows is usually used. - - -Regular text field -The most commonly used field type is regular text. Formatting such as <b>bold</b>, <i>italics</i>, font sizes and <span style="color:#aa0000">font colors</span> can be used from the Edit menu.<br /> -<br /> -It preserves carriage return spaces, but not multiple spaces within a line. - - -HTML text field -An HTML field allows tags such as <i>italics</i> to be added manually. - -It does not preserve white space. - -Characters like &lt;, &gt;, and &amp; must be escaped. - - -Spaced text field -A spaced text field preserves all white space. - -It does not allow character formatting. - - diff -Nru treeline-2.0.2/samples/110en_sample_basic_longtext.trln treeline-3.0.1/samples/110en_sample_basic_longtext.trln --- treeline-2.0.2/samples/110en_sample_basic_longtext.trln 1970-01-01 00:00:00.000000000 +0000 +++ treeline-3.0.1/samples/110en_sample_basic_longtext.trln 2018-04-08 19:19:50.000000000 +0000 @@ -0,0 +1,133 @@ +{ + "formats": [ + { + "fields": [ + { + "fieldname": "Name", + "fieldtype": "Text" + }, + { + "fieldname": "Text", + "fieldtype": "HtmlText", + "lines": 12 + } + ], + "formathtml": true, + "formatname": "HTML_TEXT", + "outputlines": [ + "{*Name*}", + "{*Text*}" + ], + "titleline": "{*Name*}" + }, + { + "fields": [ + { + "fieldname": "Name", + "fieldtype": "Text" + }, + { + "fieldname": "Text", + "fieldtype": "Text", + "lines": 12 + } + ], + "formathtml": true, + "formatname": "REGULAR_TEXT", + "outputlines": [ + "{*Name*}", + "{*Text*}" + ], + "titleline": "{*Name*}" + }, + { + "fields": [ + { + "fieldname": "Name", + "fieldtype": "Text" + } + ], + "formatname": "ROOT", + "outputlines": [ + "{*Name*}" + ], + "titleline": "{*Name*}" + }, + { + "fields": [ + { + "fieldname": "Name", + "fieldtype": "Text" + }, + { + "fieldname": "Text", + "fieldtype": "SpacedText", + "lines": 12 + } + ], + "formathtml": true, + "formatname": "SPACED_TEXT", + "outputlines": [ + "{*Name*}{*Text*}" + ], + "titleline": "{*Name*}" + } + ], + "nodes": [ + { + "children": [ + "ed02751e95a311e79cb17054d2175f18", + "ed02771c95a311e79cb17054d2175f18", + "ed027aaa95a311e79cb17054d2175f18", + "ed027f0095a311e79cb17054d2175f18" + ], + "data": { + "Name": "Text Fields" + }, + "format": "ROOT", + "uid": "ed0270d295a311e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Name": "Similar to Treepad", + "Text": "This file provides a single long text field for each node. This is similar to how the Treepad program on windows is usually used." + }, + "format": "REGULAR_TEXT", + "uid": "ed02751e95a311e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Name": "Regular text field", + "Text": "The most commonly used field type is regular text. Formatting such as bold, italics, font sizes and font colors can be used from the Edit menu.
\n
\nIt preserves carriage return spaces, but not multiple spaces within a line." + }, + "format": "REGULAR_TEXT", + "uid": "ed02771c95a311e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Name": "HTML text field", + "Text": "An HTML field allows tags such as italics to be added manually.\n\nIt does not preserve white space.\n\nCharacters like <, >, and & must be escaped." + }, + "format": "HTML_TEXT", + "uid": "ed027aaa95a311e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Name": "Spaced text field", + "Text": "A spaced text field preserves all white space.\n\nIt does not allow character formatting." + }, + "format": "SPACED_TEXT", + "uid": "ed027f0095a311e79cb17054d2175f18" + } + ], + "properties": { + "tlversion": "2.9.0", + "topnodes": [ + "ed0270d295a311e79cb17054d2175f18" + ] + } +} \ No newline at end of file diff -Nru treeline-2.0.2/samples/120en_sample_basic_contacts.trl treeline-3.0.1/samples/120en_sample_basic_contacts.trl --- treeline-2.0.2/samples/120en_sample_basic_contacts.trl 2015-01-31 19:24:19.000000000 +0000 +++ treeline-3.0.1/samples/120en_sample_basic_contacts.trl 1970-01-01 00:00:00.000000000 +0000 @@ -1,73 +0,0 @@ - - -Main - -Friends - -Bill -Smith -1492 Columbus Drive -Nina -SC -35762 -(703) 555-5647 - - -2004/11/30 -<a href="mailto:bill@pinta.com">bill@pinta.com</a> - - -John -Johnson -1941 Pearl Street -Harbor City -HI -86741 -(401) 555-8923 -(506) 555-7413 -(703) 555-2873 -1905/05/08 -<a href="mailto:jj@battleship.org">jj@battleship.org</a> - - - -Family - -Jane -Doe -101 Dalmation Way -Dogbone -SD -52782 -(654) 555-8527 -1901/07/30 -<a href="mailto:jdoe@spots.net">jdoe@spots.net</a> - - -John -Doe -20000 Leagues Street -Undersea -NY -12763 -(805) 555-7296 -(777) 555-9999 -(503) 555-8234 -1999/08/14 -<a href="mailto:johndoe@subs.com">johndoe@subs.com</a> - - - -Work - -Dil -Bert -(501) 555-5612 - - -Pointy-haired -Boss -(666) 555-8945 - - - diff -Nru treeline-2.0.2/samples/120en_sample_basic_contacts.trln treeline-3.0.1/samples/120en_sample_basic_contacts.trln --- treeline-2.0.2/samples/120en_sample_basic_contacts.trln 1970-01-01 00:00:00.000000000 +0000 +++ treeline-3.0.1/samples/120en_sample_basic_contacts.trln 2018-04-08 19:19:50.000000000 +0000 @@ -0,0 +1,235 @@ +{ + "formats": [ + { + "fields": [ + { + "fieldname": "FirstName", + "fieldtype": "Text" + }, + { + "fieldname": "LastName", + "fieldtype": "Text" + }, + { + "fieldname": "Street", + "fieldtype": "Text" + }, + { + "fieldname": "City", + "fieldtype": "Text" + }, + { + "fieldname": "State", + "fieldtype": "Text" + }, + { + "fieldname": "Zip", + "fieldtype": "Text" + }, + { + "fieldname": "HomePhone", + "fieldtype": "Text" + }, + { + "fieldname": "WorkPhone", + "fieldtype": "Text" + }, + { + "fieldname": "MobilePhone", + "fieldtype": "Text" + }, + { + "fieldname": "Birthday", + "fieldtype": "Date", + "format": "%B %-d, %Y" + }, + { + "fieldname": "Email", + "fieldtype": "ExternalLink" + } + ], + "formatname": "PERSON", + "outputlines": [ + "{*FirstName*} {*LastName*}", + "{*Street*}", + "{*City*}, {*State*} {*Zip*}", + "{*HomePhone*} (H)", + "{*WorkPhone*} (W)", + "{*MobilePhone*} (M)", + "DoB: {*Birthday*}", + "{*Email*}" + ], + "titleline": "{*FirstName*} {*LastName*}" + }, + { + "childtype": "TYPE", + "fields": [ + { + "fieldname": "NAME", + "fieldtype": "Text" + } + ], + "formatname": "ROOT", + "outputlines": [ + "{*NAME*}" + ], + "titleline": "{*NAME*}" + }, + { + "childtype": "PERSON", + "fields": [ + { + "fieldname": "Type", + "fieldtype": "Text" + } + ], + "formathtml": true, + "formatname": "TYPE", + "outputlines": [ + "
{*Type*}" + ], + "titleline": "{*Type*}" + } + ], + "nodes": [ + { + "children": [ + "fc702b0e95a311e79cb17054d2175f18", + "fc703cc095a311e79cb17054d2175f18", + "fc70413e95a311e79cb17054d2175f18" + ], + "data": { + "NAME": "Main" + }, + "format": "ROOT", + "uid": "fc7025d295a311e79cb17054d2175f18" + }, + { + "children": [ + "fc70345a95a311e79cb17054d2175f18", + "fc703af495a311e79cb17054d2175f18" + ], + "data": { + "Type": "Friends" + }, + "format": "TYPE", + "uid": "fc702b0e95a311e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Birthday": "2004-11-30", + "City": "Nina", + "Email": "bill@pinta.com", + "FirstName": "Bill", + "HomePhone": "(703) 555-5647", + "LastName": "Smith", + "State": "SC", + "Street": "1492 Columbus Drive", + "Zip": "35762" + }, + "format": "PERSON", + "uid": "fc70345a95a311e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Birthday": "1905-05-08", + "City": "Harbor City", + "Email": "jj@battleship.org", + "FirstName": "John", + "HomePhone": "(401) 555-8923", + "LastName": "Johnson", + "MobilePhone": "(703) 555-2873", + "State": "HI", + "Street": "1941 Pearl Street", + "WorkPhone": "(506) 555-7413", + "Zip": "86741" + }, + "format": "PERSON", + "uid": "fc703af495a311e79cb17054d2175f18" + }, + { + "children": [ + "fc703e0a95a311e79cb17054d2175f18", + "fc703fae95a311e79cb17054d2175f18" + ], + "data": { + "Type": "Family" + }, + "format": "TYPE", + "uid": "fc703cc095a311e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Birthday": "1901-07-30", + "City": "Dogbone", + "Email": "jdoe@spots.net", + "FirstName": "Jane", + "LastName": "Doe", + "MobilePhone": "(654) 555-8527", + "State": "SD", + "Street": "101 Dalmation Way", + "Zip": "52782" + }, + "format": "PERSON", + "uid": "fc703e0a95a311e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Birthday": "1999-08-14", + "City": "Undersea", + "Email": "johndoe@subs.com", + "FirstName": "John", + "HomePhone": "(805) 555-7296", + "LastName": "Doe", + "MobilePhone": "(503) 555-8234", + "State": "NY", + "Street": "20000 Leagues Street", + "WorkPhone": "(777) 555-9999", + "Zip": "12763" + }, + "format": "PERSON", + "uid": "fc703fae95a311e79cb17054d2175f18" + }, + { + "children": [ + "fc70427495a311e79cb17054d2175f18", + "fc7043b495a311e79cb17054d2175f18" + ], + "data": { + "Type": "Work" + }, + "format": "TYPE", + "uid": "fc70413e95a311e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "FirstName": "Dil", + "LastName": "Bert", + "WorkPhone": "(501) 555-5612" + }, + "format": "PERSON", + "uid": "fc70427495a311e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "FirstName": "Pointy-haired", + "LastName": "Boss", + "WorkPhone": "(666) 555-8945" + }, + "format": "PERSON", + "uid": "fc7043b495a311e79cb17054d2175f18" + } + ], + "properties": { + "tlversion": "2.9.0", + "topnodes": [ + "fc7025d295a311e79cb17054d2175f18" + ] + } +} \ No newline at end of file diff -Nru treeline-2.0.2/samples/130en_sample_basic_booklist.trl treeline-3.0.1/samples/130en_sample_basic_booklist.trl --- treeline-2.0.2/samples/130en_sample_basic_booklist.trl 2015-01-31 19:25:11.000000000 +0000 +++ treeline-3.0.1/samples/130en_sample_basic_booklist.trl 1970-01-01 00:00:00.000000000 +0000 @@ -1,54 +0,0 @@ - - -SF Books - -Greg -Bear -<a href="http://www.gregbear.com">www.gregbear.com</a> - -Darwin's Radio - -false -2000/10/01 -4 -Evolution caused by virus begining again - - -Blood Music -1985 -true -1998/07/01 -2 -Smart viruses take over - - - -Orson Scott -Card -<a href="http://www.hatrack.com">www.hatrack.com</a> - -Pastwatch, The Redemption of Christopher Columbus -1996 -Yes -1998/09/01 -4 -Time travel to change history; discovery of America - - -Enchantment -1999 -Yes -2000/08/01 -5 -Boy travels back to Russian fairy tale - - -Ender's Shadow -1999 -Yes -2001/05/01 -5 -Ender's Game from Bean's perspective - - - diff -Nru treeline-2.0.2/samples/130en_sample_basic_booklist.trln treeline-3.0.1/samples/130en_sample_basic_booklist.trln --- treeline-2.0.2/samples/130en_sample_basic_booklist.trln 1970-01-01 00:00:00.000000000 +0000 +++ treeline-3.0.1/samples/130en_sample_basic_booklist.trln 2018-04-08 19:19:50.000000000 +0000 @@ -0,0 +1,195 @@ +{ + "formats": [ + { + "childtype": "BOOK", + "fields": [ + { + "fieldname": "AuthorFirstName", + "fieldtype": "Text" + }, + { + "fieldname": "AuthorLastName", + "fieldtype": "Text" + }, + { + "fieldname": "WebSite", + "fieldtype": "ExternalLink", + "prefix": "<", + "suffix": ">" + } + ], + "formatname": "AUTHOR", + "icon": "book_1", + "outputlines": [ + "{*AuthorFirstName*} {*AuthorLastName*} {*WebSite*}" + ], + "titleline": "{*AuthorFirstName*} {*AuthorLastName*}" + }, + { + "fields": [ + { + "fieldname": "Title", + "fieldtype": "Text" + }, + { + "fieldname": "Copyright", + "fieldtype": "Number", + "format": "0000" + }, + { + "fieldname": "Own", + "fieldtype": "Boolean", + "format": "yes/no" + }, + { + "fieldname": "ReadDate", + "fieldtype": "Date", + "format": "%-%-M/%-d/%Y" + }, + { + "fieldname": "Rating", + "fieldtype": "Choice", + "format": "1/2/3/4/5" + }, + { + "fieldname": "Plot", + "fieldtype": "Text", + "lines": 7 + } + ], + "formatname": "BOOK", + "icon": "book_3", + "outputlines": [ + "\"{*Title*}\"", + "(c) {*Copyright*}, Own: {*Own*}", + "Last Read: {*ReadDate*}, Rating: {*Rating*}", + "{*Plot*}" + ], + "titleline": "\"{*Title*}\"" + }, + { + "childtype": "AUTHOR", + "fields": [ + { + "fieldname": "NAME", + "fieldtype": "Text" + } + ], + "formatname": "ROOT", + "outputlines": [ + "{*NAME*}" + ], + "titleline": "{*NAME*}" + } + ], + "nodes": [ + { + "children": [ + "0bf4f2ee95a411e79cb17054d2175f18", + "0bf4ffbe95a411e79cb17054d2175f18" + ], + "data": { + "NAME": "SF Books" + }, + "format": "ROOT", + "uid": "0bf4eeb695a411e79cb17054d2175f18" + }, + { + "children": [ + "0bf4f8ca95a411e79cb17054d2175f18", + "0bf4fe8895a411e79cb17054d2175f18" + ], + "data": { + "AuthorFirstName": "Greg", + "AuthorLastName": "Bear", + "WebSite": "www.gregbear.com" + }, + "format": "AUTHOR", + "uid": "0bf4f2ee95a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Own": "false", + "Plot": "Evolution caused by virus begining again", + "Rating": "4", + "ReadDate": "2000-10-01", + "Title": "Darwin's Radio" + }, + "format": "BOOK", + "uid": "0bf4f8ca95a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Copyright": "1985", + "Own": "true", + "Plot": "Smart viruses take over", + "Rating": "2", + "ReadDate": "1998-07-01", + "Title": "Blood Music" + }, + "format": "BOOK", + "uid": "0bf4fe8895a411e79cb17054d2175f18" + }, + { + "children": [ + "0bf500c295a411e79cb17054d2175f18", + "0bf501da95a411e79cb17054d2175f18", + "0bf502de95a411e79cb17054d2175f18" + ], + "data": { + "AuthorFirstName": "Orson Scott", + "AuthorLastName": "Card", + "WebSite": "www.hatrack.com" + }, + "format": "AUTHOR", + "uid": "0bf4ffbe95a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Copyright": "1996", + "Own": "Yes", + "Plot": "Time travel to change history; discovery of America", + "Rating": "4", + "ReadDate": "1998-09-01", + "Title": "Pastwatch, The Redemption of Christopher Columbus" + }, + "format": "BOOK", + "uid": "0bf500c295a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Copyright": "1999", + "Own": "Yes", + "Plot": "Boy travels back to Russian fairy tale", + "Rating": "5", + "ReadDate": "2000-08-01", + "Title": "Enchantment" + }, + "format": "BOOK", + "uid": "0bf501da95a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Copyright": "1999", + "Own": "Yes", + "Plot": "Ender's Game from Bean's perspective", + "Rating": "5", + "ReadDate": "2001-05-01", + "Title": "Ender's Shadow" + }, + "format": "BOOK", + "uid": "0bf502de95a411e79cb17054d2175f18" + } + ], + "properties": { + "tlversion": "2.9.0", + "topnodes": [ + "0bf4eeb695a411e79cb17054d2175f18" + ] + } +} \ No newline at end of file diff -Nru treeline-2.0.2/samples/140en_sample_genealogy.trln treeline-3.0.1/samples/140en_sample_genealogy.trln --- treeline-2.0.2/samples/140en_sample_genealogy.trln 1970-01-01 00:00:00.000000000 +0000 +++ treeline-3.0.1/samples/140en_sample_genealogy.trln 2018-04-08 19:19:50.000000000 +0000 @@ -0,0 +1,269 @@ +{ + "formats": [ + { + "fields": [ + { + "fieldname": "Name", + "fieldtype": "Text" + }, + { + "fieldname": "Birth", + "fieldtype": "Date", + "format": "%B %-d, %Y" + }, + { + "fieldname": "Death", + "fieldtype": "Date", + "format": "%B %-d, %Y" + }, + { + "fieldname": "Location", + "fieldtype": "Text" + } + ], + "formatname": "MEMBER", + "outputlines": [ + "{*Name*}", + "Born: {*Birth*}", + "Died: {*Death*}", + "Where: {*Location*}" + ], + "titleline": "{*Name*}" + } + ], + "nodes": [ + { + "children": [ + "5086f03aaf3a11e7a6243417ebd53aeb", + "959dd4e8af3a11e7a4263417ebd53aeb", + "a19ee912af3a11e79a1a3417ebd53aeb", + "a3ed3f0aaf3a11e799293417ebd53aeb" + ], + "data": { + "Birth": "1935-05-04", + "Location": "St. Louis, MO", + "Name": "Grandma Jones" + }, + "format": "MEMBER", + "uid": "0ec2773aaf3a11e79eea3417ebd53aeb" + }, + { + "children": [ + "4b6295b4af3a11e7beaa3417ebd53aeb", + "7c6a86deaf3a11e795c23417ebd53aeb", + "8630c54caf3a11e7b24c3417ebd53aeb" + ], + "data": { + "Birth": "1935-07-30", + "Location": "Las Vegas, NV", + "Name": "Grandpa Smith" + }, + "format": "MEMBER", + "uid": "1b0fdaefaf3911e785d43417ebd53aeb" + }, + { + "children": [], + "data": { + "Birth": "1992-07-15", + "Location": "Denver, CO", + "Name": "Linda" + }, + "format": "MEMBER", + "uid": "3096a564af3b11e7b9a63417ebd53aeb" + }, + { + "children": [], + "data": { + "Birth": "1995-02-14", + "Location": "Boston, MA", + "Name": "Billy" + }, + "format": "MEMBER", + "uid": "35eb10b6af3b11e7b0593417ebd53aeb" + }, + { + "children": [ + "f571c05eaf3a11e793b33417ebd53aeb", + "3096a564af3b11e7b9a63417ebd53aeb", + "35eb10b6af3b11e7b0593417ebd53aeb" + ], + "data": { + "Birth": "1960-08-16", + "Location": "Dallas, TX", + "Name": "Papa" + }, + "format": "MEMBER", + "uid": "4b6295b4af3a11e7beaa3417ebd53aeb" + }, + { + "children": [ + "f571c05eaf3a11e793b33417ebd53aeb", + "3096a564af3b11e7b9a63417ebd53aeb", + "35eb10b6af3b11e7b0593417ebd53aeb" + ], + "data": { + "Birth": "1962-02-10", + "Location": "Dallas, TX", + "Name": "Mama" + }, + "format": "MEMBER", + "uid": "5086f03aaf3a11e7a6243417ebd53aeb" + }, + { + "children": [], + "data": { + "Birth": "1985-12-25", + "Location": "Phoenix, AZ", + "Name": "Cousin Jimmy" + }, + "format": "MEMBER", + "uid": "7baaa7baaf3b11e7bf9a3417ebd53aeb" + }, + { + "children": [], + "data": { + "Birth": "2062-06-23", + "Location": "Pittsburgh, PA", + "Name": "Uncle Mike" + }, + "format": "MEMBER", + "uid": "7c6a86deaf3a11e795c23417ebd53aeb" + }, + { + "children": [], + "data": { + "Birth": "1987-08-31", + "Location": "Houston, TX", + "Name": "Cousin Dave" + }, + "format": "MEMBER", + "uid": "81d4264aaf3b11e7b17f3417ebd53aeb" + }, + { + "children": [ + "7baaa7baaf3b11e7bf9a3417ebd53aeb", + "81d4264aaf3b11e7b17f3417ebd53aeb" + ], + "data": { + "Birth": "2065-12-08", + "Location": "Cleveland, OH", + "Name": "Aunt Mary" + }, + "format": "MEMBER", + "uid": "8630c54caf3a11e7b24c3417ebd53aeb" + }, + { + "children": [], + "data": { + "Birth": "1984-10-05", + "Location": "New York, NY", + "Name": "Cousin Joe" + }, + "format": "MEMBER", + "uid": "916e641eaf3b11e78c293417ebd53aeb" + }, + { + "children": [], + "data": { + "Birth": "2064-06-05", + "Location": "Los Angelos, CA", + "Name": "Aunt Patty" + }, + "format": "MEMBER", + "uid": "959dd4e8af3a11e7a4263417ebd53aeb" + }, + { + "children": [], + "data": { + "Birth": "1990-07-20", + "Location": "Bar Harbor, ME", + "Name": "Cousin Susan" + }, + "format": "MEMBER", + "uid": "9bba64f8af3b11e78b123417ebd53aeb" + }, + { + "children": [ + "916e641eaf3b11e78c293417ebd53aeb", + "9bba64f8af3b11e78b123417ebd53aeb" + ], + "data": { + "Birth": "2067-11-10", + "Location": "Philadelphia, PA", + "Name": "Aunt Jenny" + }, + "format": "MEMBER", + "uid": "a19ee912af3a11e79a1a3417ebd53aeb" + }, + { + "children": [ + "a4371f0caf3b11e7ac8b3417ebd53aeb" + ], + "data": { + "Birth": "1969-05-31", + "Location": "Baltimore, MD", + "Name": "Uncle Bobby" + }, + "format": "MEMBER", + "uid": "a3ed3f0aaf3a11e799293417ebd53aeb" + }, + { + "children": [], + "data": { + "Birth": "1980-06-25", + "Location": "Wilmington, NC", + "Name": "Cousin Steve" + }, + "format": "MEMBER", + "uid": "a4371f0caf3b11e7ac8b3417ebd53aeb" + }, + { + "children": [ + "4b6295b4af3a11e7beaa3417ebd53aeb", + "7c6a86deaf3a11e795c23417ebd53aeb", + "8630c54caf3a11e7b24c3417ebd53aeb" + ], + "data": { + "Birth": "1937-04-12", + "Location": "Las Vegas, NV", + "Name": "Grandma Smith" + }, + "format": "MEMBER", + "uid": "d3b3f1c8af3911e79f6f3417ebd53aeb" + }, + { + "children": [ + "5086f03aaf3a11e7a6243417ebd53aeb", + "959dd4e8af3a11e7a4263417ebd53aeb", + "a19ee912af3a11e79a1a3417ebd53aeb", + "a3ed3f0aaf3a11e799293417ebd53aeb" + ], + "data": { + "Birth": "1932-09-17", + "Location": "St. Louis, MO", + "Name": "Grandpa Jones" + }, + "format": "MEMBER", + "uid": "f0f4f1f4af3911e790383417ebd53aeb" + }, + { + "children": [], + "data": { + "Birth": "1989-05-31", + "Location": "San Francisco, CA", + "Name": "Me" + }, + "format": "MEMBER", + "uid": "f571c05eaf3a11e793b33417ebd53aeb" + } + ], + "properties": { + "tlversion": "2.9.0", + "topnodes": [ + "1b0fdaefaf3911e785d43417ebd53aeb", + "d3b3f1c8af3911e79f6f3417ebd53aeb", + "f0f4f1f4af3911e790383417ebd53aeb", + "0ec2773aaf3a11e79eea3417ebd53aeb" + ] + } +} \ No newline at end of file diff -Nru treeline-2.0.2/samples/210en_sample_char_format.trl treeline-3.0.1/samples/210en_sample_char_format.trl --- treeline-2.0.2/samples/210en_sample_char_format.trl 2015-01-31 19:26:56.000000000 +0000 +++ treeline-3.0.1/samples/210en_sample_char_format.trl 1970-01-01 00:00:00.000000000 +0000 @@ -1,24 +0,0 @@ - - -Character Format Examples - -Bold Titles -All titles can be set to bold by adding &lt;b&gt;...&lt;/b&gt; tags in the Configure Data Types dialog's Output tab. The "Allow HTML rich text in format" option must also be checked in the Type Config tab. - - -Bold Text -Font formatting commands can be found in the Edit menu and in the <b>right-click context menu</b> in the "Data Editor" view. - - -Colored Text -Text can also be <span style="color:#aa0000">set</span> to various <span style="color:#00007f">colors</span>. - - -Font Size -<font size="+2">Large text</font> and <font size="-1">small text</font> - - -Italics and Underline -Text in <i>italics</i> and even text <u>underlined</u> - - diff -Nru treeline-2.0.2/samples/210en_sample_char_format.trln treeline-3.0.1/samples/210en_sample_char_format.trln --- treeline-2.0.2/samples/210en_sample_char_format.trln 1970-01-01 00:00:00.000000000 +0000 +++ treeline-3.0.1/samples/210en_sample_char_format.trln 2018-04-08 19:19:50.000000000 +0000 @@ -0,0 +1,105 @@ +{ + "formats": [ + { + "fields": [ + { + "fieldname": "Name", + "fieldtype": "Text" + } + ], + "formatname": "ROOT", + "outputlines": [ + "{*Name*}" + ], + "titleline": "{*Name*}" + }, + { + "fields": [ + { + "fieldname": "TOPIC", + "fieldtype": "Text" + }, + { + "fieldname": "TEXT", + "fieldtype": "Text", + "lines": 7 + } + ], + "formathtml": true, + "formatname": "TEXT_PARA", + "icon": "doc", + "outputlines": [ + "{*TOPIC*}", + "{*TEXT*}" + ], + "titleline": "{*TOPIC*}" + } + ], + "nodes": [ + { + "children": [ + "14e55e4895a411e79cb17054d2175f18", + "14e5604695a411e79cb17054d2175f18", + "14e5616895a411e79cb17054d2175f18", + "14e5626c95a411e79cb17054d2175f18", + "14e5635c95a411e79cb17054d2175f18" + ], + "data": { + "Name": "Character Format Examples" + }, + "format": "ROOT", + "uid": "14e559fc95a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "TEXT": "All titles can be set to bold by adding <b>...</b> tags in the Configure Data Types dialog's Output tab. The \"Allow HTML rich text in format\" option must also be checked in the Type Config tab.", + "TOPIC": "Bold Titles" + }, + "format": "TEXT_PARA", + "uid": "14e55e4895a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "TEXT": "Font formatting commands can be found in the Edit menu and in the right-click context menu in the \"Data Editor\" view.", + "TOPIC": "Bold Text" + }, + "format": "TEXT_PARA", + "uid": "14e5604695a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "TEXT": "Text can also be set to various colors.", + "TOPIC": "Colored Text" + }, + "format": "TEXT_PARA", + "uid": "14e5616895a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "TEXT": "Large text and small text", + "TOPIC": "Font Size" + }, + "format": "TEXT_PARA", + "uid": "14e5626c95a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "TEXT": "Text in italics and even text underlined", + "TOPIC": "Italics and Underline" + }, + "format": "TEXT_PARA", + "uid": "14e5635c95a411e79cb17054d2175f18" + } + ], + "properties": { + "tlversion": "2.9.0", + "topnodes": [ + "14e559fc95a411e79cb17054d2175f18" + ] + } +} \ No newline at end of file diff -Nru treeline-2.0.2/samples/220en_sample_bookmarks.trl treeline-3.0.1/samples/220en_sample_bookmarks.trl --- treeline-2.0.2/samples/220en_sample_bookmarks.trl 2015-01-31 19:27:31.000000000 +0000 +++ treeline-3.0.1/samples/220en_sample_bookmarks.trl 1970-01-01 00:00:00.000000000 +0000 @@ -1,139 +0,0 @@ - - -Bookmarks Menu - -Information - -News - -ABC News -<a href="http://www.abcnews.com/">http://www.abcnews.com/</a> - - -CNN.com -<a href="http://www.cnn.com/">http://www.cnn.com/</a> - - -USA Today -<a href="http://www.usatoday.com/">http://www.usatoday.com/</a> - - -WashingtonPost -<a href="http://washingtonpost.com/">http://washingtonpost.com/</a> - - - -Reference - -Dictionary.com -<a href="http://dictionary.com/">http://dictionary.com/</a> - - -DICT -<a href="http://www.dict.org/">http://www.dict.org/</a> - - -ibiblio -<a href="http://ibiblio.org/">http://ibiblio.org/</a> - - -Wikipedia -<a href="http://en.wikipedia.org/wiki/Main_Page">http://en.wikipedia.org/wiki/Main_Page</a> - - - - -Linux - -Debian - -debian.org -<a href="http://www.debian.org/">http://www.debian.org/</a> - - -Packages in "unstable" -<a href="http://packages.debian.org/unstable/">http://packages.debian.org/unstable/</a> - - -Packages in "experimental" -<a href="http://packages.debian.org/experimental/">http://packages.debian.org/experimental/</a> - - -Planet Debian -<a href="http://planet.debian.net/">http://planet.debian.net/</a> - - - -News - -Linux Today -<a href="http://linuxtoday.com/">http://linuxtoday.com/</a> - - -Linux Weekly News -<a href="http://www.lwn.net/">http://www.lwn.net/</a> - - -The H -<a href="http://www.h-online.com/">http://www.h-online.com/</a> - - - - -Programming - -PyQt - -PyQt Wiki -<a href="http://www.diotavelli.net/PyQtWiki">http://www.diotavelli.net/PyQtWiki</a> - - -Riverbank PyQt -<a href="http://www.riverbankcomputing.co.uk/">http://www.riverbankcomputing.co.uk/</a> - - -QT Trolltech -<a href="http://qt.nokia.com/">http://qt.nokia.com/</a> - - - -Python - -OReilly Python -<a href="http://www.oreillynet.com/python/">http://www.oreillynet.com/python/</a> - - -Python -<a href="http://www.python.org/">http://www.python.org/</a> - - - -Tools - -Mercurial -<a href="http://www.selenic.com/mercurial/wiki/">http://www.selenic.com/mercurial/wiki/</a> - - -Mercurial Book -<a href="http://hgbook.red-bean.com/hgbook.html">http://hgbook.red-bean.com/hgbook.html</a> - - - - -Weather - -ADDS - Aviation Digital Data Service -<a href="http://www.aviationweather.gov/adds">http://www.aviationweather.gov/adds</a> - - -NOAA -<a href="http://forecast.weather.gov/MapClick.php?lat=39.105&lon=-77.26">http://forecast.weather.gov/MapClick.php?lat=39.105&lon=-77.26</a> - - -Weather Underground -<a href="http://www.wunderground.com/cgi-bin/findweather/getForecast?query=gai">http://www.wunderground.com/cgi-bin/findweather/getForecast?query=gai</a> - - - - - diff -Nru treeline-2.0.2/samples/220en_sample_bookmarks.trln treeline-3.0.1/samples/220en_sample_bookmarks.trln --- treeline-2.0.2/samples/220en_sample_bookmarks.trln 1970-01-01 00:00:00.000000000 +0000 +++ treeline-3.0.1/samples/220en_sample_bookmarks.trln 2018-04-08 19:19:50.000000000 +0000 @@ -0,0 +1,428 @@ +{ + "formats": [ + { + "fields": [ + { + "fieldname": "Name", + "fieldtype": "Text" + }, + { + "fieldname": "Link", + "fieldtype": "ExternalLink" + } + ], + "formatname": "BOOKMARK", + "icon": "bookmark", + "outputlines": [ + "{*Name*}", + "{*Link*}" + ], + "titleline": "{*Name*}" + }, + { + "fields": [ + { + "fieldname": "Name", + "fieldtype": "Text" + } + ], + "formatname": "FOLDER", + "icon": "folder_3", + "outputlines": [ + "{*Name*}" + ], + "titleline": "{*Name*}" + }, + { + "fields": [ + { + "fieldname": "Name", + "fieldtype": "Text" + } + ], + "formathtml": true, + "formatname": "SEPARATOR", + "outputlines": [ + "
" + ], + "titleline": "------------------" + } + ], + "nodes": [ + { + "children": [ + "25608cd495a411e79cb17054d2175f18", + "25609bde95a411e79cb17054d2175f18", + "2560aca095a411e79cb17054d2175f18", + "2560b66495a411e79cb17054d2175f18" + ], + "data": { + "Name": "Bookmarks Menu" + }, + "format": "FOLDER", + "uid": "25608acc95a411e79cb17054d2175f18" + }, + { + "children": [ + "25608df695a411e79cb17054d2175f18", + "2560970695a411e79cb17054d2175f18" + ], + "data": { + "Name": "Information" + }, + "format": "FOLDER", + "uid": "25608cd495a411e79cb17054d2175f18" + }, + { + "children": [ + "256091c095a411e79cb17054d2175f18", + "256093e695a411e79cb17054d2175f18", + "256094f495a411e79cb17054d2175f18", + "2560960295a411e79cb17054d2175f18" + ], + "data": { + "Name": "News" + }, + "format": "FOLDER", + "uid": "25608df695a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Link": "http://www.abcnews.com/", + "Name": "ABC News" + }, + "format": "BOOKMARK", + "uid": "256091c095a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Link": "http://www.cnn.com/", + "Name": "CNN.com" + }, + "format": "BOOKMARK", + "uid": "256093e695a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Link": "http://www.usatoday.com/", + "Name": "USA Today" + }, + "format": "BOOKMARK", + "uid": "256094f495a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Link": "http://washingtonpost.com/", + "Name": "WashingtonPost" + }, + "format": "BOOKMARK", + "uid": "2560960295a411e79cb17054d2175f18" + }, + { + "children": [ + "256097f695a411e79cb17054d2175f18", + "256098f095a411e79cb17054d2175f18", + "256099e095a411e79cb17054d2175f18", + "25609ad095a411e79cb17054d2175f18" + ], + "data": { + "Name": "Reference" + }, + "format": "FOLDER", + "uid": "2560970695a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Link": "http://dictionary.com/", + "Name": "Dictionary.com" + }, + "format": "BOOKMARK", + "uid": "256097f695a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Link": "http://www.dict.org/", + "Name": "DICT" + }, + "format": "BOOKMARK", + "uid": "256098f095a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Link": "http://ibiblio.org/", + "Name": "ibiblio" + }, + "format": "BOOKMARK", + "uid": "256099e095a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Link": "http://en.wikipedia.org/wiki/Main_Page", + "Name": "Wikipedia" + }, + "format": "BOOKMARK", + "uid": "25609ad095a411e79cb17054d2175f18" + }, + { + "children": [ + "25609cba95a411e79cb17054d2175f18", + "2560a8cc95a411e79cb17054d2175f18" + ], + "data": { + "Name": "Linux" + }, + "format": "FOLDER", + "uid": "25609bde95a411e79cb17054d2175f18" + }, + { + "children": [ + "2560a4c695a411e79cb17054d2175f18", + "2560a5de95a411e79cb17054d2175f18", + "2560a6ce95a411e79cb17054d2175f18", + "2560a7dc95a411e79cb17054d2175f18" + ], + "data": { + "Name": "Debian" + }, + "format": "FOLDER", + "uid": "25609cba95a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Link": "http://www.debian.org/", + "Name": "debian.org" + }, + "format": "BOOKMARK", + "uid": "2560a4c695a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Link": "http://packages.debian.org/unstable/", + "Name": "Packages in \"unstable\"" + }, + "format": "BOOKMARK", + "uid": "2560a5de95a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Link": "http://packages.debian.org/experimental/", + "Name": "Packages in \"experimental\"" + }, + "format": "BOOKMARK", + "uid": "2560a6ce95a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Link": "http://planet.debian.net/", + "Name": "Planet Debian" + }, + "format": "BOOKMARK", + "uid": "2560a7dc95a411e79cb17054d2175f18" + }, + { + "children": [ + "2560a9bc95a411e79cb17054d2175f18", + "2560aaac95a411e79cb17054d2175f18", + "2560ab9295a411e79cb17054d2175f18" + ], + "data": { + "Name": "News" + }, + "format": "FOLDER", + "uid": "2560a8cc95a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Link": "http://linuxtoday.com/", + "Name": "Linux Today" + }, + "format": "BOOKMARK", + "uid": "2560a9bc95a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Link": "http://www.lwn.net/", + "Name": "Linux Weekly News" + }, + "format": "BOOKMARK", + "uid": "2560aaac95a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Link": "http://www.h-online.com/", + "Name": "The H" + }, + "format": "BOOKMARK", + "uid": "2560ab9295a411e79cb17054d2175f18" + }, + { + "children": [ + "2560ad7c95a411e79cb17054d2175f18", + "2560b11495a411e79cb17054d2175f18", + "2560b3bc95a411e79cb17054d2175f18" + ], + "data": { + "Name": "Programming" + }, + "format": "FOLDER", + "uid": "2560aca095a411e79cb17054d2175f18" + }, + { + "children": [ + "2560ae5895a411e79cb17054d2175f18", + "2560af3e95a411e79cb17054d2175f18", + "2560b02495a411e79cb17054d2175f18" + ], + "data": { + "Name": "PyQt" + }, + "format": "FOLDER", + "uid": "2560ad7c95a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Link": "http://www.diotavelli.net/PyQtWiki", + "Name": "PyQt Wiki" + }, + "format": "BOOKMARK", + "uid": "2560ae5895a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Link": "http://www.riverbankcomputing.co.uk/", + "Name": "Riverbank PyQt" + }, + "format": "BOOKMARK", + "uid": "2560af3e95a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Link": "http://qt.nokia.com/", + "Name": "QT Trolltech" + }, + "format": "BOOKMARK", + "uid": "2560b02495a411e79cb17054d2175f18" + }, + { + "children": [ + "2560b1e695a411e79cb17054d2175f18", + "2560b2cc95a411e79cb17054d2175f18" + ], + "data": { + "Name": "Python" + }, + "format": "FOLDER", + "uid": "2560b11495a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Link": "http://www.oreillynet.com/python/", + "Name": "OReilly Python" + }, + "format": "BOOKMARK", + "uid": "2560b1e695a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Link": "http://www.python.org/", + "Name": "Python" + }, + "format": "BOOKMARK", + "uid": "2560b2cc95a411e79cb17054d2175f18" + }, + { + "children": [ + "2560b48e95a411e79cb17054d2175f18", + "2560b57495a411e79cb17054d2175f18" + ], + "data": { + "Name": "Tools" + }, + "format": "FOLDER", + "uid": "2560b3bc95a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Link": "http://www.selenic.com/mercurial/wiki/", + "Name": "Mercurial" + }, + "format": "BOOKMARK", + "uid": "2560b48e95a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Link": "http://hgbook.red-bean.com/hgbook.html", + "Name": "Mercurial Book" + }, + "format": "BOOKMARK", + "uid": "2560b57495a411e79cb17054d2175f18" + }, + { + "children": [ + "2560b73695a411e79cb17054d2175f18", + "2560b82695a411e79cb17054d2175f18", + "2560b92a95a411e79cb17054d2175f18" + ], + "data": { + "Name": "Weather" + }, + "format": "FOLDER", + "uid": "2560b66495a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Link": "http://www.aviationweather.gov/adds", + "Name": "ADDS - Aviation Digital Data Service" + }, + "format": "BOOKMARK", + "uid": "2560b73695a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Link": "http://forecast.weather.gov/MapClick.php?lat=39.105&lon=-77.26", + "Name": "NOAA" + }, + "format": "BOOKMARK", + "uid": "2560b82695a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Link": "http://www.wunderground.com/cgi-bin/findweather/getForecast?query=gai", + "Name": "Weather Underground" + }, + "format": "BOOKMARK", + "uid": "2560b92a95a411e79cb17054d2175f18" + } + ], + "properties": { + "tlversion": "2.9.0", + "topnodes": [ + "25608acc95a411e79cb17054d2175f18" + ] + } +} \ No newline at end of file diff -Nru treeline-2.0.2/samples/230en_sample_intern_links.trl treeline-3.0.1/samples/230en_sample_intern_links.trl --- treeline-2.0.2/samples/230en_sample_intern_links.trl 2015-01-31 19:28:26.000000000 +0000 +++ treeline-3.0.1/samples/230en_sample_intern_links.trl 1970-01-01 00:00:00.000000000 +0000 @@ -1,17 +0,0 @@ - - -Main - - -Using the external link field -<a href="#Target_Node">Target Node</a> - - -Embedded links -Links can be <a href="#Target_Node">embedded</a> in any text field text by using the Internal Link item from the Edit Menu. The "Enable click on target" button lets you simply click on the target node. - - -Target Node -By default, the target node gets its unique ID from the content of its first field. This can be changed in the Type Config tab of the Configure Types dialog. Use the Show Advanced button to show the control for the Unique ID Reference. - - diff -Nru treeline-2.0.2/samples/230en_sample_intern_links.trln treeline-3.0.1/samples/230en_sample_intern_links.trln --- treeline-2.0.2/samples/230en_sample_intern_links.trln 1970-01-01 00:00:00.000000000 +0000 +++ treeline-3.0.1/samples/230en_sample_intern_links.trln 2018-04-08 19:19:50.000000000 +0000 @@ -0,0 +1,87 @@ +{ + "formats": [ + { + "fields": [ + { + "fieldname": "Name", + "fieldtype": "Text" + }, + { + "fieldname": "Text", + "fieldtype": "Text" + } + ], + "formatname": "DEFAULT", + "outputlines": [ + "{*Name*}", + "{*Text*}" + ], + "titleline": "{*Name*}" + }, + { + "fields": [ + { + "fieldname": "Name", + "fieldtype": "Text" + }, + { + "fieldname": "Link", + "fieldtype": "InternalLink" + } + ], + "formatname": "LINK_TYPE", + "outputlines": [ + "{*Name*}", + "{*Link*}" + ], + "titleline": "{*Name*}" + } + ], + "nodes": [ + { + "children": [ + "2d70acb095a411e79cb17054d2175f18", + "2d70aec295a411e79cb17054d2175f18", + "2d70afd095a411e79cb17054d2175f18" + ], + "data": { + "Name": "Main" + }, + "format": "DEFAULT", + "uid": "2d70a88c95a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Link": "Target Node", + "Name": "Using the external link field" + }, + "format": "LINK_TYPE", + "uid": "2d70acb095a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Name": "Embedded links", + "Text": "Links can be embedded in any text field text by using the Internal Link item from the Edit Menu. The \"Enable click on target\" button lets you simply click on the target node." + }, + "format": "DEFAULT", + "uid": "2d70aec295a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Name": "Target Node", + "Text": "By default, the target node gets its unique ID from the content of its first field. This can be changed in the Type Config tab of the Configure Types dialog. Use the Show Advanced button to show the control for the Unique ID Reference." + }, + "format": "DEFAULT", + "uid": "2d70afd095a411e79cb17054d2175f18" + } + ], + "properties": { + "tlversion": "2.9.0", + "topnodes": [ + "2d70a88c95a411e79cb17054d2175f18" + ] + } +} \ No newline at end of file diff -Nru treeline-2.0.2/samples/240en_sample_table_booklist.trl treeline-3.0.1/samples/240en_sample_table_booklist.trl --- treeline-2.0.2/samples/240en_sample_table_booklist.trl 2015-01-31 19:29:08.000000000 +0000 +++ treeline-3.0.1/samples/240en_sample_table_booklist.trl 1970-01-01 00:00:00.000000000 +0000 @@ -1,54 +0,0 @@ - - -SF Books - -Greg -Bear -<a href="http://www.gregbear.com">www.gregbear.com</a> - -Darwin's Radio -1999 -false -2000/10/01 -4 -Evolution caused by virus begining again - - -Blood Music -1985 -true -1998/07/01 -2 -Smart viruses take over - - - -Orson Scott -Card -<a href="http://www.hatrack.com">www.hatrack.com</a> - -Pastwatch, The Redemption of Christopher Columbus -1996 -Yes -1998/09/01 -4 -Time travel to change history; discovery of America - - -Enchantment -1999 -Yes -2000/08/01 -5 -Boy travels back to Russian fairy tale - - -Ender's Shadow -1999 -Yes -2001/05/01 -5 -Ender's Game from Bean's perspective - - - diff -Nru treeline-2.0.2/samples/240en_sample_table_booklist.trln treeline-3.0.1/samples/240en_sample_table_booklist.trln --- treeline-2.0.2/samples/240en_sample_table_booklist.trln 1970-01-01 00:00:00.000000000 +0000 +++ treeline-3.0.1/samples/240en_sample_table_booklist.trln 2018-04-08 19:19:50.000000000 +0000 @@ -0,0 +1,201 @@ +{ +"formats": [ +{ +"childtype": "BOOK", +"fields": [ +{ +"fieldname": "AuthorFirstName", +"fieldtype": "Text" +}, +{ +"fieldname": "AuthorLastName", +"fieldtype": "Text" +}, +{ +"fieldname": "WebSite", +"fieldtype": "ExternalLink", +"prefix": "<", +"suffix": ">" +} +], +"formatname": "AUTHOR", +"icon": "book_1", +"outputlines": [ +"{*AuthorFirstName*} {*AuthorLastName*} {*WebSite*}" +], +"titleline": "{*AuthorFirstName*} {*AuthorLastName*}" +}, +{ +"fields": [ +{ +"fieldname": "Title", +"fieldtype": "Text" +}, +{ +"fieldname": "Copyright", +"fieldtype": "Number", +"format": "0000" +}, +{ +"fieldname": "Own", +"fieldtype": "Boolean", +"format": "yes/no" +}, +{ +"fieldname": "ReadDate", +"fieldtype": "Date", +"format": "%-m/%-d/%Y" +}, +{ +"fieldname": "Rating", +"fieldtype": "Choice", +"format": "1/2/3/4/5" +}, +{ +"fieldname": "Plot", +"fieldtype": "Text", +"lines": 7 +} +], +"formathtml": true, +"formatname": "BOOK", +"icon": "book_3", +"outputlines": [ +"Title: \"{*Title*}\"", +"Status: (c) {*Copyright*}", +"Own: {*Own*}", +"Last Read: {*ReadDate*}", +"Rating: {*Rating*}", +"Plot: {*Plot*}" +], +"spacebetween": false, +"tables": true, +"titleline": "\"{*Title*}\"" +}, +{ +"childtype": "AUTHOR", +"fields": [ +{ +"fieldname": "NAME", +"fieldtype": "Text" +} +], +"formatname": "ROOT", +"outputlines": [ +"{*NAME*}" +], +"titleline": "{*NAME*}" +} +], +"nodes": [ +{ +"children": [ +"5cd2946cfe1011e7b8dc7054d2175f18", +"5cd2bffafe1011e7b8dc7054d2175f18" +], +"data": { +"NAME": "SF Books" +}, +"format": "ROOT", +"uid": "5cd28d1efe1011e7b8dc7054d2175f18" +}, +{ +"children": [ +"5cd2b3d4fe1011e7b8dc7054d2175f18", +"5cd2bdc0fe1011e7b8dc7054d2175f18" +], +"data": { +"AuthorFirstName": "Greg", +"AuthorLastName": "Bear", +"WebSite": "www.gregbear.com" +}, +"format": "AUTHOR", +"uid": "5cd2946cfe1011e7b8dc7054d2175f18" +}, +{ +"children": [], +"data": { +"Copyright": "1999", +"Own": "false", +"Plot": "Evolution caused by virus begining again", +"Rating": "4", +"ReadDate": "2000-10-01", +"Title": "Darwin's Radio" +}, +"format": "BOOK", +"uid": "5cd2b3d4fe1011e7b8dc7054d2175f18" +}, +{ +"children": [], +"data": { +"Copyright": "1985", +"Own": "true", +"Plot": "Smart viruses take over", +"Rating": "2", +"ReadDate": "1998-07-01", +"Title": "Blood Music" +}, +"format": "BOOK", +"uid": "5cd2bdc0fe1011e7b8dc7054d2175f18" +}, +{ +"children": [ +"5cd2c1bcfe1011e7b8dc7054d2175f18", +"5cd2c392fe1011e7b8dc7054d2175f18", +"5cd2c54afe1011e7b8dc7054d2175f18" +], +"data": { +"AuthorFirstName": "Orson Scott", +"AuthorLastName": "Card", +"WebSite": "www.hatrack.com" +}, +"format": "AUTHOR", +"uid": "5cd2bffafe1011e7b8dc7054d2175f18" +}, +{ +"children": [], +"data": { +"Copyright": "1996", +"Own": "Yes", +"Plot": "Time travel to change history; discovery of America", +"Rating": "4", +"ReadDate": "1998-09-01", +"Title": "Pastwatch, The Redemption of Christopher Columbus" +}, +"format": "BOOK", +"uid": "5cd2c1bcfe1011e7b8dc7054d2175f18" +}, +{ +"children": [], +"data": { +"Copyright": "1999", +"Own": "Yes", +"Plot": "Boy travels back to Russian fairy tale", +"Rating": "5", +"ReadDate": "2000-08-01", +"Title": "Enchantment" +}, +"format": "BOOK", +"uid": "5cd2c392fe1011e7b8dc7054d2175f18" +}, +{ +"children": [], +"data": { +"Copyright": "1999", +"Own": "Yes", +"Plot": "Ender's Game from Bean's perspective", +"Rating": "5", +"ReadDate": "2001-05-01", +"Title": "Ender's Shadow" +}, +"format": "BOOK", +"uid": "5cd2c54afe1011e7b8dc7054d2175f18" +} +], +"properties": { +"tlversion": "2.9.0", +"topnodes": [ +"5cd28d1efe1011e7b8dc7054d2175f18" +] +} +} \ No newline at end of file diff -Nru treeline-2.0.2/samples/310en_sample_conditional_todo.trl treeline-3.0.1/samples/310en_sample_conditional_todo.trl --- treeline-2.0.2/samples/310en_sample_conditional_todo.trl 2015-01-31 19:29:45.000000000 +0000 +++ treeline-3.0.1/samples/310en_sample_conditional_todo.trl 1970-01-01 00:00:00.000000000 +0000 @@ -1,55 +0,0 @@ - - -Conditional Task List - -Home Tasks - -Mow lawn -false -false - - -Patch wall -false -false - - -Vacuum -false -false - - -Walk dog -false -true - - -Watch TV -true -false - - - -Work Tasks - -Play solitaire -true -false - - -Write documents -false -false - - -Eat lunch -true -false - - -Compliment boss -false -true - - - diff -Nru treeline-2.0.2/samples/310en_sample_conditional_todo.trln treeline-3.0.1/samples/310en_sample_conditional_todo.trln --- treeline-2.0.2/samples/310en_sample_conditional_todo.trln 1970-01-01 00:00:00.000000000 +0000 +++ treeline-3.0.1/samples/310en_sample_conditional_todo.trln 2018-04-08 19:19:50.000000000 +0000 @@ -0,0 +1,240 @@ +{ + "formats": [ + { + "childtype": "TASK_UNDONE", + "fields": [ + { + "fieldname": "Name", + "fieldtype": "Text" + } + ], + "formathtml": true, + "formatname": "ROOT", + "outputlines": [ + "{*Name*}" + ], + "titleline": "{*Name*}" + }, + { + "condition": "Done == \"true\"", + "fields": [ + { + "fieldname": "Name", + "fieldtype": "Text" + }, + { + "fieldname": "Done", + "fieldtype": "Boolean", + "format": "yes/no", + "init": "false" + }, + { + "fieldname": "Urgent", + "fieldtype": "Boolean", + "format": "yes/no", + "init": "false" + } + ], + "formathtml": true, + "formatname": "TASK_DONE", + "icon": "smiley_4", + "outputlines": [ + "{*Name*}" + ], + "titleline": "{*Name*}" + }, + { + "fields": [ + { + "fieldname": "Name", + "fieldtype": "Text" + }, + { + "fieldname": "Done", + "fieldtype": "Boolean", + "format": "yes/no", + "init": "false" + }, + { + "fieldname": "Urgent", + "fieldtype": "Boolean", + "format": "yes/no", + "init": "false" + } + ], + "formathtml": true, + "formatname": "TASK_UNDONE", + "generic": "TASK_DONE", + "icon": "smiley_2", + "outputlines": [ + "{*Name*}" + ], + "titleline": "{*Name*}" + }, + { + "condition": "Done == \"false\" and Urgent == \"true\"", + "fields": [ + { + "fieldname": "Name", + "fieldtype": "Text" + }, + { + "fieldname": "Done", + "fieldtype": "Boolean", + "format": "yes/no", + "init": "false" + }, + { + "fieldname": "Urgent", + "fieldtype": "Boolean", + "format": "yes/no", + "init": "true" + } + ], + "formathtml": true, + "formatname": "TASK_UNDONE_URGENT", + "generic": "TASK_DONE", + "icon": "smiley_5", + "outputlines": [ + "{*Name*}" + ], + "titleline": "{*Name*}!!!" + } + ], + "nodes": [ + { + "children": [ + "4295fc4e95a411e79cb17054d2175f18", + "42960f7c95a411e79cb17054d2175f18" + ], + "data": { + "Name": "Conditional Task List" + }, + "format": "ROOT", + "uid": "4295faaa95a411e79cb17054d2175f18" + }, + { + "children": [ + "4295ff6e95a411e79cb17054d2175f18", + "429602ac95a411e79cb17054d2175f18", + "429603c495a411e79cb17054d2175f18", + "429607b695a411e79cb17054d2175f18", + "42960cca95a411e79cb17054d2175f18" + ], + "data": { + "Name": "Home Tasks" + }, + "format": "ROOT", + "uid": "4295fc4e95a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Done": "false", + "Name": "Mow lawn", + "Urgent": "false" + }, + "format": "TASK_UNDONE", + "uid": "4295ff6e95a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Done": "false", + "Name": "Patch wall", + "Urgent": "false" + }, + "format": "TASK_UNDONE", + "uid": "429602ac95a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Done": "false", + "Name": "Vacuum", + "Urgent": "false" + }, + "format": "TASK_UNDONE", + "uid": "429603c495a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Done": "false", + "Name": "Walk dog", + "Urgent": "true" + }, + "format": "TASK_UNDONE_URGENT", + "uid": "429607b695a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Done": "true", + "Name": "Watch TV", + "Urgent": "false" + }, + "format": "TASK_DONE", + "uid": "42960cca95a411e79cb17054d2175f18" + }, + { + "children": [ + "4296107695a411e79cb17054d2175f18", + "429611b695a411e79cb17054d2175f18", + "429612a695a411e79cb17054d2175f18", + "4296139695a411e79cb17054d2175f18" + ], + "data": { + "Name": "Work Tasks" + }, + "format": "ROOT", + "uid": "42960f7c95a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Done": "true", + "Name": "Play solitaire", + "Urgent": "false" + }, + "format": "TASK_DONE", + "uid": "4296107695a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Done": "false", + "Name": "Write documents", + "Urgent": "false" + }, + "format": "TASK_UNDONE", + "uid": "429611b695a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Done": "true", + "Name": "Eat lunch", + "Urgent": "false" + }, + "format": "TASK_DONE", + "uid": "429612a695a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Done": "false", + "Name": "Compliment boss", + "Urgent": "true" + }, + "format": "TASK_UNDONE_URGENT", + "uid": "4296139695a411e79cb17054d2175f18" + } + ], + "properties": { + "tlversion": "2.9.0", + "topnodes": [ + "4295faaa95a411e79cb17054d2175f18" + ] + } +} \ No newline at end of file diff -Nru treeline-2.0.2/samples/320en_sample_other_fields.trl treeline-3.0.1/samples/320en_sample_other_fields.trl --- treeline-2.0.2/samples/320en_sample_other_fields.trl 2015-01-31 19:31:07.000000000 +0000 +++ treeline-3.0.1/samples/320en_sample_other_fields.trl 1970-01-01 00:00:00.000000000 +0000 @@ -1,31 +0,0 @@ - - -Other Field References - -File Information - - -Parent Info - - -Child Info - -First child - - -Next child - - -Third child - - - - - - - - - - - - diff -Nru treeline-2.0.2/samples/320en_sample_other_fields.trln treeline-3.0.1/samples/320en_sample_other_fields.trln --- treeline-2.0.2/samples/320en_sample_other_fields.trln 1970-01-01 00:00:00.000000000 +0000 +++ treeline-3.0.1/samples/320en_sample_other_fields.trln 2018-04-08 19:19:50.000000000 +0000 @@ -0,0 +1,184 @@ +{ + "formats": [ + { + "fields": [ + { + "fieldname": "Name", + "fieldtype": "Text" + } + ], + "formathtml": true, + "formatname": "DEFAULT", + "outputlines": [ + "{*Name*}" + ], + "titleline": "{*Name*}" + }, + { + "childtype": "DEFAULT", + "fields": [ + { + "fieldname": "Name", + "fieldtype": "Text" + } + ], + "formathtml": true, + "formatname": "FILE_INFO", + "outputlines": [ + "{*Name*}", + "File: {*!File_Path*}/{*!File_Name*}", + "File Size: {*!File_Size*} bytes", + "File Modified: {*!File_Mod_Date*} {*!File_Mod_Time*}" + ], + "titleline": "{*Name*}" + }, + { + "fields": [ + { + "fieldname": "File_Name", + "fieldtype": "Text" + }, + { + "fieldname": "File_Path", + "fieldtype": "Text" + }, + { + "fieldname": "File_Size", + "fieldtype": "Number", + "format": "#" + }, + { + "fieldname": "File_Mod_Date", + "fieldtype": "Date", + "format": "%B %-d, %Y" + }, + { + "fieldname": "File_Mod_Time", + "fieldtype": "Time", + "format": "%-I:%M:%S %p" + }, + { + "fieldname": "File_Owner", + "fieldtype": "Text" + }, + { + "fieldname": "Page_Number", + "fieldtype": "Text" + }, + { + "fieldname": "Number_of_Pages", + "fieldtype": "Text" + } + ], + "formatname": "INT_TL_FILE_DATA_FORM", + "outputlines": [ + "" + ], + "titleline": "" + }, + { + "childtype": "DEFAULT", + "fields": [ + { + "fieldname": "Name", + "fieldtype": "Text" + } + ], + "formathtml": true, + "formatname": "PARENT_CHILD", + "outputlines": [ + "{*Name*}", + "Parent's Name: {**Name*}", + "Children's Names: {*&Name*}" + ], + "titleline": "{*Name*}" + }, + { + "childtype": "DEFAULT", + "fields": [ + { + "fieldname": "Name", + "fieldtype": "Text" + } + ], + "formathtml": true, + "formatname": "ROOT", + "outputlines": [ + "{*Name*}" + ], + "titleline": "{*Name*}" + } + ], + "nodes": [ + { + "children": [ + "50cdc8dc95a411e79cb17054d2175f18", + "50cdce0495a411e79cb17054d2175f18", + "50cdcf7695a411e79cb17054d2175f18" + ], + "data": { + "Name": "Other Field References" + }, + "format": "ROOT", + "uid": "50cdc36e95a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Name": "File Information" + }, + "format": "FILE_INFO", + "uid": "50cdc8dc95a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Name": "Parent Info" + }, + "format": "PARENT_CHILD", + "uid": "50cdce0495a411e79cb17054d2175f18" + }, + { + "children": [ + "50cdd27895a411e79cb17054d2175f18", + "50cdd3e095a411e79cb17054d2175f18", + "50cdd4e495a411e79cb17054d2175f18" + ], + "data": { + "Name": "Child Info" + }, + "format": "PARENT_CHILD", + "uid": "50cdcf7695a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Name": "First child" + }, + "format": "DEFAULT", + "uid": "50cdd27895a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Name": "Next child" + }, + "format": "DEFAULT", + "uid": "50cdd3e095a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Name": "Third child" + }, + "format": "DEFAULT", + "uid": "50cdd4e495a411e79cb17054d2175f18" + } + ], + "properties": { + "tlversion": "2.9.0", + "topnodes": [ + "50cdc36e95a411e79cb17054d2175f18" + ] + } +} \ No newline at end of file diff -Nru treeline-2.0.2/samples/330en_sample_math_fields.trl treeline-3.0.1/samples/330en_sample_math_fields.trl --- treeline-2.0.2/samples/330en_sample_math_fields.trl 2015-05-10 18:32:44.000000000 +0000 +++ treeline-3.0.1/samples/330en_sample_math_fields.trl 1970-01-01 00:00:00.000000000 +0000 @@ -1,58 +0,0 @@ - - -123456 -Widget Top Assembly -1 -3.5 -23.89 - -456789 -Lever Assembly -2 -1.75 -10.040000000000001 -23.89 -42.02595228128925 - -987654 -Lever -3 -5.4 -22.60359983256593 -High - - -998877 -Lever Bolt -3 -2.89 -12.097111762243616 -Low - - - -112233 -Bracket Assembly -2 -2.25 -10.35 -23.89 -43.32356634575136 - -445566 -Bracket -3 -6.2 -25.952281289242364 -High - - -665544 -Bracket Pin -3 -1.9 -7.95311845960653 -Low - - - diff -Nru treeline-2.0.2/samples/330en_sample_math_fields.trln treeline-3.0.1/samples/330en_sample_math_fields.trln --- treeline-2.0.2/samples/330en_sample_math_fields.trln 1970-01-01 00:00:00.000000000 +0000 +++ treeline-3.0.1/samples/330en_sample_math_fields.trln 2018-04-08 19:19:50.000000000 +0000 @@ -0,0 +1,255 @@ +{ + "formats": [ + { + "childtype": "PART", + "fields": [ + { + "fieldname": "PartNumber", + "fieldtype": "Text" + }, + { + "fieldname": "Name", + "fieldtype": "Text" + }, + { + "eqn": "{**Level*} + 1", + "fieldname": "Level", + "fieldtype": "Math", + "format": "#" + }, + { + "fieldname": "LaborCost", + "fieldtype": "Number", + "format": "0.00", + "prefix": "$" + }, + { + "eqn": "sum({*&Cost*}) + {*LaborCost*}", + "fieldname": "Cost", + "fieldtype": "Math", + "format": "0.00", + "prefix": "$" + }, + { + "eqn": "{**TotalCost*}", + "fieldname": "TotalCost", + "fieldtype": "Math", + "format": "0.00", + "prefix": "$" + }, + { + "eqn": "{*Cost*} / {*TotalCost*} * 100", + "fieldname": "PercentCost", + "fieldtype": "Math", + "format": "0", + "suffix": "%" + } + ], + "formatname": "ASSEMBLY", + "outputlines": [ + "Part {*PartNumber*}", + "{*Name*}", + "Assembly Level: {*Level*}", + "Cost: {*Cost*}", + "Percent Cost: {*PercentCost*}" + ], + "titleline": "{*PartNumber*} {*Name*}" + }, + { + "fields": [ + { + "fieldname": "PartNumber", + "fieldtype": "Text" + }, + { + "fieldname": "Name", + "fieldtype": "Text" + }, + { + "eqn": "{**Level*} + 1", + "fieldname": "Level", + "fieldtype": "Math", + "format": "#" + }, + { + "fieldname": "Cost", + "fieldtype": "Number", + "format": "0.00", + "prefix": "$" + }, + { + "eqn": "{*Cost*} / {**TotalCost*} * 100", + "fieldname": "PercentCost", + "fieldtype": "Math", + "format": "0", + "suffix": "%" + }, + { + "eqn": "('Low') if ({*PercentCost*} < 20) else ('High')", + "fieldname": "CostLevel", + "fieldtype": "Math", + "format": "#.##", + "resulttype": "text" + } + ], + "formatname": "PART", + "outputlines": [ + "Part {*PartNumber*}", + "{*Name*}", + "Assembly Level: {*Level*}", + "Cost: {*Cost*}", + "Percent Cost: {*PercentCost*}", + "Cost Level: {*CostLevel*}" + ], + "titleline": "{*PartNumber*} {*Name*}" + }, + { + "childtype": "ASSEMBLY", + "fields": [ + { + "fieldname": "PartNumber", + "fieldtype": "Text" + }, + { + "fieldname": "Name", + "fieldtype": "Text" + }, + { + "fieldname": "Level", + "fieldtype": "Number", + "format": "#" + }, + { + "fieldname": "LaborCost", + "fieldtype": "Number", + "format": "0.00", + "prefix": "$" + }, + { + "eqn": "sum({*&Cost*}) + {*LaborCost*}", + "fieldname": "TotalCost", + "fieldtype": "Math", + "format": "0.00", + "prefix": "$" + } + ], + "formatname": "TOP_ASSEMBLY", + "outputlines": [ + "Part {*PartNumber*}", + "{*Name*}", + "Assembly Level: {*Level*}", + "Cost: {*TotalCost*}" + ], + "titleline": "{*PartNumber*} {*Name*}" + } + ], + "nodes": [ + { + "children": [ + "5f8ff84a95a411e79cb17054d2175f18", + "5f901e5695a411e79cb17054d2175f18" + ], + "data": { + "LaborCost": "3.5", + "Level": "1", + "Name": "Widget Top Assembly", + "PartNumber": "123456", + "TotalCost": "23.89" + }, + "format": "TOP_ASSEMBLY", + "uid": "5f8fdd3895a411e79cb17054d2175f18" + }, + { + "children": [ + "5f900eb695a411e79cb17054d2175f18", + "5f901d0c95a411e79cb17054d2175f18" + ], + "data": { + "Cost": "10.040000000000001", + "LaborCost": "1.75", + "Level": "2", + "Name": "Lever Assembly", + "PartNumber": "456789", + "PercentCost": "42.02595228128925", + "TotalCost": "23.89" + }, + "format": "ASSEMBLY", + "uid": "5f8ff84a95a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Cost": "5.4", + "CostLevel": "High", + "Level": "3", + "Name": "Lever", + "PartNumber": "987654", + "PercentCost": "22.60359983256593" + }, + "format": "PART", + "uid": "5f900eb695a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Cost": "2.89", + "CostLevel": "Low", + "Level": "3", + "Name": "Lever Bolt", + "PartNumber": "998877", + "PercentCost": "12.097111762243616" + }, + "format": "PART", + "uid": "5f901d0c95a411e79cb17054d2175f18" + }, + { + "children": [ + "5f901f6e95a411e79cb17054d2175f18", + "5f90208695a411e79cb17054d2175f18" + ], + "data": { + "Cost": "10.35", + "LaborCost": "2.25", + "Level": "2", + "Name": "Bracket Assembly", + "PartNumber": "112233", + "PercentCost": "43.32356634575136", + "TotalCost": "23.89" + }, + "format": "ASSEMBLY", + "uid": "5f901e5695a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Cost": "6.2", + "CostLevel": "High", + "Level": "3", + "Name": "Bracket", + "PartNumber": "445566", + "PercentCost": "25.952281289242364" + }, + "format": "PART", + "uid": "5f901f6e95a411e79cb17054d2175f18" + }, + { + "children": [], + "data": { + "Cost": "1.9", + "CostLevel": "Low", + "Level": "3", + "Name": "Bracket Pin", + "PartNumber": "665544", + "PercentCost": "7.95311845960653" + }, + "format": "PART", + "uid": "5f90208695a411e79cb17054d2175f18" + } + ], + "properties": { + "tlversion": "2.9.0", + "topnodes": [ + "5f8fdd3895a411e79cb17054d2175f18" + ] + } +} \ No newline at end of file diff -Nru treeline-2.0.2/source/breadcrumbview.py treeline-3.0.1/source/breadcrumbview.py --- treeline-2.0.2/source/breadcrumbview.py 1970-01-01 00:00:00.000000000 +0000 +++ treeline-3.0.1/source/breadcrumbview.py 2018-04-08 19:19:50.000000000 +0000 @@ -0,0 +1,177 @@ +#!/usr/bin/env python3 + +#****************************************************************************** +# breadcrumbview.py, provides a class for the breadcrumb view +# +# TreeLine, an information storage program +# Copyright (C) 2017, 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 +# version. This program is distributed in the hope that it will be useful, +# but WITTHOUT ANY WARRANTY. See the included LICENSE file for details. +#****************************************************************************** + +import operator +from PyQt5.QtCore import QSize, Qt +from PyQt5.QtGui import QPainter, QPalette +from PyQt5.QtWidgets import (QAbstractItemView, QApplication, + QStyledItemDelegate, QTableWidget, + QTableWidgetItem) +import globalref + + +class CrumbItem(QTableWidgetItem): + """Class to store breadcrumb item spot refs and positions. + """ + def __init__(self, spotRef): + """Initialize the breadcrumb item. + + Arguments: + spotRef -- ref to the associated spot item + """ + super().__init__(spotRef.nodeRef.title(spotRef)) + self.spot = spotRef + self.selectedSpot = False + self.setTextAlignment(Qt.AlignCenter) + self.setForeground(QApplication.palette().brush(QPalette.Link)) + + +class BorderDelegate(QStyledItemDelegate): + """Class override to show borders between rows. + """ + def __init__(self, parent=None): + """Initialize the delegate class. + + Arguments: + parent -- the parent view + """ + super().__init__(parent) + + def paint(self, painter, styleOption, modelIndex): + """Paint the cells with borders between rows. + """ + super().paint(painter, styleOption, modelIndex) + cell = self.parent().item(modelIndex.row(), modelIndex.column()) + if modelIndex.row() > 0 and cell: + upperCell = None + row = modelIndex.row() + while not upperCell and row > 0: + row -= 1 + upperCell = self.parent().item(row, modelIndex.column()) + if cell.text() and upperCell and upperCell.text(): + painter.drawLine(styleOption.rect.topLeft(), + styleOption.rect.topRight()) + + +class BreadcrumbView(QTableWidget): + """Class override for the breadcrumb view. + + Sets view defaults and updates the content. + """ + def __init__(self, treeView, parent=None): + """Initialize the breadcrumb view. + + Arguments: + treeView - the tree view, needed for the current selection model + parent -- the parent main window + """ + super().__init__(parent) + self.treeView = treeView + self.borderItems = [] + self.setFocusPolicy(Qt.NoFocus) + self.horizontalHeader().hide() + self.verticalHeader().hide() + self.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel) + self.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel) + self.setSelectionMode(QAbstractItemView.NoSelection) + self.setEditTriggers(QAbstractItemView.NoEditTriggers) + self.setItemDelegate(BorderDelegate(self)) + self.setShowGrid(False) + self.setMouseTracking(True) + self.itemClicked.connect(self.changeSelection) + + def updateContents(self): + """Reload the view's content if the view is shown. + + Avoids update if view is not visible or has zero height or width. + """ + if not self.isVisible() or self.height() == 0 or self.width() == 0: + return + self.clear() + self.clearSpans() + selModel = self.treeView.selectionModel() + selSpots = selModel.selectedSpots() + if len(selSpots) != 1: + return + selSpot = selSpots[0] + spotList = sorted(list(selSpot.nodeRef.spotRefs), + key=operator.methodcaller('sortKey')) + chainList = [[CrumbItem(chainSpot) for chainSpot in spot.spotChain()] + for spot in spotList] + self.setRowCount(len(chainList)) + for row in range(len(chainList)): + columns = len(chainList[row]) * 2 - 1 + if columns > self.columnCount(): + self.setColumnCount(columns) + for col in range(len(chainList[row])): + item = chainList[row][col] + if (row == 0 or col >= len(chainList[row - 1]) or + item.spot is not chainList[row - 1][col].spot): + rowSpan = 1 + while (row + rowSpan < len(chainList) and + col < len(chainList[row + rowSpan]) and + item.spot is chainList[row + rowSpan][col].spot): + rowSpan += 1 + if col < len(chainList[row]) - 1: + arrowItem = QTableWidgetItem('\u25ba') + arrowItem.setTextAlignment(Qt.AlignCenter) + self.setItem(row, col * 2 + 1, arrowItem) + if rowSpan > 1: + self.setSpan(row, col * 2 + 1, rowSpan, 1) + self.setItem(row, col * 2, item) + if rowSpan > 1: + self.setSpan(row, col * 2, rowSpan, 1) + if item.spot is selSpot: + item.selectedSpot = True + item.setForeground(QApplication.palette(). + brush(QPalette.WindowText)) + self.resizeColumnsToContents() + + def changeSelection(self, item): + """Change the current selection to given item bassed on a mouse click. + + Arguments: + item -- the breadcrumb item that was clicked + """ + selModel = self.treeView.selectionModel() + if hasattr(item, 'spot') and not item.selectedSpot: + selModel.selectSpots([item.spot]) + self.setCursor(Qt.ArrowCursor) + + def minimumSizeHint(self): + """Set a short minimum size fint to allow the display of one row. + """ + return QSize(super().minimumSizeHint().width(), + self.fontInfo().pixelSize() * 3) + + def mouseMoveEvent(self, event): + """Change the mouse pointer if over a clickable item. + + Arguments: + event -- the mouse move event + """ + item = self.itemAt(event.localPos().toPoint()) + if item and hasattr(item, 'spot') and not item.selectedSpot: + self.setCursor(Qt.PointingHandCursor) + else: + self.setCursor(Qt.ArrowCursor) + super().mouseMoveEvent(event) + + def resizeEvent(self, event): + """Update view if was collaped by splitter. + """ + if ((event.oldSize().height() == 0 and event.size().height()) or + (event.oldSize().width() == 0 and event.size().width())): + self.updateContents() + return super().resizeEvent(event) diff -Nru treeline-2.0.2/source/conditional.py treeline-3.0.1/source/conditional.py --- treeline-2.0.2/source/conditional.py 2015-01-31 19:13:12.000000000 +0000 +++ treeline-3.0.1/source/conditional.py 2018-04-08 19:19:50.000000000 +0000 @@ -4,7 +4,7 @@ # conditional.py, provides a class to store field comparison functions # # TreeLine, an information storage program -# Copyright (C) 2015, Douglas W. Bell +# Copyright (C) 2017, 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 @@ -13,7 +13,11 @@ #****************************************************************************** import re -from PyQt4 import QtCore, QtGui +import enum +from PyQt5.QtCore import QSize, Qt, pyqtSignal +from PyQt5.QtWidgets import (QComboBox, QDialog, QGroupBox, QHBoxLayout, + QLabel, QLineEdit, QListWidget, QPushButton, + QSizePolicy, QVBoxLayout) import treeformats import configdialog import undo @@ -53,7 +57,8 @@ self.nodeFormatNames = set() if nodeFormatName: self.nodeFormatNames.add(nodeFormatName) - nodeFormats = globalref.mainControl.activeControl.model.formats + nodeFormats = (globalref.mainControl.activeControl.structure. + treeFormats) for nodeType in nodeFormats[nodeFormatName].derivedTypes: self.nodeFormatNames.add(nodeType.name) @@ -64,7 +69,7 @@ node -- the node to check for a field match """ if (self.nodeFormatNames and - node.formatName not in self.nodeFormatNames): + node.formatRef.name not in self.nodeFormatNames): return False result = True for conditon in self.conditionLines: @@ -129,7 +134,7 @@ prevResult -- the result to combine with the boolOper """ try: - field = node.nodeFormat().fieldDict[self.fieldName] + field = node.formatRef.fieldDict[self.fieldName] except KeyError: if self.boolOper == 'and': return False @@ -191,14 +196,16 @@ return False -class ConditionDialog(QtGui.QDialog): +FindDialogType = enum.Enum('FindDialogType', + 'typeDialog findDialog filterDialog') + +class ConditionDialog(QDialog): """Dialog for defining field condition tests. Used for defining conditional types (modal), for finding by condition (nonmodal) and for filtering by condition (nonmodal). """ - dialogShown = QtCore.pyqtSignal(bool) - typeDialog, findDialog, filterDialog = range(3) + dialogShown = pyqtSignal(bool) def __init__(self, dialogType, caption, nodeFormat=None, parent=None): """Create the conditional dialog. @@ -219,77 +226,99 @@ self.fieldNames = [] if nodeFormat: self.fieldNames = nodeFormat.fieldNames() - topLayout = QtGui.QVBoxLayout(self) + topLayout = QVBoxLayout(self) - if dialogType == ConditionDialog.typeDialog: - self.setWindowFlags(QtCore.Qt.Dialog | QtCore.Qt.WindowTitleHint | - QtCore.Qt.WindowSystemMenuHint) + if dialogType == FindDialogType.typeDialog: + self.setWindowFlags(Qt.Dialog | Qt.WindowTitleHint | + Qt.WindowCloseButtonHint) else: - self.setAttribute(QtCore.Qt.WA_QuitOnClose, False) - self.setWindowFlags(QtCore.Qt.Window | - QtCore.Qt.WindowStaysOnTopHint) - typeBox = QtGui.QGroupBox(_('Node Type')) + self.setAttribute(Qt.WA_QuitOnClose, False) + self.setWindowFlags(Qt.Window | + Qt.WindowStaysOnTopHint) + typeBox = QGroupBox(_('Node Type')) topLayout.addWidget(typeBox) - typeLayout = QtGui.QVBoxLayout(typeBox) - self.typeCombo = QtGui.QComboBox() + typeLayout = QVBoxLayout(typeBox) + self.typeCombo = QComboBox() typeLayout.addWidget(self.typeCombo) self.typeCombo.currentIndexChanged.connect(self.updateDataType) - self.mainLayout = QtGui.QVBoxLayout() + self.mainLayout = QVBoxLayout() topLayout.addLayout(self.mainLayout) - upCtrlLayout = QtGui.QHBoxLayout() + upCtrlLayout = QHBoxLayout() topLayout.addLayout(upCtrlLayout) - upCtrlLayout.addStretch() - addButton = QtGui.QPushButton(_('&Add New Rule')) + addButton = QPushButton(_('&Add New Rule')) upCtrlLayout.addWidget(addButton) addButton.clicked.connect(self.addNewRule) - self.removeButton = QtGui.QPushButton(_('&Remove Rule')) + self.removeButton = QPushButton(_('&Remove Rule')) upCtrlLayout.addWidget(self.removeButton) self.removeButton.clicked.connect(self.removeRule) + upCtrlLayout.addStretch() - if dialogType == ConditionDialog.typeDialog: - okButton = QtGui.QPushButton(_('&OK')) + if dialogType == FindDialogType.typeDialog: + okButton = QPushButton(_('&OK')) upCtrlLayout.addWidget(okButton) okButton.clicked.connect(self.accept) - cancelButton = QtGui.QPushButton(_('&Cancel')) + cancelButton = QPushButton(_('&Cancel')) upCtrlLayout.addWidget(cancelButton) cancelButton.clicked.connect(self.reject) else: self.removeButton.setEnabled(False) - self.retrieveButton = QtGui.QPushButton(_('R&etrieve Rules...')) - upCtrlLayout.addWidget(self.retrieveButton) - self.retrieveButton.clicked.connect(self.retrieveRules) - saveButton = QtGui.QPushButton(_('&Save Rules...')) - upCtrlLayout.addWidget(saveButton) - saveButton.clicked.connect(self.saveRules) + saveBox = QGroupBox(_('Saved Rules')) + topLayout.addWidget(saveBox) + saveLayout = QVBoxLayout(saveBox) + self.saveListBox = SmallListWidget() + saveLayout.addWidget(self.saveListBox) + self.saveListBox.itemDoubleClicked.connect(self.loadSavedRule) + nameLayout = QHBoxLayout() + saveLayout.addLayout(nameLayout) + label = QLabel(_('Name:')) + nameLayout.addWidget(label) + self.saveNameEdit = QLineEdit() + nameLayout.addWidget(self.saveNameEdit) + self.saveNameEdit.textChanged.connect(self.updateSaveEnable) + saveButtonLayout = QHBoxLayout() + saveLayout.addLayout(saveButtonLayout) + self.loadSavedButton = QPushButton(_('&Load')) + saveButtonLayout.addWidget(self.loadSavedButton) + self.loadSavedButton.clicked.connect(self.loadSavedRule) + self.saveButton = QPushButton(_('&Save')) + saveButtonLayout.addWidget(self.saveButton) + self.saveButton.clicked.connect(self.saveRule) + self.saveButton.setEnabled(False) + self.delSavedButton = QPushButton(_('&Delete')) + saveButtonLayout.addWidget(self.delSavedButton) + self.delSavedButton.clicked.connect(self.deleteRule) + saveButtonLayout.addStretch() - lowCtrlLayout = QtGui.QHBoxLayout() + if dialogType == FindDialogType.findDialog: + self.resultLabel = QLabel() + topLayout.addWidget(self.resultLabel) + lowCtrlLayout = QHBoxLayout() topLayout.addLayout(lowCtrlLayout) - lowCtrlLayout.addStretch() - if dialogType == ConditionDialog.findDialog: - previousButton = QtGui.QPushButton(_('Find &Previous')) + if dialogType == FindDialogType.findDialog: + previousButton = QPushButton(_('Find &Previous')) lowCtrlLayout.addWidget(previousButton) previousButton.clicked.connect(self.findPrevious) - nextButton = QtGui.QPushButton(_('Find &Next')) + nextButton = QPushButton(_('Find &Next')) nextButton.setDefault(True) lowCtrlLayout.addWidget(nextButton) nextButton.clicked.connect(self.findNext) - self.resultLabel = QtGui.QLabel() - topLayout.addWidget(self.resultLabel) else: - filterButton = QtGui.QPushButton(_('&Filter')) + filterButton = QPushButton(_('&Filter')) lowCtrlLayout.addWidget(filterButton) filterButton.clicked.connect(self.startFilter) - self.endFilterButton = QtGui.QPushButton(_('&End Filter')) + self.endFilterButton = QPushButton(_('&End Filter')) lowCtrlLayout.addWidget(self.endFilterButton) self.endFilterButton.setEnabled(False) self.endFilterButton.clicked.connect(self.endFilter) - closeButton = QtGui.QPushButton(_('&Close')) + lowCtrlLayout.addStretch() + closeButton = QPushButton(_('&Close')) lowCtrlLayout.addWidget(closeButton) closeButton.clicked.connect(self.close) origTypeName = nodeFormat.name if nodeFormat else '' self.loadTypeNames(origTypeName) + self.loadSavedNames() self.ruleList.append(ConditionRule(1, self.fieldNames)) self.mainLayout.addWidget(self.ruleList[0]) @@ -301,14 +330,14 @@ combineBool -- the boolean op for combining with the previous rule """ if self.ruleList: - boolBox = QtGui.QComboBox() + boolBox = QComboBox() boolBox.setEditable(False) self.combiningBoxes.append(boolBox) boolBox.addItems([_(op) for op in _boolOper]) if combineBool != 'and': boolBox.setCurrentIndex(1) self.mainLayout.insertWidget(len(self.ruleList) * 2 - 1, boolBox, - 0, QtCore.Qt.AlignHCenter) + 0, Qt.AlignHCenter) rule = ConditionRule(len(self.ruleList) + 1, self.fieldNames) self.ruleList.append(rule) self.mainLayout.insertWidget(len(self.ruleList) * 2 - 2, rule) @@ -323,7 +352,7 @@ del self.combiningBoxes[-1] self.ruleList[-1].hide() del self.ruleList[-1] - if self.dialogType == ConditionDialog.typeDialog: + if self.dialogType == FindDialogType.typeDialog: self.removeButton.setEnabled(len(self.ruleList) > 0) else: self.removeButton.setEnabled(len(self.ruleList) > 1) @@ -385,7 +414,7 @@ """ if not origTypeName: origTypeName = self.typeCombo.currentText() - nodeFormats = globalref.mainControl.activeControl.model.formats + nodeFormats = globalref.mainControl.activeControl.structure.treeFormats self.typeCombo.blockSignals(True) self.typeCombo.clear() self.typeCombo.addItem(_allTypeEntry) @@ -400,7 +429,6 @@ self.endFilter() self.clearRules() self.typeCombo.blockSignals(False) - self.retrieveButton.setEnabled(len(nodeFormats.savedConditions()) > 0) self.updateDataType() def updateDataType(self): @@ -409,7 +437,7 @@ typeName = self.typeCombo.currentText() if not typeName: return - nodeFormats = globalref.mainControl.activeControl.model.formats + nodeFormats = globalref.mainControl.activeControl.structure.treeFormats if typeName == _allTypeEntry: fieldNameSet = set() for typeFormat in nodeFormats.values(): @@ -426,11 +454,43 @@ break rule.reloadFieldBox(self.fieldNames, currentField) + def loadSavedNames(self, updateOtherDialog=False): + """Refresh the list of saved rule names. + """ + selNum = 0 + if self.saveListBox.count(): + selNum = self.saveListBox.currentRow() + self.saveListBox.clear() + nodeFormats = globalref.mainControl.activeControl.structure.treeFormats + savedRules = nodeFormats.savedConditions() + ruleNames = sorted(list(savedRules.keys())) + if ruleNames: + self.saveListBox.addItems(ruleNames) + if selNum >= len(ruleNames): + selNum = len(ruleNames) - 1 + self.saveListBox.setCurrentRow(selNum) + self.loadSavedButton.setEnabled(len(ruleNames) > 0) + self.delSavedButton.setEnabled(len(ruleNames) > 0) + if updateOtherDialog: + if (self != globalref.mainControl.findConditionDialog and + globalref.mainControl.findConditionDialog and + globalref.mainControl.findConditionDialog.isVisible()): + globalref.mainControl.findConditionDialog.loadSavedNames() + elif (self != globalref.mainControl.filterConditionDialog and + globalref.mainControl.filterConditionDialog and + globalref.mainControl.filterConditionDialog .isVisible()): + globalref.mainControl.filterConditionDialog.loadSavedNames() + + def updateSaveEnable(self): + """Set the save rule button enabled based on save name entry. + """ + self.saveButton.setEnabled(len(self.saveNameEdit.text())) + def updateFilterControls(self): """Set filter button status based on active window changes. """ window = globalref.mainControl.activeControl.activeWindow - if window.isFiltering(): + if window.treeFilterView: filterView = window.treeFilterView conditional = filterView.conditionalFilter self.setCondition(conditional, conditional.origNodeFormatName) @@ -438,54 +498,53 @@ else: self.endFilterButton.setEnabled(False) - def retrieveRules(self): - """Show a menu to retrieve stored rules. + def loadSavedRule(self): + """Load the current saved rule into the dialog. """ - modelRef = globalref.mainControl.activeControl.model - nodeFormats = modelRef.formats + nodeFormats = globalref.mainControl.activeControl.structure.treeFormats savedRules = nodeFormats.savedConditions() - ruleNames = sorted(list(savedRules.keys())) - dlg = RuleRetrieveDialog(ruleNames, self) - if dlg.exec_() == QtGui.QDialog.Accepted: - if dlg.selectedRule: - conditional = savedRules[dlg.selectedRule] - self.setCondition(conditional, conditional.origNodeFormatName) - if dlg.removedRules: - undo.FormatUndo(modelRef.undoList, nodeFormats, - treeformats.TreeFormats()) - for ruleName in dlg.removedRules: - conditional = savedRules[ruleName] - if conditional.origNodeFormatName: - typeFormat = nodeFormats[conditional. - origNodeFormatName] - del typeFormat.savedConditionText[ruleName] - else: - del nodeFormats.savedConditionText[ruleName] - self.retrieveButton.setEnabled(len(nodeFormats. - savedConditions()) > 0) - globalref.mainControl.activeControl.setModified() - - def saveRules(self): - """Prompt for a name for storing these rules. - """ - modelRef = globalref.mainControl.activeControl.model - nodeFormats = modelRef.formats - usedNames = set(nodeFormats.savedConditions().keys()) - dlg = configdialog.NameEntryDialog(_('Save Rules'), - _('Enter a descriptive name'), '', - '', usedNames, self) - if dlg.exec_() == QtGui.QDialog.Accepted: - undo.FormatUndo(modelRef.undoList, nodeFormats, - treeformats.TreeFormats()) - typeName = self.typeCombo.currentText() - if typeName == _allTypeEntry: - nodeFormat = nodeFormats - else: - nodeFormat = nodeFormats[typeName] - nodeFormat.savedConditionText[dlg.text] = (self.conditional(). - conditionStr()) - self.retrieveButton.setEnabled(True) - globalref.mainControl.activeControl.setModified() + ruleName = self.saveListBox.currentItem().text() + conditional = savedRules[ruleName] + self.setCondition(conditional, conditional.origNodeFormatName) + + def saveRule(self): + """Save the current rule settings. + """ + name = self.saveNameEdit.text() + self.saveNameEdit.setText('') + treeStructure = globalref.mainControl.activeControl.structure + undo.FormatUndo(treeStructure.undoList, treeStructure.treeFormats, + treeformats.TreeFormats()) + typeName = self.typeCombo.currentText() + if typeName == _allTypeEntry: + nodeFormat = treeStructure.treeFormats + else: + nodeFormat = treeStructure.treeFormats[typeName] + nodeFormat.savedConditionText[name] = (self.conditional(). + conditionStr()) + self.loadSavedNames(True) + self.saveListBox.setCurrentItem(self.saveListBox. + findItems(name, Qt.MatchExactly)[0]) + globalref.mainControl.activeControl.setModified() + + def deleteRule(self): + """Remove the current saved rule. + """ + treeStructure = globalref.mainControl.activeControl.structure + nodeFormats = treeStructure.treeFormats + undo.FormatUndo(treeStructure.undoList, nodeFormats, + treeformats.TreeFormats()) + savedRules = nodeFormats.savedConditions() + ruleName = self.saveListBox.currentItem().text() + conditional = savedRules[ruleName] + if conditional.origNodeFormatName: + typeFormat = nodeFormats[conditional. + origNodeFormatName] + del typeFormat.savedConditionText[ruleName] + else: + del nodeFormats.savedConditionText[ruleName] + self.loadSavedNames(True) + globalref.mainControl.activeControl.setModified() def find(self, forward=True): """Find another match in the indicated direction. @@ -513,19 +572,17 @@ """Start filtering nodes. """ window = globalref.mainControl.activeControl.activeWindow - filterView = window.treeFilterView + filterView = window.filterView() filterView.conditionalFilter = self.conditional() filterView.updateContents() - window.treeStack.setCurrentWidget(filterView) self.endFilterButton.setEnabled(True) def endFilter(self): """Stop filtering nodes. """ window = globalref.mainControl.activeControl.activeWindow - window.treeStack.setCurrentWidget(window.treeView) + window.removeFilterView() self.endFilterButton.setEnabled(False) - globalref.mainControl.currentStatusBar().clearMessage() def closeEvent(self, event): """Signal that the dialog is closing. @@ -536,7 +593,7 @@ self.dialogShown.emit(False) -class ConditionRule(QtGui.QGroupBox): +class ConditionRule(QGroupBox): """Group boxes for conditional rules in the ConditionDialog. """ def __init__(self, num, fieldNames, parent=None): @@ -550,19 +607,19 @@ super().__init__(parent) self.fieldNames = fieldNames self.setTitle(_('Rule {0}').format(num)) - layout = QtGui.QHBoxLayout(self) - self.fieldBox = QtGui.QComboBox() + layout = QHBoxLayout(self) + self.fieldBox = QComboBox() self.fieldBox.setEditable(False) self.fieldBox.addItems(fieldNames) layout.addWidget(self.fieldBox) - self.operBox = QtGui.QComboBox() + self.operBox = QComboBox() self.operBox.setEditable(False) self.operBox.addItems([_(op) for op in _operators]) layout.addWidget(self.operBox) self.operBox.currentIndexChanged.connect(self.changeOper) - self.editor = QtGui.QLineEdit() + self.editor = QLineEdit() layout.addWidget(self.editor) self.fieldBox.setFocus() @@ -613,58 +670,25 @@ self.fieldBox.setEnabled(realOp) -class RuleRetrieveDialog(QtGui.QDialog): - """Dialog to select saved conditional rules for retrieval or removal. +class SmallListWidget(QListWidget): + """ListWidget with a smaller size hint. """ - def __init__(self, ruleNames, parent=None): - """Initialize the rule retrieval dialog. + def __init__(self, parent=None): + """Initialize the widget. Arguments: - ruleNames -- a list of rulenames to show - parent -- the parent overall dialog + parent -- the parent, if given """ super().__init__(parent) - self.ruleNames = ruleNames - self.setWindowFlags(QtCore.Qt.Dialog | QtCore.Qt.WindowTitleHint | - QtCore.Qt.WindowSystemMenuHint) - self.setWindowTitle(_('Retrieve Rules')) - self.selectedRule = '' - self.removedRules = [] - - topLayout = QtGui.QVBoxLayout(self) - label = QtGui.QLabel(_('Select rule set to retrieve:')) - topLayout.addWidget(label) - self.listBox = QtGui.QListWidget() - topLayout.addWidget(self.listBox) - self.listBox.addItems(ruleNames) - self.listBox.setCurrentRow (0) - self.listBox.itemDoubleClicked.connect(self.accept) - - ctrlLayout = QtGui.QHBoxLayout() - topLayout.addLayout(ctrlLayout) - removeButton = QtGui.QPushButton(_('Remove Rule')) - ctrlLayout.addWidget(removeButton) - removeButton.clicked.connect(self.removeRule) - ctrlLayout.addStretch() - okButton = QtGui.QPushButton(_('&OK')) - ctrlLayout.addWidget(okButton) - okButton.clicked.connect(self.accept) - cancelButton = QtGui.QPushButton(_('&Cancel')) - ctrlLayout.addWidget(cancelButton) - cancelButton.clicked.connect(self.reject) - def removeRule(self): - """Remove the currently selected rule. + def sizeHint(self): + """Return smaller height. """ - currentItem = self.listBox.currentItem() - if currentItem: - self.removedRules.append(currentItem.text()) - self.listBox.takeItem(self.listBox.currentRow()) - - def accept(self): - """Recored results before closing. - """ - currentItem = self.listBox.currentItem() - if currentItem: - self.selectedRule = currentItem.text() - return super().accept() + if self.count(): + rowHeight = self.sizeHintForRow(0) + else: + self.addItem('tmp') + rowHeight = self.sizeHintForRow(0) + self.takeItem(0) + newHeight = rowHeight * 3 + self.frameWidth() * 2 + return QSize(super().sizeHint().width(), newHeight) diff -Nru treeline-2.0.2/source/configdialog.py treeline-3.0.1/source/configdialog.py --- treeline-2.0.2/source/configdialog.py 2015-03-27 01:39:14.000000000 +0000 +++ treeline-3.0.1/source/configdialog.py 2018-09-29 16:40:05.000000000 +0000 @@ -4,7 +4,7 @@ # configdialog.py, provides classes for the type configuration dialog # # TreeLine, an information storage program -# Copyright (C) 2015, Douglas W. Bell +# Copyright (C) 2018, 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 @@ -15,7 +15,16 @@ import re import copy import operator -from PyQt4 import QtCore, QtGui +from PyQt5.QtCore import QPoint, QSize, Qt, pyqtSignal +from PyQt5.QtGui import QTextCursor +from PyQt5.QtWidgets import (QAbstractItemView, QApplication, QButtonGroup, + QCheckBox, QComboBox, QDialog, QGridLayout, + QGroupBox, QHBoxLayout, QLabel, QLineEdit, + QListView, QListWidget, QListWidgetItem, QMenu, + QMessageBox, QPushButton, QScrollArea, + QSizePolicy, QSpinBox, QTabWidget, QTextEdit, + QTreeWidget, QTreeWidgetItem, QVBoxLayout, + QWidget) import nodeformat import fieldformat import icondict @@ -24,13 +33,13 @@ import globalref -class ConfigDialog(QtGui.QDialog): +class ConfigDialog(QDialog): """Class override for the main config dialog Contains the tabbed pages that handle the actual settings. """ - dialogShown = QtCore.pyqtSignal(bool) - modelRef = None + dialogShown = pyqtSignal(bool) + treeStruct = None formatsRef = None currentTypeName = '' currentFieldName = '' @@ -41,16 +50,17 @@ parent -- the parent window """ super().__init__(parent) - self.setAttribute(QtCore.Qt.WA_QuitOnClose, False) - self.setWindowFlags(QtCore.Qt.Window) + self.setAttribute(Qt.WA_QuitOnClose, False) + self.setWindowFlags(Qt.Window) self.setWindowTitle(_('Configure Data Types')) self.prevPage = None + self.localControl = None self.selectionModel = None - topLayout = QtGui.QVBoxLayout(self) + topLayout = QVBoxLayout(self) self.setLayout(topLayout) - self.tabs = QtGui.QTabWidget() + self.tabs = QTabWidget() topLayout.addWidget(self.tabs) typeListPage = TypeListPage(self) self.tabs.addTab(typeListPage, _('T&ype List')) @@ -64,39 +74,41 @@ self.tabs.addTab(outputPage, _('O&utput')) self.tabs.currentChanged.connect(self.updatePage) - ctrlLayout = QtGui.QHBoxLayout() + ctrlLayout = QHBoxLayout() topLayout.addLayout(ctrlLayout) - self.advancedButton = QtGui.QPushButton(_('&Show Advanced')) + self.advancedButton = QPushButton(_('&Show Advanced')) ctrlLayout.addWidget(self.advancedButton) self.advancedButton.setCheckable(True) self.advancedButton.clicked.connect(self.toggleAdavanced) ctrlLayout.addStretch() - okButton = QtGui.QPushButton(_('&OK')) + okButton = QPushButton(_('&OK')) ctrlLayout.addWidget(okButton) okButton.clicked.connect(self.applyAndClose) - self.applyButton = QtGui.QPushButton(_('&Apply')) + self.applyButton = QPushButton(_('&Apply')) ctrlLayout.addWidget(self.applyButton) self.applyButton.clicked.connect(self.applyChanges) - self.resetButton = QtGui.QPushButton(_('&Reset')) + self.resetButton = QPushButton(_('&Reset')) ctrlLayout.addWidget(self.resetButton) self.resetButton.clicked.connect(self.reset) - cancelButton = QtGui.QPushButton(_('&Cancel')) + cancelButton = QPushButton(_('&Cancel')) ctrlLayout.addWidget(cancelButton) cancelButton.clicked.connect(self.resetAndClose) - def setRefs(self, modelRef, selectionModel, resetSelect=False): + def setRefs(self, localControl, resetSelect=False, forceCopy=False): """Set refs to model and formats, then update dialog data. Sets current type to current node's type if resetSelect or if invalid. Sets current field to first field if resetSelect or if invalid. Arguments: - modelRef - a reference to the model and formats - selectionModel - holds node selection for initial selections + localControl -- a reference to the local control resetSelect -- if True, forces reset of current selections + forceCopy -- if True, force making a new copy of formats """ - ConfigDialog.modelRef = modelRef - ConfigDialog.formatsRef = modelRef.getConfigDialogFormats() - self.selectionModel = selectionModel + self.localControl = localControl + ConfigDialog.treeStruct = localControl.structure + ConfigDialog.formatsRef = (ConfigDialog.treeStruct. + getConfigDialogFormats(forceCopy)) + self.selectionModel = localControl.currentSelectionModel() self.updateSelections(resetSelect) self.setModified(modified=False) self.prevPage = None @@ -110,8 +122,12 @@ """ if forceUpdate or (ConfigDialog.currentTypeName not in ConfigDialog.formatsRef): - ConfigDialog.currentTypeName = (self.selectionModel.currentNode(). - formatName) + try: + ConfigDialog.currentTypeName = (self.selectionModel. + currentNode().formatRef.name) + except AttributeError: # no current node + ConfigDialog.currentTypeName = (ConfigDialog.treeStruct. + childList[0].formatRef.name) if forceUpdate or (ConfigDialog.currentFieldName not in ConfigDialog.formatsRef[ConfigDialog. currentTypeName].fieldNames()): @@ -158,7 +174,7 @@ def reset(self): """Set the formats back to original settings. """ - ConfigDialog.formatsRef = (ConfigDialog.modelRef. + ConfigDialog.formatsRef = (ConfigDialog.treeStruct. getConfigDialogFormats(True)) self.updateSelections() self.setModified(modified=False) @@ -173,18 +189,13 @@ self.tabs.currentWidget().readChanges() if ConfigDialog.formatsRef.configModified: try: - ConfigDialog.modelRef.applyConfigDialogFormats() + ConfigDialog.treeStruct.applyConfigDialogFormats() except matheval.CircularMathError: - QtGui.QMessageBox.warning(self, 'TreeLine', + QMessageBox.warning(self, 'TreeLine', _('Error - circular reference in math field equations')) return False - ConfigDialog.formatsRef = (ConfigDialog.modelRef. - getConfigDialogFormats()) self.setModified(modified=False) - pluginInterface = globalref.mainControl.pluginInterface - if pluginInterface: - pluginInterface.execCallback(pluginInterface. - formatChangeCallbacks) + self.localControl.updateAll() return True def applyAndClose(self): @@ -208,7 +219,7 @@ self.dialogShown.emit(False) -class ConfigPage(QtGui.QWidget): +class ConfigPage(QWidget): """Abstract base class for config dialog tabbed pages. """ def __init__(self, parent=None): @@ -245,7 +256,9 @@ ConfigDialog.currentTypeName = typeName ConfigDialog.currentFieldName = (ConfigDialog.formatsRef[typeName]. fieldNames()[0]) - self.updateContent() + if type(self) != TypeListPage: + # "if" statement added to work around list view selection bug + self.updateContent() def changeCurrentField(self, fieldName): """Change the current format field based on a signal from lists. @@ -277,26 +290,27 @@ parent -- the parent overall dialog """ super().__init__(parent) - topLayout = QtGui.QVBoxLayout(self) - box = QtGui.QGroupBox(_('Add or Remove Data Types')) + topLayout = QVBoxLayout(self) + box = QGroupBox(_('Add or Remove Data Types')) topLayout.addWidget(box) - horizLayout = QtGui.QHBoxLayout(box) - self.listBox = QtGui.QListWidget() + horizLayout = QHBoxLayout(box) + self.listBox = QListWidget() + self.listBox.setSelectionMode(QAbstractItemView.SingleSelection) horizLayout.addWidget(self.listBox) self.listBox.currentTextChanged.connect(self.changeCurrentType) - buttonLayout = QtGui.QVBoxLayout() + buttonLayout = QVBoxLayout() horizLayout.addLayout(buttonLayout) - newButton = QtGui.QPushButton(_('&New Type...')) + newButton = QPushButton(_('&New Type...')) buttonLayout.addWidget(newButton) newButton.clicked.connect(self.newType) - copyButton = QtGui.QPushButton(_('Co&py Type...')) + copyButton = QPushButton(_('Co&py Type...')) buttonLayout.addWidget(copyButton) copyButton.clicked.connect(self.copyType) - renameButton = QtGui.QPushButton(_('Rena&me Type...')) + renameButton = QPushButton(_('Rena&me Type...')) buttonLayout.addWidget(renameButton) renameButton.clicked.connect(self.renameType) - deleteButton = QtGui.QPushButton(_('&Delete Type')) + deleteButton = QPushButton(_('&Delete Type')) buttonLayout.addWidget(deleteButton) deleteButton.clicked.connect(self.deleteType) @@ -315,9 +329,9 @@ """ dlg = NameEntryDialog(_('Add Type'), _('Enter new type name:'), '', '', ConfigDialog.formatsRef.typeNames(), self) - if dlg.exec_() == QtGui.QDialog.Accepted: + if dlg.exec_() == QDialog.Accepted: newFormat = nodeformat.NodeFormat(dlg.text, - ConfigDialog.formatsRef, {}, + ConfigDialog.formatsRef, None, True) ConfigDialog.formatsRef[dlg.text] = newFormat ConfigDialog.currentTypeName = dlg.text @@ -335,7 +349,7 @@ ConfigDialog.formatsRef.typeNames(), self) if currentFormat.genericType: dlg.extraCheckBox.setEnabled(False) - if dlg.exec_() == QtGui.QDialog.Accepted: + if dlg.exec_() == QDialog.Accepted: newFormat = copy.deepcopy(currentFormat) newFormat.name = dlg.text ConfigDialog.formatsRef[dlg.text] = newFormat @@ -353,7 +367,7 @@ dlg = NameEntryDialog(_('Rename Type'), _('Rename from {} to:').format(oldName), oldName, '', ConfigDialog.formatsRef.typeNames(), self) - if dlg.exec_() == QtGui.QDialog.Accepted: + if dlg.exec_() == QDialog.Accepted: currentType = ConfigDialog.formatsRef[oldName] currentType.name = dlg.text del ConfigDialog.formatsRef[oldName] @@ -373,6 +387,9 @@ nodeType.childType = dlg.text if nodeType.genericType == oldName: nodeType.genericType = dlg.text + if oldName in nodeType.childTypeLimit: + nodeType.childTypeLimit.remove(oldName) + nodeType.childTypeLimit.add(dlg.text) ConfigDialog.currentTypeName = dlg.text self.updateContent() self.mainDialogRef.setModified() @@ -380,17 +397,26 @@ def deleteType(self): """Delete the selected type based on button signal. """ - if ConfigDialog.modelRef.root.usesType(ConfigDialog.currentTypeName): - QtGui.QMessageBox.warning(self, 'TreeLine', + # reverse the rename dict - find original name (before any rename) + reverseDict = {} + for old, new in ConfigDialog.formatsRef.typeRenameDict.items(): + reverseDict[new] = old + origName = reverseDict.get(ConfigDialog.currentTypeName, + ConfigDialog.currentTypeName) + if ConfigDialog.treeStruct.usesType(origName): + QMessageBox.warning(self, 'TreeLine', _('Cannot delete data type being used by nodes')) return del ConfigDialog.formatsRef[ConfigDialog.currentTypeName] + if origName != ConfigDialog.currentTypeName: + del ConfigDialog.formatsRef.typeRenameDict[origName] for nodeType in ConfigDialog.formatsRef.values(): if nodeType.childType == ConfigDialog.currentTypeName: nodeType.childType = '' if nodeType.genericType == ConfigDialog.currentTypeName: nodeType.genericType = '' - nodeType.conditional = conditional.Conditional() + nodeType.conditional = None + nodeType.childTypeLimit.discard(ConfigDialog.currentTypeName) ConfigDialog.formatsRef.updateDerivedRefs() ConfigDialog.currentTypeName = ConfigDialog.formatsRef.typeNames()[0] ConfigDialog.currentFieldName = ConfigDialog.formatsRef[ConfigDialog. @@ -411,89 +437,89 @@ parent -- the parent overall dialog """ super().__init__(parent) - topLayout = QtGui.QGridLayout(self) - typeBox = QtGui.QGroupBox(_('&Data Type')) + topLayout = QGridLayout(self) + typeBox = QGroupBox(_('&Data Type')) topLayout.addWidget(typeBox, 0, 0) - typeLayout = QtGui.QVBoxLayout(typeBox) - self.typeCombo = QtGui.QComboBox() + typeLayout = QVBoxLayout(typeBox) + self.typeCombo = QComboBox() typeLayout.addWidget(self.typeCombo) self.typeCombo.currentIndexChanged[str].connect(self.changeCurrentType) - childBox = QtGui.QGroupBox(_('Default Child &Type')) + childBox = QGroupBox(_('Default Child &Type')) topLayout.addWidget(childBox, 0, 1) - childLayout = QtGui.QVBoxLayout(childBox) - self.childCombo = QtGui.QComboBox() + childLayout = QVBoxLayout(childBox) + self.childCombo = QComboBox() childLayout.addWidget(self.childCombo) self.childCombo.currentIndexChanged.connect(self.mainDialogRef. setModified) - iconBox = QtGui.QGroupBox(_('Icon')) + iconBox = QGroupBox(_('Icon')) topLayout.addWidget(iconBox, 1, 1) - iconLayout = QtGui.QHBoxLayout(iconBox) - self.iconImage = QtGui.QLabel() + iconLayout = QHBoxLayout(iconBox) + self.iconImage = QLabel() iconLayout.addWidget(self.iconImage) - self.iconImage.setAlignment(QtCore.Qt.AlignCenter) - iconButton = QtGui.QPushButton(_('Change &Icon')) + self.iconImage.setAlignment(Qt.AlignCenter) + iconButton = QPushButton(_('Change &Icon')) iconLayout.addWidget(iconButton) iconButton.clicked.connect(self.changeIcon) - optionsBox = QtGui.QGroupBox(_('Output Options')) + optionsBox = QGroupBox(_('Output Options')) topLayout.addWidget(optionsBox, 1, 0, 2, 1) - optionsLayout = QtGui.QVBoxLayout(optionsBox) - self.blanksButton = QtGui.QCheckBox(_('Add &blank lines between ' + optionsLayout = QVBoxLayout(optionsBox) + self.blanksButton = QCheckBox(_('Add &blank lines between ' 'nodes')) optionsLayout.addWidget(self.blanksButton) self.blanksButton.toggled.connect(self.mainDialogRef.setModified) - self.htmlButton = QtGui.QCheckBox(_('Allow &HTML rich text in format')) + self.htmlButton = QCheckBox(_('Allow &HTML rich text in format')) optionsLayout.addWidget(self.htmlButton) self.htmlButton.toggled.connect(self.mainDialogRef.setModified) - self.bulletButton = QtGui.QCheckBox(_('Add text bullet&s')) + self.bulletButton = QCheckBox(_('Add text bullet&s')) optionsLayout.addWidget(self.bulletButton) self.bulletButton.toggled.connect(self.changeUseBullets) - self.tableButton = QtGui.QCheckBox(_('Use a table for field &data')) + self.tableButton = QCheckBox(_('Use a table for field &data')) optionsLayout.addWidget(self.tableButton) self.tableButton.toggled.connect(self.changeUseTable) # advanced widgets - outputSepBox = QtGui.QGroupBox(_('Combination && Child List Output ' - '&Separator')) + outputSepBox = QGroupBox(_('Combination && Child List Output ' + '&Separator')) topLayout.addWidget(outputSepBox, 2, 1) self.advancedWidgets.append(outputSepBox) - outputSepLayout = QtGui.QVBoxLayout(outputSepBox) - self.outputSepEdit = QtGui.QLineEdit() + outputSepLayout = QVBoxLayout(outputSepBox) + self.outputSepEdit = QLineEdit() outputSepLayout.addWidget(self.outputSepEdit) sizePolicy = self.outputSepEdit.sizePolicy() - sizePolicy.setHorizontalPolicy(QtGui.QSizePolicy.Preferred) + sizePolicy.setHorizontalPolicy(QSizePolicy.Preferred) self.outputSepEdit.setSizePolicy(sizePolicy) self.outputSepEdit.textEdited.connect(self.mainDialogRef.setModified) - idFieldBox = QtGui.QGroupBox(_('Uni&que ID Reference Field')) - topLayout.addWidget(idFieldBox, 3, 0) - self.advancedWidgets.append(idFieldBox) - idFieldLayout = QtGui.QVBoxLayout(idFieldBox) - self.idFieldCombo = QtGui.QComboBox() - idFieldLayout.addWidget(self.idFieldCombo) - self.idFieldCombo.currentIndexChanged.connect(self.mainDialogRef. - setModified) - - genericBox = QtGui.QGroupBox(_('Derived from &Generic Type')) - topLayout.addWidget(genericBox, 3, 1) + genericBox = QGroupBox(_('Derived from &Generic Type')) + topLayout.addWidget(genericBox, 3, 0) self.advancedWidgets.append(genericBox) - genericLayout = QtGui.QVBoxLayout(genericBox) - self.genericCombo = QtGui.QComboBox() + genericLayout = QVBoxLayout(genericBox) + self.genericCombo = QComboBox() genericLayout.addWidget(self.genericCombo) - self.genericCombo.currentIndexChanged.connect(self.setGenericIdRef) + self.genericCombo.currentIndexChanged.connect(self.setConditionAvail) self.genericCombo.currentIndexChanged.connect(self.mainDialogRef. setModified) - conditionBox = QtGui.QGroupBox(_('Automatic Types')) - topLayout.addWidget(conditionBox, 4, 1) + conditionBox = QGroupBox(_('Automatic Types')) + topLayout.addWidget(conditionBox, 3, 1) self.advancedWidgets.append(conditionBox) - conditionLayout = QtGui.QVBoxLayout(conditionBox) - self.conditionButton = QtGui.QPushButton() + conditionLayout = QVBoxLayout(conditionBox) + self.conditionButton = QPushButton() conditionLayout.addWidget(self.conditionButton) self.conditionButton.clicked.connect(self.showConditionDialog) + typeLimitBox = QGroupBox(_('Child Type Limits')) + topLayout.addWidget(typeLimitBox, 4, 0) + self.advancedWidgets.append(typeLimitBox) + typeLimitLayout = QVBoxLayout(typeLimitBox) + self.typeLimitCombo = TypeLimitCombo() + typeLimitLayout.addWidget(self.typeLimitCombo) + self.typeLimitCombo.limitChanged.connect(self.mainDialogRef. + setModified) + topLayout.setRowStretch(5, 1) def updateContent(self): @@ -546,34 +572,30 @@ self.outputSepEdit.setText(currentFormat.outputSeparator) - self.idFieldCombo.blockSignals(True) - self.idFieldCombo.clear() - self.idFieldCombo.addItems(currentFormat.fieldNames()) - self.idFieldCombo.setCurrentIndex(currentFormat.fieldNames(). - index(currentFormat.idField.name)) - self.idFieldCombo.blockSignals(False) - self.idFieldCombo.setEnabled(not currentFormat.genericType) - self.genericCombo.blockSignals(True) self.genericCombo.clear() self.genericCombo.addItem(_noTypeSetName) - typeNames = [name for name in typeNames if - name != ConfigDialog.currentTypeName] - self.genericCombo.addItems(typeNames) + genTypeNames = [name for name in typeNames if + name != ConfigDialog.currentTypeName and + not ConfigDialog.formatsRef[name].genericType] + self.genericCombo.addItems(genTypeNames) try: - generic = typeNames.index(currentFormat.genericType) + 1 + generic = genTypeNames.index(currentFormat.genericType) + 1 except ValueError: generic = 0 self.genericCombo.setCurrentIndex(generic) self.genericCombo.blockSignals(False) self.setConditionAvail() + self.typeLimitCombo.updateLists(typeNames, + currentFormat.childTypeLimit) + def changeIcon(self): """Show the change icon dialog based on a button press. """ currentFormat = ConfigDialog.formatsRef[ConfigDialog.currentTypeName] dlg = IconSelectDialog(currentFormat, self) - if (dlg.exec_() == QtGui.QDialog.Accepted and + if (dlg.exec_() == QDialog.Accepted and dlg.currentIconName != currentFormat.iconName): currentFormat.iconName = dlg.currentIconName self.mainDialogRef.setModified() @@ -607,24 +629,6 @@ self.htmlButton.setEnabled(not checked) self.mainDialogRef.setModified() - def setGenericIdRef(self): - """Update the unique ID combobox based on a generic type change. - """ - genericType = self.genericCombo.currentText() - if genericType == _noTypeSetName: - typeFormat = ConfigDialog.formatsRef[ConfigDialog.currentTypeName] - self.idFieldCombo.setEnabled(True) - else: - typeFormat = ConfigDialog.formatsRef[genericType] - self.idFieldCombo.setEnabled(False) - self.idFieldCombo.blockSignals(True) - self.idFieldCombo.clear() - self.idFieldCombo.addItems(typeFormat.fieldNames()) - self.idFieldCombo.setCurrentIndex(typeFormat.fieldNames(). - index(typeFormat.idField.name)) - self.idFieldCombo.blockSignals(False) - self.setConditionAvail() - def setConditionAvail(self): """Enable conditional button if generic or dervived type. @@ -644,14 +648,16 @@ """Show the dialog to create or modify conditional types. """ currentFormat = ConfigDialog.formatsRef[ConfigDialog.currentTypeName] - dialog = conditional.ConditionDialog(conditional.ConditionDialog. + dialog = conditional.ConditionDialog(conditional.FindDialogType. typeDialog, _('Set Types Conditionally'), currentFormat) if currentFormat.conditional: dialog.setCondition(currentFormat.conditional) - if dialog.exec_() == QtGui.QDialog.Accepted: + if dialog.exec_() == QDialog.Accepted: currentFormat.conditional = dialog.conditional() + if not currentFormat.conditional: + currentFormat.conditional = None ConfigDialog.formatsRef.updateDerivedRefs() self.mainDialogRef.setModified() self.updateContent() @@ -664,7 +670,6 @@ if currentFormat.childType == _noTypeSetName: currentFormat.childType = '' currentFormat.outputSeparator = self.outputSepEdit.text() - oldIdField = currentFormat.idField prevGenericType = currentFormat.genericType currentFormat.genericType = self.genericCombo.currentText() if currentFormat.genericType == _noTypeSetName: @@ -672,8 +677,11 @@ if currentFormat.genericType != prevGenericType: ConfigDialog.formatsRef.updateDerivedRefs() currentFormat.updateFromGeneric(formatsRef=ConfigDialog.formatsRef) + if ConfigDialog.currentFieldName not in currentFormat.fieldNames(): + ConfigDialog.currentFieldName = currentFormat.fieldNames()[0] currentFormat.spaceBetween = self.blanksButton.isChecked() currentFormat.formatHtml = self.htmlButton.isChecked() + currentFormat.childTypeLimit = self.typeLimitCombo.selectSet useBullets = self.bulletButton.isChecked() useTables = self.tableButton.isChecked() if (useBullets != currentFormat.useBullets or @@ -686,11 +694,6 @@ currentFormat.addTables() else: currentFormat.clearBulletsAndTables() - currentFormat.idField = currentFormat.fieldDict[self.idFieldCombo. - currentText()] - if currentFormat.idField != oldIdField: - currentFormat.updateDerivedTypes() - ConfigDialog.formatsRef.changedIdFieldTypes.add(currentFormat) class FieldListPage(ConfigPage): @@ -703,18 +706,18 @@ parent -- the parent overall dialog """ super().__init__(parent) - topLayout = QtGui.QVBoxLayout(self) - typeBox = QtGui.QGroupBox(_('&Data Type')) + topLayout = QVBoxLayout(self) + typeBox = QGroupBox(_('&Data Type')) topLayout.addWidget(typeBox) - typeLayout = QtGui.QVBoxLayout(typeBox) - self.typeCombo = QtGui.QComboBox() + typeLayout = QVBoxLayout(typeBox) + self.typeCombo = QComboBox() typeLayout.addWidget(self.typeCombo) self.typeCombo.currentIndexChanged[str].connect(self.changeCurrentType) - fieldBox = QtGui.QGroupBox(_('Modify &Field List')) + fieldBox = QGroupBox(_('Modify &Field List')) topLayout.addWidget(fieldBox) - horizLayout = QtGui.QHBoxLayout(fieldBox) - self.fieldListBox = QtGui.QTreeWidget() + horizLayout = QHBoxLayout(fieldBox) + self.fieldListBox = QTreeWidget() horizLayout.addWidget(self.fieldListBox) self.fieldListBox.setRootIsDecorated(False) self.fieldListBox.setColumnCount(3) @@ -722,24 +725,24 @@ _('Sort Key')]) self.fieldListBox.currentItemChanged.connect(self.changeField) - buttonLayout = QtGui.QVBoxLayout() + buttonLayout = QVBoxLayout() horizLayout.addLayout(buttonLayout) - self.upButton = QtGui.QPushButton(_('Move U&p')) + self.upButton = QPushButton(_('Move U&p')) buttonLayout.addWidget(self.upButton) self.upButton.clicked.connect(self.moveUp) - self.downButton = QtGui.QPushButton(_('Move Do&wn')) + self.downButton = QPushButton(_('Move Do&wn')) buttonLayout.addWidget(self.downButton) self.downButton.clicked.connect(self.moveDown) - self.newButton = QtGui.QPushButton(_('&New Field...')) + self.newButton = QPushButton(_('&New Field...')) buttonLayout.addWidget(self.newButton) self.newButton.clicked.connect(self.newField) - self.renameButton = QtGui.QPushButton(_('Rena&me Field...')) + self.renameButton = QPushButton(_('Rena&me Field...')) buttonLayout.addWidget(self.renameButton) self.renameButton.clicked.connect(self.renameField) - self.deleteButton = QtGui.QPushButton(_('Dele&te Field')) + self.deleteButton = QPushButton(_('Dele&te Field')) buttonLayout.addWidget(self.deleteButton) self.deleteButton.clicked.connect(self.deleteField) - sortKeyButton = QtGui.QPushButton(_('Sort &Keys...')) + sortKeyButton = QPushButton(_('Sort &Keys...')) buttonLayout.addWidget(sortKeyButton) sortKeyButton.clicked.connect(self.defineSortKeys) @@ -769,13 +772,13 @@ sortKey = '{0} ({1})'.format(sortKey, sortDir) except ValueError: sortKey = '' - QtGui.QTreeWidgetItem(self.fieldListBox, - [field.name, _(field.typeName), sortKey]) + typeName = fieldformat.translatedTypeName(field.typeName) + QTreeWidgetItem(self.fieldListBox, [field.name, typeName, sortKey]) selectNum = currentFormat.fieldNames().index(ConfigDialog. currentFieldName) selectItem = self.fieldListBox.topLevelItem(selectNum) self.fieldListBox.setCurrentItem(selectItem) - self.fieldListBox.setItemSelected(selectItem, True) + selectItem.setSelected(True) width = self.fieldListBox.viewport().width() self.fieldListBox.setColumnWidth(0, width // 2.5) self.fieldListBox.setColumnWidth(1, width // 2.5) @@ -831,7 +834,7 @@ currentFormat = ConfigDialog.formatsRef[ConfigDialog.currentTypeName] dlg = NameEntryDialog(_('Add Field'), _('Enter new field name:'), '', '', currentFormat.fieldNames(), self) - if dlg.exec_() == QtGui.QDialog.Accepted: + if dlg.exec_() == QDialog.Accepted: currentFormat.addField(dlg.text) ConfigDialog.currentFieldName = dlg.text currentFormat.updateDerivedTypes() @@ -847,7 +850,7 @@ dlg = NameEntryDialog(_('Rename Field'), _('Rename from {} to:').format(oldName), oldName, '', fieldList, self) - if dlg.exec_() == QtGui.QDialog.Accepted: + if dlg.exec_() == QDialog.Accepted: num = fieldList.index(oldName) fieldList[num] = dlg.text for nodeFormat in [currentFormat] + currentFormat.derivedTypes: @@ -855,13 +858,14 @@ field.name = dlg.text nodeFormat.fieldDict[field.name] = field nodeFormat.reorderFields(fieldList) - nodeFormat.conditional.renameFields(oldName, field.name) - savedConditions = {} - for name, text in nodeFormat.savedConditionText.items(): - condition = conditional.Conditional(text, nodeFormat.name) - condition.renameFields(oldName, field.name) - savedConditions[name] = condition.conditionStr() - nodeFormat.savedConditionText = savedConditions + if nodeFormat.conditional: + nodeFormat.conditional.renameFields(oldName, field.name) + # savedConditions = {} + # for name, text in nodeFormat.savedConditionText.items(): + # condition = conditional.Conditional(text, nodeFormat.name) + # condition.renameFields(oldName, field.name) + # savedConditions[name] = condition.conditionStr() + # nodeFormat.savedConditionText = savedConditions renameDict = (ConfigDialog.formatsRef.fieldRenameDict. setdefault(nodeFormat.name, {})) # reverse rename dict - find original name (multiple renames) @@ -883,9 +887,6 @@ field = nodeFormat.fieldDict[ConfigDialog.currentFieldName] nodeFormat.removeField(field) del nodeFormat.fieldDict[ConfigDialog.currentFieldName] - if nodeFormat.idField == field: - nodeFormat.idField = list(nodeFormat.fieldDict.values())[0] - ConfigDialog.formatsRef.changedIdFieldTypes.add(nodeFormat) if num > 0: num -= 1 ConfigDialog.currentFieldName = currentFormat.fieldNames()[num] @@ -898,7 +899,7 @@ """ currentFormat = ConfigDialog.formatsRef[ConfigDialog.currentTypeName] dlg = SortKeyDialog(currentFormat.fieldDict, self) - if dlg.exec_() == QtGui.QDialog.Accepted: + if dlg.exec_() == QDialog.Accepted: self.updateContent() self.mainDialogRef.setModified() @@ -917,91 +918,97 @@ super().__init__(parent) self.currentFileInfoField = '' - topLayout = QtGui.QGridLayout(self) - typeBox = QtGui.QGroupBox(_('&Data Type')) + topLayout = QGridLayout(self) + typeBox = QGroupBox(_('&Data Type')) topLayout.addWidget(typeBox, 0, 0) - typeLayout = QtGui.QVBoxLayout(typeBox) - self.typeCombo = QtGui.QComboBox() + typeLayout = QVBoxLayout(typeBox) + self.typeCombo = QComboBox() typeLayout.addWidget(self.typeCombo) self.typeCombo.currentIndexChanged[str].connect(self.changeCurrentType) - fieldBox = QtGui.QGroupBox(_('F&ield')) + fieldBox = QGroupBox(_('F&ield')) topLayout.addWidget(fieldBox, 0, 1) - fieldLayout = QtGui.QVBoxLayout(fieldBox) - self.fieldCombo = QtGui.QComboBox() + fieldLayout = QVBoxLayout(fieldBox) + self.fieldCombo = QComboBox() fieldLayout.addWidget(self.fieldCombo) self.fieldCombo.currentIndexChanged[str].connect(self. changeCurrentField) - fieldTypeBox = QtGui.QGroupBox(_('&Field Type')) + fieldTypeBox = QGroupBox(_('&Field Type')) topLayout.addWidget(fieldTypeBox, 1, 0) - fieldTypeLayout = QtGui.QVBoxLayout(fieldTypeBox) - self.fieldTypeCombo = QtGui.QComboBox() + fieldTypeLayout = QVBoxLayout(fieldTypeBox) + self.fieldTypeCombo = QComboBox() fieldTypeLayout.addWidget(self.fieldTypeCombo) - self.fieldTypeCombo.addItems([_(name) for name in - fieldformat.fieldTypes]) + self.fieldTypeCombo.addItems(fieldformat.translatedFieldTypes) self.fieldTypeCombo.currentIndexChanged.connect(self.changeFieldType) - self.formatBox = QtGui.QGroupBox(_('Outpu&t Format')) + self.formatBox = QGroupBox(_('Outpu&t Format')) topLayout.addWidget(self.formatBox, 1, 1) - formatLayout = QtGui.QHBoxLayout(self.formatBox) - self.formatEdit = QtGui.QLineEdit() + formatLayout = QHBoxLayout(self.formatBox) + self.formatEdit = QLineEdit() formatLayout.addWidget(self.formatEdit) self.formatEdit.textEdited.connect(self.mainDialogRef.setModified) - self.helpButton = QtGui.QPushButton(_('Format &Help')) + self.helpButton = QPushButton(_('Format &Help')) formatLayout.addWidget(self.helpButton) self.helpButton.clicked.connect(self.formatHelp) - extraBox = QtGui.QGroupBox(_('Extra Text')) + extraBox = QGroupBox(_('Extra Text')) topLayout.addWidget(extraBox, 2, 0, 2, 1) - extraLayout = QtGui.QVBoxLayout(extraBox) + extraLayout = QVBoxLayout(extraBox) extraLayout.setSpacing(0) - prefixLabel = QtGui.QLabel(_('&Prefix')) + prefixLabel = QLabel(_('&Prefix')) extraLayout.addWidget(prefixLabel) - self.prefixEdit = QtGui.QLineEdit() + self.prefixEdit = QLineEdit() extraLayout.addWidget(self.prefixEdit) prefixLabel.setBuddy(self.prefixEdit) self.prefixEdit.textEdited.connect(self.mainDialogRef.setModified) extraLayout.addSpacing(8) - suffixLabel = QtGui.QLabel(_('Suffi&x')) + suffixLabel = QLabel(_('Suffi&x')) extraLayout.addWidget(suffixLabel) - self.suffixEdit = QtGui.QLineEdit() + self.suffixEdit = QLineEdit() extraLayout.addWidget(self.suffixEdit) suffixLabel.setBuddy(self.suffixEdit) self.suffixEdit.textEdited.connect(self.mainDialogRef.setModified) - defaultBox = QtGui.QGroupBox(_('Default &Value for New Nodes')) + defaultBox = QGroupBox(_('Default &Value for New Nodes')) topLayout.addWidget(defaultBox, 2, 1) - defaultLayout = QtGui.QVBoxLayout(defaultBox) - self.defaultCombo = QtGui.QComboBox() + defaultLayout = QVBoxLayout(defaultBox) + self.defaultCombo = QComboBox() defaultLayout.addWidget(self.defaultCombo) self.defaultCombo.setEditable(True) self.defaultCombo.editTextChanged.connect(self.mainDialogRef. setModified) - self.heightBox = QtGui.QGroupBox(_('Editor Height')) + self.heightBox = QGroupBox(_('Editor Height')) topLayout.addWidget(self.heightBox, 3, 1) - heightLayout = QtGui.QHBoxLayout(self.heightBox) - heightLabel = QtGui.QLabel(_('Num&ber of text lines')) + heightLayout = QHBoxLayout(self.heightBox) + heightLabel = QLabel(_('Num&ber of text lines')) heightLayout.addWidget(heightLabel) - self.heightCtrl = QtGui.QSpinBox() + self.heightCtrl = QSpinBox() heightLayout.addWidget(self.heightCtrl) self.heightCtrl.setMinimum(1) self.heightCtrl.setMaximum(999) heightLabel.setBuddy(self.heightCtrl) self.heightCtrl.valueChanged.connect(self.mainDialogRef.setModified) - self.equationBox = QtGui.QGroupBox(_('Math Equation')) + self.equationBox = QGroupBox(_('Math Equation')) topLayout.addWidget(self.equationBox, 4, 0, 1, 2) - equationLayout = QtGui.QHBoxLayout(self.equationBox) - self.equationViewer = QtGui.QLineEdit() + equationLayout = QHBoxLayout(self.equationBox) + self.equationViewer = QLineEdit() equationLayout.addWidget(self.equationViewer) self.equationViewer.setReadOnly(True) - equationButton = QtGui.QPushButton(_('Define Equation')) + equationButton = QPushButton(_('Define Equation')) equationLayout.addWidget(equationButton) equationButton.clicked.connect(self.defineMathEquation) - topLayout.setRowStretch(5, 1) + htmlBox = QGroupBox(_('Output HTML')) + topLayout.addWidget(htmlBox, 5, 0) + htmlLayout = QVBoxLayout(htmlBox) + self.htmlButton = QCheckBox(_('Evaluate &HTML tags')) + htmlLayout.addWidget(self.htmlButton) + self.htmlButton.toggled.connect(self.mainDialogRef.setModified) + + topLayout.setRowStretch(6, 1) def updateContent(self): """Update page contents from current format settings. @@ -1030,12 +1037,12 @@ selectNum = fieldformat.fieldTypes.index(currentField.typeName) self.fieldTypeCombo.setCurrentIndex(selectNum) self.fieldTypeCombo.blockSignals(False) - self.fieldTypeCombo.setEnabled(not self.currentFileInfoField and - not currentFormat.genericType) + # also disable for generic types + self.fieldTypeCombo.setEnabled(not self.currentFileInfoField) self.formatBox.setEnabled(currentField.defaultFormat != '') if (hasattr(currentField, 'resultType') and - currentField.resultType == fieldformat.textResult): + currentField.resultType == fieldformat.MathResult.text): self.formatBox.setEnabled(False) self.formatEdit.setText(currentField.format) @@ -1044,18 +1051,29 @@ self.defaultCombo.blockSignals(True) self.defaultCombo.clear() - self.defaultCombo.addItem(currentField.getEditorInitDefault()) - self.defaultCombo.addItems(currentField.initDefaultChoices()) + initDefault = currentField.getEditorInitDefault() + self.defaultCombo.addItem(initDefault) + initDefaultList = currentField.initDefaultChoices() + if initDefaultList: + if initDefaultList[0] == initDefault: + initDefaultList[0] = '' # don't duplicate first entry + self.defaultCombo.addItems(initDefaultList) self.defaultCombo.setCurrentIndex(0) self.defaultCombo.blockSignals(False) - self.defaultCombo.setEnabled(not self.currentFileInfoField) + self.defaultCombo.setEnabled(currentField.supportsInitDefault and + not self.currentFileInfoField) self.heightCtrl.blockSignals(True) self.heightCtrl.setValue(currentField.numLines) self.heightCtrl.blockSignals(False) self.heightBox.setEnabled(not self.currentFileInfoField and - issubclass(currentField.editorClass, - QtGui.QTextEdit)) + currentField.editorClassName in + ('RichTextEditor', 'HtmlTextEditor', + 'PlainTextEditor')) + self.htmlButton.blockSignals(True) + self.htmlButton.setChecked(currentField.evalHtml) + self.htmlButton.blockSignals(False) + self.htmlButton.setEnabled(not currentField.fixEvalHtmlSetting) if currentField.typeName == 'Math': self.equationBox.show() @@ -1128,7 +1146,7 @@ prevEqnText = currentField.equationText() prevResultType = currentField.resultType dlg = MathEquationDialog(currentFormat, currentField, self) - if (dlg.exec_() == QtGui.QDialog.Accepted and + if (dlg.exec_() == QDialog.Accepted and (currentField.equationText() != prevEqnText or currentField.resultType != prevResultType)): self.mainDialogRef.setModified() @@ -1138,7 +1156,7 @@ """Provide a format help menu based on a button signal. """ currentFormat, currentField = self.currentFormatAndField() - menu = QtGui.QMenu(self) + menu = QMenu(self) self.formatHelpDict = {} for descript, key in currentField.getFormatHelpMenuList(): if descript: @@ -1147,7 +1165,7 @@ else: menu.addSeparator() menu.popup(self.helpButton. - mapToGlobal(QtCore.QPoint(0, self.helpButton.height()))) + mapToGlobal(QPoint(0, self.helpButton.height()))) menu.triggered.connect(self.insertFormat) def insertFormat(self, action): @@ -1162,13 +1180,16 @@ """Make changes to the format for each widget. """ currentFormat, currentField = self.currentFormatAndField() - if self.formatEdit.text() != currentField.format: - try: - currentField.setFormat(self.formatEdit.text()) - if self.currentFileInfoField: - currentFormat.fieldFormatModified = True - except ValueError: - self.formatEdit.setText(currentField.format) + if not currentField.fixEvalHtmlSetting: + currentField.evalHtml = self.htmlButton.isChecked() + prevFormat = currentField.format + try: + currentField.setFormat(self.formatEdit.text()) + if (self.currentFileInfoField and + self.formatEdit.text() != prevFormat): + currentFormat.fieldFormatModified = True + except ValueError: + self.formatEdit.setText(currentField.format) currentField.prefix = self.prefixEdit.text() currentField.suffix = self.suffixEdit.text() if self.currentFileInfoField and (currentField.prefix or @@ -1204,64 +1225,65 @@ self.refLevelFlag = '' self.refLevelType = None - topLayout = QtGui.QGridLayout(self) - typeBox = QtGui.QGroupBox(_('&Data Type')) + topLayout = QGridLayout(self) + typeBox = QGroupBox(_('&Data Type')) topLayout.addWidget(typeBox, 0, 0) - typeLayout = QtGui.QVBoxLayout(typeBox) - self.typeCombo = QtGui.QComboBox() + typeLayout = QVBoxLayout(typeBox) + self.typeCombo = QComboBox() typeLayout.addWidget(self.typeCombo) self.typeCombo.currentIndexChanged[str].connect(self.changeCurrentType) - fieldBox = QtGui.QGroupBox(_('F&ield List')) + fieldBox = QGroupBox(_('F&ield List')) topLayout.addWidget(fieldBox, 1, 0, 2, 1) - boxLayout = QtGui.QVBoxLayout(fieldBox) - self.fieldListBox = QtGui.QTreeWidget() + boxLayout = QVBoxLayout(fieldBox) + self.fieldListBox = QTreeWidget() boxLayout.addWidget(self.fieldListBox) self.fieldListBox.setRootIsDecorated(False) + self.fieldListBox.setSelectionMode(QAbstractItemView.ExtendedSelection) self.fieldListBox.setColumnCount(2) self.fieldListBox.setHeaderLabels([_('Name'), _('Type')]) - self.fieldListBox.currentItemChanged.connect(self.changeField) + self.fieldListBox.itemSelectionChanged.connect(self.changeField) - titleButtonLayout = QtGui.QVBoxLayout() + titleButtonLayout = QVBoxLayout() topLayout.addLayout(titleButtonLayout, 1, 1) - self.toTitleButton = QtGui.QPushButton('>>') + self.toTitleButton = QPushButton('>>') titleButtonLayout.addWidget(self.toTitleButton) self.toTitleButton.setMaximumWidth(self.toTitleButton. sizeHint().height()) self.toTitleButton.clicked.connect(self.fieldToTitle) - self.delTitleButton = QtGui.QPushButton('<<') + self.delTitleButton = QPushButton('<<') titleButtonLayout.addWidget(self.delTitleButton) self.delTitleButton.setMaximumWidth(self.delTitleButton. sizeHint().height()) self.delTitleButton.clicked.connect(self.delTitleField) - titleBox = QtGui.QGroupBox(_('&Title Format')) + titleBox = QGroupBox(_('&Title Format')) topLayout.addWidget(titleBox, 1, 2) - titleLayout = QtGui.QVBoxLayout(titleBox) + titleLayout = QVBoxLayout(titleBox) self.titleEdit = TitleEdit() titleLayout.addWidget(self.titleEdit) self.titleEdit.cursorPositionChanged.connect(self. setControlAvailability) self.titleEdit.textEdited.connect(self.mainDialogRef.setModified) - outputButtonLayout = QtGui.QVBoxLayout() + outputButtonLayout = QVBoxLayout() topLayout.addLayout(outputButtonLayout, 2, 1) - self.toOutputButton = QtGui.QPushButton('>>') + self.toOutputButton = QPushButton('>>') outputButtonLayout.addWidget(self.toOutputButton) self.toOutputButton.setMaximumWidth(self.toOutputButton. sizeHint().height()) self.toOutputButton.clicked.connect(self.fieldToOutput) - self.delOutputButton = QtGui.QPushButton('<<') + self.delOutputButton = QPushButton('<<') outputButtonLayout.addWidget(self.delOutputButton) self.delOutputButton.setMaximumWidth(self.delOutputButton. sizeHint().height()) self.delOutputButton.clicked.connect(self.delOutputField) - outputBox = QtGui.QGroupBox(_('Out&put Format')) + outputBox = QGroupBox(_('Out&put Format')) topLayout.addWidget(outputBox, 2, 2) - outputLayout = QtGui.QVBoxLayout(outputBox) - self.outputEdit = QtGui.QTextEdit() - self.outputEdit.setLineWrapMode(QtGui.QTextEdit.NoWrap) + outputLayout = QVBoxLayout(outputBox) + self.outputEdit = QTextEdit() + self.outputEdit.setLineWrapMode(QTextEdit.NoWrap) outputLayout.addWidget(self.outputEdit) self.outputEdit.setTabChangesFocus(True) self.outputEdit.cursorPositionChanged.connect(self. @@ -1269,26 +1291,26 @@ self.outputEdit.textChanged.connect(self.mainDialogRef.setModified) # advanced widgets - otherBox = QtGui.QGroupBox(_('Other Field References')) + otherBox = QGroupBox(_('Other Field References')) topLayout.addWidget(otherBox, 0, 2) self.advancedWidgets.append(otherBox) - otherLayout = QtGui.QHBoxLayout(otherBox) - levelLayout = QtGui.QVBoxLayout() + otherLayout = QHBoxLayout(otherBox) + levelLayout = QVBoxLayout() otherLayout.addLayout(levelLayout) levelLayout.setSpacing(0) - levelLabel = QtGui.QLabel(_('Reference Le&vel')) + levelLabel = QLabel(_('Reference Le&vel')) levelLayout.addWidget(levelLabel) - levelCombo = QtGui.QComboBox() + levelCombo = QComboBox() levelLayout.addWidget(levelCombo) levelLabel.setBuddy(levelCombo) levelCombo.addItems(_refLevelList) levelCombo.currentIndexChanged.connect(self.changeRefLevel) - refTypeLayout = QtGui.QVBoxLayout() + refTypeLayout = QVBoxLayout() otherLayout.addLayout(refTypeLayout) refTypeLayout.setSpacing(0) - refTypeLabel = QtGui.QLabel(_('Refere&nce Type')) + refTypeLabel = QLabel(_('Refere&nce Type')) refTypeLayout.addWidget(refTypeLabel) - self.refTypeCombo = QtGui.QComboBox() + self.refTypeCombo = QComboBox() refTypeLayout.addWidget(self.refTypeCombo) refTypeLabel.setBuddy(self.refTypeCombo) self.refTypeCombo.currentIndexChanged.connect(self.changeRefType) @@ -1309,15 +1331,14 @@ currentFormat = ConfigDialog.formatsRef[ConfigDialog.currentTypeName] self.updateFieldList() - lines = currentFormat.getLines() self.titleEdit.blockSignals(True) - self.titleEdit.setText(lines[0]) + self.titleEdit.setText(currentFormat.getTitleLine()) self.titleEdit.end(False) self.titleEdit.blockSignals(False) self.outputEdit.blockSignals(True) - self.outputEdit.setPlainText('\n'.join(lines[1:])) + self.outputEdit.setPlainText('\n'.join(currentFormat.getOutputLines())) cursor = self.outputEdit.textCursor() - cursor.movePosition(QtGui.QTextCursor.End) + cursor.movePosition(QTextCursor.End) self.outputEdit.setTextCursor(cursor) self.outputEdit.blockSignals(False) @@ -1348,54 +1369,53 @@ else: try: activeFormat = ConfigDialog.formatsRef[self.refLevelType] - except ValueError: + except (KeyError, ValueError): self.refLevelType = ConfigDialog.currentTypeName activeFormat = currentFormat self.fieldListBox.blockSignals(True) self.fieldListBox.clear() for field in activeFormat.fields(): if field.showInDialog: - QtGui.QTreeWidgetItem(self.fieldListBox, - [field.name, field.typeName]) - if self.refLevelFlag == '!': - QtGui.QTreeWidgetItem(self.fieldListBox, - [nodeformat.uniqueIdFieldName, 'Text']) + typeName = fieldformat.translatedTypeName(field.typeName) + QTreeWidgetItem(self.fieldListBox, [field.name, typeName]) selectList = self.fieldListBox.findItems(ConfigDialog.currentFieldName, - QtCore.Qt.MatchFixedString | - QtCore.Qt.MatchCaseSensitive) + Qt.MatchFixedString | + Qt.MatchCaseSensitive) selectItem = (selectList[0] if selectList else self.fieldListBox.topLevelItem(0)) self.fieldListBox.setCurrentItem(selectItem) - self.fieldListBox.setItemSelected(selectItem, True) + selectItem.setSelected(True) self.fieldListBox.setColumnWidth(0, self.fieldListBox.width() // 2) self.fieldListBox.blockSignals(False) - def changeField(self, currentItem, prevItem): + def changeField(self): """Change the current format field based on a tree widget signal. Not set if a special field ref level is active. - Arguments: - currentItem -- the new current tree widget item - prevItem -- the old current tree widget item """ - if not self.refLevelFlag: - ConfigDialog.currentFieldName = currentItem.text(0) + selectList = self.fieldListBox.selectedItems() + if (not self.refLevelFlag and len(selectList) == 1): + ConfigDialog.currentFieldName = selectList[0].text(0) + self.setControlAvailability() def setControlAvailability(self): """Set controls available based on text cursor movements. """ + fieldsSelected = len(self.fieldListBox.selectedItems()) > 0 cursorInTitleField = self.isCursorInTitleField() - self.toTitleButton.setEnabled(cursorInTitleField == None) + self.toTitleButton.setEnabled(cursorInTitleField == None and + fieldsSelected) self.delTitleButton.setEnabled(cursorInTitleField == True) cursorInOutputField = self.isCursorInOutputField() - self.toOutputButton.setEnabled(cursorInOutputField == None) + self.toOutputButton.setEnabled(cursorInOutputField == None and + fieldsSelected) self.delOutputButton.setEnabled(cursorInOutputField == True) self.refTypeCombo.setEnabled(self.refLevelFlag not in ('', '!', '#')) def fieldToTitle(self): """Add selected field to cursor pos in title editor. """ - self.titleEdit.insert(self.currentFieldSepName()) + self.titleEdit.insert(self.selectedFieldSepNames(' ')) self.titleEdit.setFocus() def delTitleField(self): @@ -1407,7 +1427,7 @@ def fieldToOutput(self): """Add selected field to cursor pos in output editor. """ - self.outputEdit.insertPlainText(self.currentFieldSepName()) + self.outputEdit.insertPlainText(self.selectedFieldSepNames()) self.outputEdit.setFocus() def delOutputField(self): @@ -1416,13 +1436,17 @@ if self.isCursorInOutputField(True): self.outputEdit.insertPlainText('') - def currentFieldSepName(self): - """Return current field name with proper separators. + def selectedFieldSepNames(self, sep='\n'): + """Return selected field name(s) with proper separators. Adjusts for special field ref levels. + Multiple selections result in fields joined with the separator. + Arguments: + sep -- the separator to join multiple fields. """ - return '{{*{0}{1}*}}'.format(self.refLevelFlag, - self.fieldListBox.currentItem().text(0)) + fields = ['{{*{0}{1}*}}'.format(self.refLevelFlag, item.text(0)) for + item in self.fieldListBox.selectedItems()] + return '\n'.join(fields) def isCursorInTitleField(self, selectField=False): """Return True if a field pattern encloses the cursor/selection. @@ -1475,7 +1499,7 @@ if selectField: outputCursor.setPosition(start + blockStart) outputCursor.setPosition(end + blockStart, - QtGui.QTextCursor.KeepAnchor) + QTextCursor.KeepAnchor) self.outputEdit.setTextCursor(outputCursor) return True @@ -1533,10 +1557,10 @@ not currentFormat.formatHtml) -class TitleEdit(QtGui.QLineEdit): +class TitleEdit(QLineEdit): """LineEdit that avoids changing the selection on focus changes. """ - focusIn = QtCore.pyqtSignal(QtGui.QWidget) + focusIn = pyqtSignal(QWidget) def __init__(self, parent=None): """Initialize the config dialog page. @@ -1577,9 +1601,148 @@ self.setSelection(selectStart, cursorPos - selectStart) +class TypeLimitCombo(QComboBox): + """A combo box for selecting limited child types. + """ + limitChanged = pyqtSignal() + def __init__(self, parent=None): + """Initialize the editor class. + + Arguments: + parent -- the parent, if given + """ + super().__init__(parent) + self.checkBoxDialog = None + self.typeNames = [] + self.selectSet = set() + + def updateLists(self, typeNames, selectSet): + """Update control text and store data for popup. + + Arguments: + typeNames -- a list of available type names + selectSet -- a set of seleected type names + """ + self.typeNames = typeNames + self.updateSelects(selectSet) + + def updateSelects(self, selectSet): + """Update control text and store selected items. + + Arguments: + selectSet -- a set of seleected type names + """ + self.selectSet = selectSet + if not selectSet or selectSet == set(self.typeNames): + text = _('[All Types Available]') + self.selectSet = set() + else: + text = ', '.join(sorted(selectSet)) + self.addItem(text) + self.setCurrentText(text) + + def showPopup(self): + """Override to show a popup entry widget in place of a list view. + """ + self.checkBoxDialog = TypeLimitCheckBox(self.typeNames, + self.selectSet, self) + self.checkBoxDialog.setMinimumWidth(self.width()) + self.checkBoxDialog.buttonChanged.connect(self.updateFromButton) + self.checkBoxDialog.show() + pos = self.mapToGlobal(self.rect().bottomRight()) + pos.setX(pos.x() - self.checkBoxDialog.width() + 1) + screenBottom = (QApplication.desktop().screenGeometry(self). + bottom()) + if pos.y() + self.checkBoxDialog.height() > screenBottom: + pos.setY(pos.y() - self.rect().height() - + self.checkBoxDialog.height()) + self.checkBoxDialog.move(pos) + + def hidePopup(self): + """Override to hide the popup entry widget. + """ + if self.checkBoxDialog: + self.checkBoxDialog.hide() + super().hidePopup() + + def updateFromButton(self): + """Update selected items based on a button change. + """ + self.updateSelects(self.checkBoxDialog.selectSet()) + self.limitChanged.emit() + + +class TypeLimitCheckBox(QDialog): + """A popup dialog box for selecting limited child types. + """ + buttonChanged = pyqtSignal() + def __init__(self, textList, selectSet, parent=None): + """Initialize the combination dialog. + + Arguments: + textList -- a list of text choices + selectSet -- a set of choices to preselect + parent -- the parent, if given + """ + super().__init__(parent) + self.setWindowFlags(Qt.Popup) + topLayout = QVBoxLayout(self) + topLayout.setContentsMargins(0, 0, 0, 0) + scrollArea = QScrollArea() + scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + topLayout.addWidget(scrollArea) + innerWidget = QWidget() + innerLayout = QVBoxLayout(innerWidget) + self.buttonGroup = QButtonGroup(self) + self.buttonGroup.setExclusive(False) + self.buttonGroup.buttonClicked.connect(self.buttonChanged) + for text in textList: + button = QCheckBox(text, innerWidget) + if text in selectSet: + button.setChecked(True) + self.buttonGroup.addButton(button) + innerLayout.addWidget(button) + scrollArea.setWidget(innerWidget) + buttons = self.buttonGroup.buttons() + if buttons: + buttons[0].setFocus() + + def selectSet(self): + """Return a set of currently checked text. + """ + result = set() + for button in self.buttonGroup.buttons(): + if button.isChecked(): + result.add(button.text()) + return result + + def selectAll(self): + """Select all of the entries. + """ + for button in self.buttonGroup.buttons(): + button.setChecked(True) + + def selectNone(self): + """Clear all of the selections. + """ + for button in self.buttonGroup.buttons(): + button.setChecked(False) + + def contextMenuEvent(self, event): + """Create a popup context menu. + + Arguments: + event -- the menu even to process + """ + menu = QMenu(self) + menu.addAction(_('&Select All'), self.selectAll) + menu.addAction(_('Select &None'), self.selectNone) + menu.exec_(event.globalPos()) + + _illegalRe = re.compile(r'[^\w_\-.]') -class NameEntryDialog(QtGui.QDialog): +class NameEntryDialog(QDialog): """Dialog to handle user entry of a type or field name. Restricts entry to alpha-numerics, underscores, dashes and periods. @@ -1601,31 +1764,31 @@ if badText: self.badText = badText self.text = '' - self.setWindowFlags(QtCore.Qt.Dialog | QtCore.Qt.WindowTitleHint | - QtCore.Qt.WindowSystemMenuHint) + self.setWindowFlags(Qt.Dialog | Qt.WindowTitleHint | + Qt.WindowCloseButtonHint) self.setWindowTitle(caption) - topLayout = QtGui.QVBoxLayout(self) - label = QtGui.QLabel(labelText) + topLayout = QVBoxLayout(self) + label = QLabel(labelText) topLayout.addWidget(label) - self.entry = QtGui.QLineEdit(defaultText) + self.entry = QLineEdit(defaultText) topLayout.addWidget(self.entry) self.entry.setFocus() self.entry.returnPressed.connect(self.accept) self.extraChecked = False if addCheckBox: - self.extraCheckBox = QtGui.QCheckBox(addCheckBox) + self.extraCheckBox = QCheckBox(addCheckBox) topLayout.addWidget(self.extraCheckBox) else: self.extraCheckBox = None - ctrlLayout = QtGui.QHBoxLayout() + ctrlLayout = QHBoxLayout() topLayout.addLayout(ctrlLayout) ctrlLayout.addStretch() - okButton = QtGui.QPushButton(_('&OK')) + okButton = QPushButton(_('&OK')) ctrlLayout.addWidget(okButton) okButton.clicked.connect(self.accept) - cancelButton = QtGui.QPushButton(_('&Cancel')) + cancelButton = QPushButton(_('&Cancel')) ctrlLayout.addWidget(cancelButton) cancelButton.clicked.connect(self.reject) @@ -1651,10 +1814,10 @@ if self.extraCheckBox: self.extraChecked = self.extraCheckBox.isChecked() return super().accept() - QtGui.QMessageBox.warning(self, 'TreeLine', error) + QMessageBox.warning(self, 'TreeLine', error) -class IconSelectDialog(QtGui.QDialog): +class IconSelectDialog(QDialog): """Dialog for selecting icons for a format type. """ dialogSize = () @@ -1671,29 +1834,28 @@ if (not self.currentIconName or self.currentIconName not in globalref.treeIcons): self.currentIconName = icondict.defaultName - self.setWindowFlags(QtCore.Qt.Dialog | QtCore.Qt.WindowTitleHint | - QtCore.Qt.WindowSystemMenuHint) + self.setWindowFlags(Qt.Dialog | Qt.WindowTitleHint | + Qt.WindowCloseButtonHint) self.setWindowTitle(_('Set Data Type Icon')) - topLayout = QtGui.QVBoxLayout(self) - self.iconView = QtGui.QListWidget() - self.iconView.setViewMode(QtGui.QListView.ListMode) - self.iconView.setMovement(QtGui.QListView.Static) - self.iconView.setResizeMode(QtGui.QListView.Adjust) + topLayout = QVBoxLayout(self) + self.iconView = QListWidget() + self.iconView.setViewMode(QListView.ListMode) + self.iconView.setMovement(QListView.Static) self.iconView.setWrapping(True) - self.iconView.setGridSize(QtCore.QSize(112, 32)) + self.iconView.setGridSize(QSize(112, 32)) topLayout.addWidget(self.iconView) self.iconView.itemDoubleClicked.connect(self.accept) - ctrlLayout = QtGui.QHBoxLayout() + ctrlLayout = QHBoxLayout() topLayout.addLayout(ctrlLayout) ctrlLayout.addStretch() - clearButton = QtGui.QPushButton(_('Clear &Select')) + clearButton = QPushButton(_('Clear &Select')) ctrlLayout.addWidget(clearButton) clearButton.clicked.connect(self.iconView.clearSelection) - okButton = QtGui.QPushButton(_('&OK')) + okButton = QPushButton(_('&OK')) ctrlLayout.addWidget(okButton) okButton.clicked.connect(self.accept) - cancelButton = QtGui.QPushButton(_('&Cancel')) + cancelButton = QPushButton(_('&Cancel')) ctrlLayout.addWidget(cancelButton) cancelButton.clicked.connect(self.reject) if IconSelectDialog.dialogSize: @@ -1710,14 +1872,14 @@ globalref.treeIcons.loadAllIcons() for name, icon in globalref.treeIcons.items(): if icon: - item = QtGui.QListWidgetItem(icon, name, self.iconView) + item = QListWidgetItem(icon, name, self.iconView) if name == self.currentIconName: self.iconView.setCurrentItem(item) self.iconView.sortItems() selectedItem = self.iconView.currentItem() if selectedItem: self.iconView.scrollToItem(selectedItem, - QtGui.QAbstractItemView.PositionAtCenter) + QAbstractItemView.PositionAtCenter) def saveSize(self): """Record dialog size at close. @@ -1745,7 +1907,7 @@ super().reject() -class SortKeyDialog(QtGui.QDialog): +class SortKeyDialog(QDialog): """Dialog for defining sort key fields and directions. """ directionNameDict = {True: _('forward'), False: _('reverse')} @@ -1761,63 +1923,63 @@ super().__init__(parent) self.fieldDict = fieldDict self.numChanges = 0 - self.setWindowFlags(QtCore.Qt.Dialog | QtCore.Qt.WindowTitleHint | - QtCore.Qt.WindowSystemMenuHint) + self.setWindowFlags(Qt.Dialog | Qt.WindowTitleHint | + Qt.WindowCloseButtonHint) self.setWindowTitle(_('Sort Key Fields')) - topLayout = QtGui.QVBoxLayout(self) - horizLayout = QtGui.QHBoxLayout() + topLayout = QVBoxLayout(self) + horizLayout = QHBoxLayout() topLayout.addLayout(horizLayout) - fieldBox = QtGui.QGroupBox(_('Available &Fields')) + fieldBox = QGroupBox(_('Available &Fields')) horizLayout.addWidget(fieldBox) - boxLayout = QtGui.QVBoxLayout(fieldBox) - self.fieldListBox = QtGui.QTreeWidget() + boxLayout = QVBoxLayout(fieldBox) + self.fieldListBox = QTreeWidget() boxLayout.addWidget(self.fieldListBox) self.fieldListBox.setRootIsDecorated(False) self.fieldListBox.setColumnCount(2) self.fieldListBox.setHeaderLabels([_('Name'), _('Type')]) - midButtonLayout = QtGui.QVBoxLayout() + midButtonLayout = QVBoxLayout() horizLayout.addLayout(midButtonLayout) - self.addFieldButton = QtGui.QPushButton('>>') + self.addFieldButton = QPushButton('>>') midButtonLayout.addWidget(self.addFieldButton) self.addFieldButton.setMaximumWidth(self.addFieldButton. sizeHint().height()) self.addFieldButton.clicked.connect(self.addField) - self.removeFieldButton = QtGui.QPushButton('<<') + self.removeFieldButton = QPushButton('<<') midButtonLayout.addWidget(self.removeFieldButton) self.removeFieldButton.setMaximumWidth(self.removeFieldButton. sizeHint().height()) self.removeFieldButton.clicked.connect(self.removeField) - sortBox = QtGui.QGroupBox(_('&Sort Criteria')) + sortBox = QGroupBox(_('&Sort Criteria')) horizLayout.addWidget(sortBox) - boxLayout = QtGui.QVBoxLayout(sortBox) - self.sortListBox = QtGui.QTreeWidget() + boxLayout = QVBoxLayout(sortBox) + self.sortListBox = QTreeWidget() boxLayout.addWidget(self.sortListBox) self.sortListBox.setRootIsDecorated(False) self.sortListBox.setColumnCount(3) self.sortListBox.setHeaderLabels(['#', _('Field'), _('Direction')]) self.sortListBox.currentItemChanged.connect(self.setControlsAvail) - rightButtonLayout = QtGui.QVBoxLayout() + rightButtonLayout = QVBoxLayout() horizLayout.addLayout(rightButtonLayout) - self.upButton = QtGui.QPushButton(_('Move &Up')) + self.upButton = QPushButton(_('Move &Up')) rightButtonLayout.addWidget(self.upButton) self.upButton.clicked.connect(self.moveUp) - self.downButton = QtGui.QPushButton(_('&Move Down')) + self.downButton = QPushButton(_('&Move Down')) rightButtonLayout.addWidget(self.downButton) self.downButton.clicked.connect(self.moveDown) - self.flipButton = QtGui.QPushButton(_('Flip &Direction')) + self.flipButton = QPushButton(_('Flip &Direction')) rightButtonLayout.addWidget(self.flipButton) self.flipButton.clicked.connect(self.flipDirection) - ctrlLayout = QtGui.QHBoxLayout() + ctrlLayout = QHBoxLayout() topLayout.addLayout(ctrlLayout) ctrlLayout.addStretch() - self.okButton = QtGui.QPushButton(_('&OK')) + self.okButton = QPushButton(_('&OK')) ctrlLayout.addWidget(self.okButton) self.okButton.clicked.connect(self.accept) - cancelButton = QtGui.QPushButton(_('&Cancel')) + cancelButton = QPushButton(_('&Cancel')) ctrlLayout.addWidget(cancelButton) cancelButton.clicked.connect(self.reject) self.updateContent() @@ -1833,7 +1995,7 @@ self.fieldListBox.clear() for field in self.fieldDict.values(): if field not in sortFields: - QtGui.QTreeWidgetItem(self.fieldListBox, + QTreeWidgetItem(self.fieldListBox, [field.name, field.typeName]) if self.fieldListBox.topLevelItemCount() > 0: self.fieldListBox.setCurrentItem(self.fieldListBox.topLevelItem(0)) @@ -1843,7 +2005,7 @@ self.sortListBox.clear() for num, field in enumerate(sortFields, 1): sortDir = SortKeyDialog.directionNameDict[field.sortKeyForward] - QtGui.QTreeWidgetItem(self.sortListBox, + QTreeWidgetItem(self.sortListBox, [repr(num), field.name, sortDir]) self.sortListBox.setCurrentItem(self.sortListBox.topLevelItem(0)) self.sortListBox.blockSignals(False) @@ -1877,7 +2039,7 @@ sortNum = self.sortListBox.topLevelItemCount() + 1 sortDir = SortKeyDialog.directionNameDict[field.sortKeyForward] self.sortListBox.blockSignals(True) - sortItem = QtGui.QTreeWidgetItem(self.sortListBox, + sortItem = QTreeWidgetItem(self.sortListBox, [repr(sortNum), fieldName, sortDir]) self.sortListBox.setCurrentItem(sortItem) self.sortListBox.blockSignals(False) @@ -1900,7 +2062,7 @@ fieldList = [field for field in self.fieldDict.values() if field.name not in sortFieldNames] pos = fieldList.index(field) - fieldItem = QtGui.QTreeWidgetItem([fieldName, field.typeName]) + fieldItem = QTreeWidgetItem([fieldName, field.typeName]) self.fieldListBox.insertTopLevelItem(pos, fieldItem) self.setControlsAvail() self.numChanges += 1 @@ -1969,7 +2131,7 @@ _('Root Reference'), _('Child Reference'), _('Child Count')] # _mathRefLevelFlags correspond to _mathRefLevels _mathRefLevelFlags = ['', '*', '$', '&', '#'] -_mathResultTypes = [N_('Numeric Result'), N_('Date Result'), N_('Time Result'), +_mathResultTypes = [N_('Number Result'), N_('Date Result'), N_('Time Result'), N_('Boolean Result'), N_('Text Result')] _operatorTypes = [_('Arithmetic Operators'), _('Comparison Operators'), _('Text Operators')] @@ -2028,7 +2190,7 @@ # _operatorLists correspond to _operatorTypes _operatorLists = [_arithmeticOperators, _comparisonOperators, _textOperators] -class MathEquationDialog(QtGui.QDialog): +class MathEquationDialog(QDialog): """Dialog for defining equations for Math fields. """ def __init__(self, nodeFormat, field, parent=None): @@ -2043,75 +2205,75 @@ self.typeFormats = self.nodeFormat.parentFormats self.field = field self.refLevelFlag = '' - self.setWindowFlags(QtCore.Qt.Dialog | QtCore.Qt.WindowTitleHint | - QtCore.Qt.WindowSystemMenuHint) + self.setWindowFlags(Qt.Dialog | Qt.WindowTitleHint | + Qt.WindowCloseButtonHint) self.setWindowTitle(_('Define Math Field Equation')) - topLayout = QtGui.QGridLayout(self) - fieldBox = QtGui.QGroupBox(_('Field References')) + topLayout = QGridLayout(self) + fieldBox = QGroupBox(_('Field References')) topLayout.addWidget(fieldBox, 0, 0, 2, 1) - fieldLayout = QtGui.QVBoxLayout(fieldBox) - innerLayout = QtGui.QVBoxLayout() + fieldLayout = QVBoxLayout(fieldBox) + innerLayout = QVBoxLayout() innerLayout.setSpacing(0) fieldLayout.addLayout(innerLayout) - levelLabel = QtGui.QLabel(_('Reference &Level')) + levelLabel = QLabel(_('Reference &Level')) innerLayout.addWidget(levelLabel) - levelCombo = QtGui.QComboBox() + levelCombo = QComboBox() innerLayout.addWidget(levelCombo) levelLabel.setBuddy(levelCombo) levelCombo.addItems(_mathRefLevels) levelCombo.currentIndexChanged.connect(self.changeRefLevel) - innerLayout = QtGui.QVBoxLayout() + innerLayout = QVBoxLayout() innerLayout.setSpacing(0) fieldLayout.addLayout(innerLayout) - typeLabel = QtGui.QLabel(_('Reference &Type')) + typeLabel = QLabel(_('Reference &Type')) innerLayout.addWidget(typeLabel) - self.typeCombo = QtGui.QComboBox() + self.typeCombo = QComboBox() innerLayout.addWidget(self.typeCombo) typeLabel.setBuddy(self.typeCombo) self.typeCombo.addItems(self.typeFormats.typeNames()) self.typeCombo.currentIndexChanged.connect(self.updateFieldList) - innerLayout = QtGui.QVBoxLayout() + innerLayout = QVBoxLayout() innerLayout.setSpacing(0) fieldLayout.addLayout(innerLayout) - fieldLabel = QtGui.QLabel(_('Available &Field List')) + fieldLabel = QLabel(_('Available &Field List')) innerLayout.addWidget(fieldLabel) - self.fieldListBox = QtGui.QTreeWidget() + self.fieldListBox = QTreeWidget() innerLayout.addWidget(self.fieldListBox) fieldLabel.setBuddy(self.fieldListBox) self.fieldListBox.setRootIsDecorated(False) self.fieldListBox.setColumnCount(2) self.fieldListBox.setHeaderLabels([_('Name'), _('Type')]) - resultTypeBox = QtGui.QGroupBox(_('&Result Type')) + resultTypeBox = QGroupBox(_('&Result Type')) topLayout.addWidget(resultTypeBox, 0, 1) - resultTypeLayout = QtGui.QVBoxLayout(resultTypeBox) - self.resultTypeCombo = QtGui.QComboBox() + resultTypeLayout = QVBoxLayout(resultTypeBox) + self.resultTypeCombo = QComboBox() resultTypeLayout.addWidget(self.resultTypeCombo) self.resultTypeCombo.addItems([_(str) for str in _mathResultTypes]) results = [s.split(' ', 1)[0].lower() for s in _mathResultTypes] - resultStr = fieldformat.mathResultStr[self.field.resultType] + resultStr = self.field.resultType.name self.resultTypeCombo.setCurrentIndex(results.index(resultStr)) - operBox = QtGui.QGroupBox(_('Operations')) + operBox = QGroupBox(_('Operations')) topLayout.addWidget(operBox, 1, 1) - operLayout = QtGui.QVBoxLayout(operBox) - innerLayout = QtGui.QVBoxLayout() + operLayout = QVBoxLayout(operBox) + innerLayout = QVBoxLayout() innerLayout.setSpacing(0) operLayout.addLayout(innerLayout) - operTypeLabel = QtGui.QLabel(_('O&perator Type')) + operTypeLabel = QLabel(_('O&perator Type')) innerLayout.addWidget(operTypeLabel) - operTypeCombo = QtGui.QComboBox() + operTypeCombo = QComboBox() innerLayout.addWidget(operTypeCombo) operTypeLabel.setBuddy(operTypeCombo) operTypeCombo.addItems(_operatorTypes) operTypeCombo.currentIndexChanged.connect(self.replaceOperatorList) - innerLayout = QtGui.QVBoxLayout() + innerLayout = QVBoxLayout() innerLayout.setSpacing(0) operLayout.addLayout(innerLayout) - operListLabel = QtGui.QLabel(_('Oper&ator List')) + operListLabel = QLabel(_('Oper&ator List')) innerLayout.addWidget(operListLabel) - self.operListBox = QtGui.QTreeWidget() + self.operListBox = QTreeWidget() innerLayout.addWidget(self.operListBox) operListLabel.setBuddy(self.operListBox) self.operListBox.setRootIsDecorated(False) @@ -2119,46 +2281,46 @@ self.operListBox.setHeaderLabels([_('Name'), _('Description')]) self.replaceOperatorList(0) - buttonLayout = QtGui.QHBoxLayout() + buttonLayout = QHBoxLayout() topLayout.addLayout(buttonLayout, 2, 0) buttonLayout.addStretch() - self.addFieldButton = QtGui.QPushButton('\u25bc') + self.addFieldButton = QPushButton('\u25bc') buttonLayout.addWidget(self.addFieldButton) self.addFieldButton.setMaximumWidth(self.addFieldButton. sizeHint().height()) self.addFieldButton.clicked.connect(self.addField) - self.delFieldButton = QtGui.QPushButton('\u25b2') + self.delFieldButton = QPushButton('\u25b2') buttonLayout.addWidget(self.delFieldButton) self.delFieldButton.setMaximumWidth(self.delFieldButton. sizeHint().height()) self.delFieldButton.clicked.connect(self.deleteField) buttonLayout.addStretch() - buttonLayout = QtGui.QHBoxLayout() + buttonLayout = QHBoxLayout() topLayout.addLayout(buttonLayout, 2, 1) - self.addOperButton = QtGui.QPushButton('\u25bc') + self.addOperButton = QPushButton('\u25bc') buttonLayout.addWidget(self.addOperButton) self.addOperButton.setMaximumWidth(self.addOperButton. sizeHint().height()) self.addOperButton.clicked.connect(self.addOperator) - equationBox = QtGui.QGroupBox(_('&Equation')) + equationBox = QGroupBox(_('&Equation')) topLayout.addWidget(equationBox, 3, 0, 1, 2) - equationLayout = QtGui.QVBoxLayout(equationBox) + equationLayout = QVBoxLayout(equationBox) self.equationEdit = TitleEdit() equationLayout.addWidget(self.equationEdit) self.equationEdit.setText(self.field.equationText()) self.equationEdit.cursorPositionChanged.connect(self. setControlAvailability) - ctrlLayout = QtGui.QHBoxLayout() + ctrlLayout = QHBoxLayout() topLayout.addLayout(ctrlLayout, 4, 0, 1, 2) ctrlLayout.addStretch() - okButton = QtGui.QPushButton(_('&OK')) + okButton = QPushButton(_('&OK')) ctrlLayout.addWidget(okButton) okButton.setDefault(True) okButton.clicked.connect(self.accept) - cancelButton = QtGui.QPushButton(_('&Cancel')) + cancelButton = QPushButton(_('&Cancel')) ctrlLayout.addWidget(cancelButton) cancelButton.clicked.connect(self.reject) self.changeRefLevel(0) @@ -2173,15 +2335,14 @@ for field in currentFormat.fields(): if (hasattr(field, 'mathValue') and field.showInDialog and (self.refLevelFlag or field != self.field)): - QtGui.QTreeWidgetItem(self.fieldListBox, - [field.name, _(field.typeName)]) + QTreeWidgetItem(self.fieldListBox, + [field.name, _(field.typeName)]) else: - QtGui.QTreeWidgetItem(self.fieldListBox, - ['Count', 'Number of Children']) + QTreeWidgetItem(self.fieldListBox, ['Count', 'Number of Children']) if self.fieldListBox.topLevelItemCount(): selectItem = self.fieldListBox.topLevelItem(0) self.fieldListBox.setCurrentItem(selectItem) - self.fieldListBox.setItemSelected(selectItem, True) + selectItem.setSelected(True) self.fieldListBox.resizeColumnToContents(0) self.fieldListBox.setColumnWidth(0, self.fieldListBox.columnWidth(0) * 2) @@ -2281,14 +2442,14 @@ """ self.operListBox.clear() for oper, descr in _operatorLists[num]: - QtGui.QTreeWidgetItem(self.operListBox, [oper, descr]) + QTreeWidgetItem(self.operListBox, [oper, descr]) self.operListBox.resizeColumnToContents(0) self.operListBox.setColumnWidth(0, self.operListBox.columnWidth(0) * 1.2) self.operListBox.resizeColumnToContents(1) selectItem = self.operListBox.topLevelItem(0) self.operListBox.setCurrentItem(selectItem) - self.operListBox.setItemSelected(selectItem, True) + selectItem.setSelected(True) def accept(self): """Verify the equation and close the dialog if acceptable. @@ -2299,8 +2460,8 @@ try: eqn.validate() except ValueError as err: - QtGui.QMessageBox.warning(self, 'TreeLine', - _('Equation error: {}').format(err)) + QMessageBox.warning(self, 'TreeLine', + _('Equation error: {}').format(err)) return self.typeFormats.emptiedMathDict.setdefault(self.nodeFormat.name, set()).discard(self.field.name) @@ -2312,5 +2473,5 @@ self.field.equation = None resultStr = (_mathResultTypes[self.resultTypeCombo.currentIndex()]. split(' ', 1)[0].lower()) - self.field.changeResultType(fieldformat.mathResultVar[resultStr]) + self.field.changeResultType(fieldformat.MathResult[resultStr]) super().accept() diff -Nru treeline-2.0.2/source/dataeditors.py treeline-3.0.1/source/dataeditors.py --- treeline-2.0.2/source/dataeditors.py 2015-03-14 18:49:23.000000000 +0000 +++ treeline-3.0.1/source/dataeditors.py 2018-08-16 00:55:25.000000000 +0000 @@ -4,7 +4,7 @@ # dataeditors.py, provides classes for data editors in the data edit view # # TreeLine, an information storage program -# Copyright (C) 2015, Douglas W. Bell +# Copyright (C) 2018, 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 @@ -15,20 +15,37 @@ import xml.sax.saxutils import os.path import sys +import re +import math +import enum +import datetime import subprocess -from PyQt4 import QtCore, QtGui +from PyQt5.QtCore import (QDate, QDateTime, QPoint, QPointF, QRect, QSize, + QTime, Qt, pyqtSignal) +from PyQt5.QtGui import (QBrush, QFont, QFontMetrics, QPainter, QPainterPath, + QPixmap, QPen, QTextCursor, QTextDocument, QValidator) +from PyQt5.QtWidgets import (QAbstractItemView, QAbstractSpinBox, + QAction, QApplication, QButtonGroup, + QCalendarWidget, QCheckBox, QColorDialog, + QComboBox, QDialog, QFileDialog, QHBoxLayout, + QHeaderView, QLabel, QLineEdit, QMenu, + QPushButton, QRadioButton, QScrollArea, + QSizePolicy, QSpinBox, QTextEdit, QTreeWidget, + QTreeWidgetItem, QVBoxLayout, QWidget) import dataeditview import fieldformat import urltools import globalref import optiondefaults +multipleSpaceRegEx = re.compile(r' {2,}') -class PlainTextEditor(QtGui.QTextEdit): + +class PlainTextEditor(QTextEdit): """An editor widget for multi-line plain text fields. """ dragLinkEnabled = False - contentsChanged = QtCore.pyqtSignal(QtGui.QWidget) + contentsChanged = pyqtSignal(QWidget) def __init__(self, parent=None): """Initialize the editor class. @@ -37,7 +54,7 @@ """ super().__init__(parent) self.setAcceptRichText(False) - self.setPalette(QtGui.QApplication.palette()) + self.setPalette(QApplication.palette()) self.setStyleSheet('QTextEdit {border: 2px solid palette(highlight)}') self.setTabChangesFocus(True) self.cursorPositionChanged.connect(self.updateActions) @@ -77,7 +94,7 @@ def resetCursor(self): """Set the cursor to end for tab-focus use. """ - self.moveCursor(QtGui.QTextCursor.End) + self.moveCursor(QTextCursor.End) def signalUpdate(self): """Signal the delegate to update the model based on an editor change. @@ -90,7 +107,7 @@ """ self.allActions['EditCut'].setEnabled(True) self.allActions['EditCopy'].setEnabled(True) - mime = QtGui.QApplication.clipboard().mimeData() + mime = QApplication.clipboard().mimeData() self.allActions['EditPaste'].setEnabled(len(mime.data('text/xml') or mime.data('text/plain')) > 0) @@ -101,7 +118,7 @@ hasSelection = self.textCursor().hasSelection() self.allActions['EditCut'].setEnabled(hasSelection) self.allActions['EditCopy'].setEnabled(hasSelection) - mime = QtGui.QApplication.clipboard().mimeData() + mime = QApplication.clipboard().mimeData() self.allActions['EditPaste'].setEnabled(len(mime.data('text/plain')) > 0) @@ -111,8 +128,8 @@ Arguments: event -- the menu event """ - menu = QtGui.QMenu(self) - menu.addAction(self.allActions['EditSelectAll']) + menu = QMenu(self) + menu.addAction(self.allActions['FormatSelectAll']) menu.addSeparator() menu.addAction(self.allActions['EditCut']) menu.addAction(self.allActions['EditCopy']) @@ -135,7 +152,7 @@ event -- the focus event """ super().focusOutEvent(event) - if event.reason() != QtCore.Qt.PopupFocusReason: + if event.reason() != Qt.PopupFocusReason: self.disableActions() def hideEvent(self, event): @@ -153,7 +170,7 @@ """ htmlFontSizes = ('small', '', 'large', 'x-large', 'xx-large') dragLinkEnabled = True - inLinkSelectMode = QtCore.pyqtSignal(bool) + inLinkSelectMode = pyqtSignal(bool) def __init__(self, parent=None): """Initialize the editor class. @@ -162,18 +179,19 @@ """ super().__init__(parent) self.intLinkDialog = None - self.addedIntLinkFlag = False - self.allActions['EditBoldFont'].triggered.connect(self.setBoldFont) - self.allActions['EditItalicFont'].triggered.connect(self.setItalicFont) - self.allActions['EditUnderlineFont'].triggered.connect(self. + self.nodeRef = None + self.allActions['FormatBoldFont'].triggered.connect(self.setBoldFont) + self.allActions['FormatItalicFont'].triggered.connect(self. + setItalicFont) + self.allActions['FormatUnderlineFont'].triggered.connect(self. setUnderlineFont) - self.allActions['EditFontSize'].parent().triggered.connect(self. + self.allActions['FormatFontSize'].parent().triggered.connect(self. setFontSize) - self.allActions['EditFontSize'].triggered.connect(self. + self.allActions['FormatFontSize'].triggered.connect(self. showFontSizeMenu) - self.allActions['EditFontColor'].triggered.connect(self.setFontColor) - self.allActions['EditExtLink'].triggered.connect(self.setExtLink) - self.allActions['EditIntLink'].triggered.connect(self.setIntLink) + self.allActions['FormatFontColor'].triggered.connect(self.setFontColor) + self.allActions['FormatExtLink'].triggered.connect(self.setExtLink) + self.allActions['FormatIntLink'].triggered.connect(self.setIntLink) def insertTagText(self, prefix, suffix): """Insert given tag text and maintain the original selection. @@ -188,7 +206,7 @@ text = '{0}{1}{2}'.format(prefix, cursor.selectedText(), suffix) self.insertPlainText(text) cursor.setPosition(start + len(prefix)) - cursor.setPosition(end + len(prefix), QtGui.QTextCursor.KeepAnchor) + cursor.setPosition(end + len(prefix), QTextCursor.KeepAnchor) self.setTextCursor(cursor) def setBoldFont(self, checked): @@ -235,7 +253,7 @@ """ try: if self.hasFocus(): - actions = self.allActions['EditFontSize'].parent().actions() + actions = self.allActions['FormatFontSize'].parent().actions() sizeNum = actions.index(action) size = HtmlTextEditor.htmlFontSizes[sizeNum] self.insertTagText(''.format(size), @@ -252,7 +270,7 @@ if self.hasFocus(): charFormat = self.currentCharFormat() oldColor = charFormat.foreground().color() - newColor = QtGui.QColorDialog.getColor(oldColor, self) + newColor = QColorDialog.getColor(oldColor, self) if newColor.isValid(): self.insertTagText(''. format(newColor.name()), '') @@ -269,7 +287,7 @@ if address.startswith('#'): address = name = '' dialog.setFromComponents(address, name) - if dialog.exec_() == QtGui.QDialog.Accepted: + if dialog.exec_() == QDialog.Accepted: self.insertPlainText(dialog.htmlText()) except RuntimeError: pass # avoid calling a deleted C++ editor object @@ -279,16 +297,18 @@ """ try: if self.hasFocus(): - self.intLinkDialog = IntLinkDialog(False, self) + self.intLinkDialog = EmbedIntLinkDialog(self.nodeRef. + treeStructureRef(), + self) address, name = self.selectLink() if address.startswith('#'): address = address.lstrip('#') else: address = '' self.intLinkDialog.setFromComponents(address, name) - self.intLinkDialog.setModal(True) self.intLinkDialog.finished.connect(self.insertInternalLink) self.intLinkDialog.show() + self.inLinkSelectMode.emit(True) except RuntimeError: pass # avoid calling a deleted C++ editor object @@ -298,10 +318,10 @@ Arguments: resultCode -- the result from the dialog (OK or cancel) """ - if resultCode == QtGui.QDialog.Accepted: - self.addedIntLinkFlag = True + if resultCode == QDialog.Accepted: self.insertPlainText(self.intLinkDialog.htmlText()) self.intLinkDialog = None + self.inLinkSelectMode.emit(False) def setLinkFromNode(self, node): """Set the current internal link from a clicked node. @@ -328,7 +348,7 @@ if start < anchor < end or start < position < end: address, name = match.groups() cursor.setPosition(start) - cursor.setPosition(end, QtGui.QTextCursor.KeepAnchor) + cursor.setPosition(end, QTextCursor.KeepAnchor) self.setTextCursor(cursor) return (address, name) return ('', cursor.selectedText()) @@ -347,43 +367,43 @@ """Set format actions to unavailable. """ super().disableActions() - self.allActions['EditBoldFont'].setEnabled(False) - self.allActions['EditItalicFont'].setEnabled(False) - self.allActions['EditUnderlineFont'].setEnabled(False) - self.allActions['EditFontSize'].parent().setEnabled(False) - self.allActions['EditFontColor'].setEnabled(False) - self.allActions['EditExtLink'].setEnabled(False) - self.allActions['EditIntLink'].setEnabled(False) + self.allActions['FormatBoldFont'].setEnabled(False) + self.allActions['FormatItalicFont'].setEnabled(False) + self.allActions['FormatUnderlineFont'].setEnabled(False) + self.allActions['FormatFontSize'].parent().setEnabled(False) + self.allActions['FormatFontColor'].setEnabled(False) + self.allActions['FormatExtLink'].setEnabled(False) + self.allActions['FormatIntLink'].setEnabled(False) def updateActions(self): """Set editor format actions to available and update toggle states. """ super().updateActions() - boldFontAct = self.allActions['EditBoldFont'] + boldFontAct = self.allActions['FormatBoldFont'] boldFontAct.setEnabled(True) boldFontAct.setChecked(False) - italicAct = self.allActions['EditItalicFont'] + italicAct = self.allActions['FormatItalicFont'] italicAct.setEnabled(True) italicAct.setChecked(False) - underlineAct = self.allActions['EditUnderlineFont'] + underlineAct = self.allActions['FormatUnderlineFont'] underlineAct.setEnabled(True) underlineAct.setChecked(False) - fontSizeSubMenu = self.allActions['EditFontSize'].parent() + fontSizeSubMenu = self.allActions['FormatFontSize'].parent() fontSizeSubMenu.setEnabled(True) for action in fontSizeSubMenu.actions(): action.setChecked(False) - self.allActions['EditFontColor'].setEnabled(True) - self.allActions['EditExtLink'].setEnabled(True) - self.allActions['EditIntLink'].setEnabled(True) + self.allActions['FormatFontColor'].setEnabled(True) + self.allActions['FormatExtLink'].setEnabled(True) + self.allActions['FormatIntLink'].setEnabled(True) def showFontSizeMenu(self): """Show a context menu for font size at this edit box. """ if self.hasFocus(): rect = self.rect() - pt = self.mapToGlobal(QtCore.QPoint(rect.center().x(), + pt = self.mapToGlobal(QPoint(rect.center().x(), rect.bottom())) - self.allActions['EditFontSize'].parent().popup(pt) + self.allActions['FormatFontSize'].parent().popup(pt) def contextMenuEvent(self, event): """Override popup menu to add formatting and global actions. @@ -391,18 +411,18 @@ Arguments: event -- the menu event """ - menu = QtGui.QMenu(self) - menu.addAction(self.allActions['EditBoldFont']) - menu.addAction(self.allActions['EditItalicFont']) - menu.addAction(self.allActions['EditUnderlineFont']) + menu = QMenu(self) + menu.addAction(self.allActions['FormatBoldFont']) + menu.addAction(self.allActions['FormatItalicFont']) + menu.addAction(self.allActions['FormatUnderlineFont']) menu.addSeparator() - menu.addMenu(self.allActions['EditFontSize'].parent()) - menu.addAction(self.allActions['EditFontColor']) + menu.addMenu(self.allActions['FormatFontSize'].parent()) + menu.addAction(self.allActions['FormatFontColor']) menu.addSeparator() - menu.addAction(self.allActions['EditExtLink']) - menu.addAction(self.allActions['EditIntLink']) + menu.addAction(self.allActions['FormatExtLink']) + menu.addAction(self.allActions['FormatIntLink']) menu.addSeparator() - menu.addAction(self.allActions['EditSelectAll']) + menu.addAction(self.allActions['FormatSelectAll']) menu.addSeparator() menu.addAction(self.allActions['EditCut']) menu.addAction(self.allActions['EditCopy']) @@ -434,19 +454,19 @@ super().__init__(parent) self.setAcceptRichText(True) if not RichTextEditor.fontPointSizes: - doc = QtGui.QTextDocument() + doc = QTextDocument() doc.setDefaultFont(self.font()) for sizeName in HtmlTextEditor.htmlFontSizes: if sizeName: doc.setHtml('text'. format(sizeName)) - pointSize = (QtGui.QTextCursor(doc).charFormat().font(). + pointSize = (QTextCursor(doc).charFormat().font(). pointSize()) else: pointSize = self.font().pointSize() RichTextEditor.fontPointSizes.append(pointSize) - self.allActions['EditClearFormat'].triggered.connect(self. - setClearFormat) + self.allActions['FormatClearFormat'].triggered.connect(self. + setClearFormat) self.allActions['EditPastePlain'].triggered.connect(self.pastePlain) def setContents(self, text): @@ -476,7 +496,7 @@ text = xml.sax.saxutils.escape(fragIter.fragment().text()) text = text.replace('\u2028', '
\n') charFormat = fragIter.fragment().charFormat() - if charFormat.fontWeight() >= QtGui.QFont.Bold: + if charFormat.fontWeight() >= QFont.Bold: text = '{0}'.format(text) if charFormat.fontItalic(): text = '{0}'.format(text) @@ -515,9 +535,9 @@ try: if self.hasFocus(): if checked: - self.setFontWeight(QtGui.QFont.Bold) + self.setFontWeight(QFont.Bold) else: - self.setFontWeight(QtGui.QFont.Normal) + self.setFontWeight(QFont.Normal) except RuntimeError: pass # avoid calling a deleted C++ editor object @@ -553,7 +573,7 @@ """ try: if self.hasFocus(): - actions = self.allActions['EditFontSize'].parent().actions() + actions = self.allActions['FormatFontSize'].parent().actions() sizeNum = actions.index(action) pointSize = RichTextEditor.fontPointSizes[sizeNum] charFormat = self.currentCharFormat() @@ -571,9 +591,9 @@ if self.hasFocus(): charFormat = self.currentCharFormat() oldColor = charFormat.foreground().color() - newColor = QtGui.QColorDialog.getColor(oldColor, self) + newColor = QColorDialog.getColor(oldColor, self) if newColor.isValid(): - charFormat.setForeground(QtGui.QBrush(newColor)) + charFormat.setForeground(QBrush(newColor)) self.setCurrentCharFormat(charFormat) except RuntimeError: pass # avoid calling a deleted C++ editor object @@ -602,7 +622,7 @@ if address.startswith('#'): address = name = '' dialog.setFromComponents(address, name) - if dialog.exec_() == QtGui.QDialog.Accepted: + if dialog.exec_() == QDialog.Accepted: if self.textCursor().hasSelection(): self.insertHtml(dialog.htmlText()) else: @@ -616,13 +636,13 @@ Arguments: resultCode -- the result from the dialog (OK or cancel) """ - if resultCode == QtGui.QDialog.Accepted: - self.addedIntLinkFlag = True + if resultCode == QDialog.Accepted: if self.textCursor().hasSelection(): self.insertHtml(self.intLinkDialog.htmlText()) else: self.insertHtml(self.intLinkDialog.htmlText() + ' ') self.intLinkDialog = None + self.inLinkSelectMode.emit(False) def selectLink(self): """Select the full link at the cursor, return link data. @@ -635,7 +655,7 @@ if not cursor.hasSelection() and not cursor.charFormat().anchorHref(): return ('', '') selectText = cursor.selection().toPlainText() - anchorCursor = QtGui.QTextCursor(self.document()) + anchorCursor = QTextCursor(self.document()) anchorCursor.setPosition(cursor.anchor()) cursor.clearSelection() if cursor < anchorCursor: @@ -663,7 +683,7 @@ if not name: name = selectText.split('\n')[0] cursor.setPosition(anchorCursor.position()) - cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor) + cursor.setPosition(position, QTextCursor.KeepAnchor) self.setTextCursor(cursor) return (address, name) @@ -682,7 +702,7 @@ def pastePlain(self): """Paste non-formatted text from the clipboard. """ - text = QtGui.QApplication.clipboard().mimeData().text() + text = QApplication.clipboard().mimeData().text() if text and self.hasFocus(): self.insertPlainText(text) @@ -690,26 +710,26 @@ """Set format actions to unavailable. """ super().disableActions() - self.allActions['EditClearFormat'].setEnabled(False) + self.allActions['FormatClearFormat'].setEnabled(False) self.allActions['EditPastePlain'].setEnabled(False) def updateActions(self): """Set editor format actions to available and update toggle states. """ super().updateActions() - self.allActions['EditBoldFont'].setChecked(self.fontWeight() == - QtGui.QFont.Bold) - self.allActions['EditItalicFont'].setChecked(self.fontItalic()) - self.allActions['EditUnderlineFont'].setChecked(self.fontUnderline()) - fontSizeSubMenu = self.allActions['EditFontSize'].parent() + self.allActions['FormatBoldFont'].setChecked(self.fontWeight() == + QFont.Bold) + self.allActions['FormatItalicFont'].setChecked(self.fontItalic()) + self.allActions['FormatUnderlineFont'].setChecked(self.fontUnderline()) + fontSizeSubMenu = self.allActions['FormatFontSize'].parent() pointSize = int(self.fontPointSize()) try: sizeNum = RichTextEditor.fontPointSizes.index(pointSize) except ValueError: sizeNum = 1 # default size fontSizeSubMenu.actions()[sizeNum].setChecked(True) - self.allActions['EditClearFormat'].setEnabled(True) - mime = QtGui.QApplication.clipboard().mimeData() + self.allActions['FormatClearFormat'].setEnabled(True) + mime = QApplication.clipboard().mimeData() self.allActions['EditPastePlain'].setEnabled(len(mime. data('text/plain')) > 0) @@ -720,19 +740,19 @@ Arguments: event -- the menu event """ - menu = QtGui.QMenu(self) - menu.addAction(self.allActions['EditBoldFont']) - menu.addAction(self.allActions['EditItalicFont']) - menu.addAction(self.allActions['EditUnderlineFont']) + menu = QMenu(self) + menu.addAction(self.allActions['FormatBoldFont']) + menu.addAction(self.allActions['FormatItalicFont']) + menu.addAction(self.allActions['FormatUnderlineFont']) menu.addSeparator() - menu.addMenu(self.allActions['EditFontSize'].parent()) - menu.addAction(self.allActions['EditFontColor']) + menu.addMenu(self.allActions['FormatFontSize'].parent()) + menu.addAction(self.allActions['FormatFontColor']) menu.addSeparator() - menu.addAction(self.allActions['EditExtLink']) - menu.addAction(self.allActions['EditIntLink']) + menu.addAction(self.allActions['FormatExtLink']) + menu.addAction(self.allActions['FormatIntLink']) menu.addSeparator() - menu.addAction(self.allActions['EditSelectAll']) - menu.addAction(self.allActions['EditClearFormat']) + menu.addAction(self.allActions['FormatSelectAll']) + menu.addAction(self.allActions['FormatClearFormat']) menu.addSeparator() menu.addAction(self.allActions['EditCut']) menu.addAction(self.allActions['EditCopy']) @@ -746,18 +766,19 @@ Arguments: event -- the mouse event """ - if (event.button() == QtCore.Qt.LeftButton and - event.modifiers() == QtCore.Qt.ControlModifier): + if (event.button() == Qt.LeftButton and + event.modifiers() == Qt.ControlModifier): cursor = self.cursorForPosition(event.pos()) address = cursor.charFormat().anchorHref() if address: if address.startswith('#'): editView = self.parent().parent() - editView.selectModel.selectNodeById(address[1:]) + selectModel = editView.treeView.selectionModel() + selectModel.selectNodeById(address[1:]) else: # check for relative path if urltools.isRelative(address): - defaultPath = (globalref.mainControl. - defaultFilePath(True)) + defaultPath = str(globalref.mainControl. + defaultPathObj(True)) address = urltools.toAbsolute(address, defaultPath) openExtUrl(address) event.accept() @@ -789,7 +810,7 @@ self.blockSignals(True) self.setHtml(text) self.blockSignals(False) - self.moveCursor(QtGui.QTextCursor.End) + self.moveCursor(QTextCursor.End) def keyPressEvent(self, event): """Customize handling of return and control keys. @@ -797,18 +818,18 @@ Arguments: event -- the key press event """ - if event.key() not in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return): + if event.key() not in (Qt.Key_Enter, Qt.Key_Return): super().keyPressEvent(event) -class LineEditor(QtGui.QLineEdit): +class LineEditor(QLineEdit): """An editor widget for unformatted single-line fields. Used both stand-alone and as part of the combo box editor. """ dragLinkEnabled = False - contentsChanged = QtCore.pyqtSignal(QtGui.QWidget) - contextMenuPrep = QtCore.pyqtSignal() + contentsChanged = pyqtSignal(QWidget) + contextMenuPrep = pyqtSignal() def __init__(self, parent=None, subControl=False): """Initialize the editor class. @@ -818,7 +839,7 @@ subcontrol -- true if used inside a combo box (no border or signal) """ super().__init__(parent) - self.setPalette(QtGui.QApplication.palette()) + self.setPalette(QApplication.palette()) self.cursorPositionChanged.connect(self.updateActions) self.selectionChanged.connect(self.updateActions) try: @@ -882,19 +903,19 @@ """ super().paintEvent(event) if self.errorFlag: - painter = QtGui.QPainter(self) - path = QtGui.QPainterPath(QtCore.QPointF(0, 0)) + painter = QPainter(self) + path = QPainterPath(QPointF(0, 0)) path.lineTo(0, 10) path.lineTo(10, 0) path.closeSubpath() - painter.fillPath(path, QtGui.QApplication.palette().highlight()) + painter.fillPath(path, QApplication.palette().highlight()) def disableActions(self): """Reset action availability after focus is lost. """ self.allActions['EditCut'].setEnabled(True) self.allActions['EditCopy'].setEnabled(True) - mime = QtGui.QApplication.clipboard().mimeData() + mime = QApplication.clipboard().mimeData() self.allActions['EditPaste'].setEnabled(len(mime.data('text/xml') or mime.data('text/plain')) > 0) @@ -905,7 +926,7 @@ hasSelection = self.hasSelectedText() self.allActions['EditCut'].setEnabled(hasSelection) self.allActions['EditCopy'].setEnabled(hasSelection) - mime = QtGui.QApplication.clipboard().mimeData() + mime = QApplication.clipboard().mimeData() self.allActions['EditPaste'].setEnabled(len(mime.data('text/plain')) > 0) @@ -916,12 +937,12 @@ event -- the menu event """ self.contextMenuPrep.emit() - menu = QtGui.QMenu(self) + menu = QMenu(self) if self.extraMenuActions: for action in self.extraMenuActions: menu.addAction(action) menu.addSeparator() - menu.addAction(self.allActions['EditSelectAll']) + menu.addAction(self.allActions['FormatSelectAll']) menu.addSeparator() menu.addAction(self.allActions['EditCut']) menu.addAction(self.allActions['EditCopy']) @@ -935,7 +956,7 @@ event -- the focus event """ super().focusInEvent(event) - if (event.reason() == QtCore.Qt.OtherFocusReason and + if (event.reason() == Qt.OtherFocusReason and self.savedCursorPos != None): self.setCursorPosition(self.savedCursorPos) self.savedCursorPos = None @@ -948,7 +969,7 @@ event -- the focus event """ super().focusOutEvent(event) - if event.reason() != QtCore.Qt.PopupFocusReason: + if event.reason() != Qt.PopupFocusReason: self.disableActions() def hideEvent(self, event): @@ -975,13 +996,13 @@ self.setReadOnly(True) -class ComboEditor(QtGui.QComboBox): +class ComboEditor(QComboBox): """A general combo box editor widget. Uses the LineEditor class to paint the error flag. """ dragLinkEnabled = False - contentsChanged = QtCore.pyqtSignal(QtGui.QWidget) + contentsChanged = pyqtSignal(QWidget) def __init__(self, parent=None): """Initialize the editor class. @@ -990,21 +1011,20 @@ parent -- the parent, if given """ super().__init__(parent) - self.setPalette(QtGui.QApplication.palette()) + self.setPalette(QApplication.palette()) self.setStyleSheet('QComboBox {border: 2px solid palette(highlight)}') self.setEditable(True) self.setLineEdit(LineEditor(self, True)) - self.listView = QtGui.QTreeWidget() + self.listView = QTreeWidget() self.listView.setColumnCount(2) self.listView.header().hide() self.listView.setRootIsDecorated(False) - self.listView.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) - self.listView.header().setResizeMode(QtGui.QHeaderView. - ResizeToContents) + self.listView.setSelectionBehavior(QAbstractItemView.SelectRows) + self.listView.header().setSectionResizeMode(QHeaderView. + ResizeToContents) self.setModel(self.listView.model()) self.setView(self.listView) self.setModelColumn(0) - self.lineEdit().setFont(dataeditview.defaultFont) self.modified = False self.fieldRef = None self.nodeRef = None @@ -1032,8 +1052,8 @@ text = self.currentText() if self.fieldRef.autoAddChoices: self.fieldRef.clearChoices() - for node in self.nodeRef.modelRef.root.descendantGen(): - if node.formatName == self.nodeRef.formatName: + for node in self.nodeRef.treeStructureRef().nodeDict.values(): + if node.formatRef == self.nodeRef.formatRef: self.fieldRef.addChoice(node.data.get(self.fieldRef.name, '')) self.blockSignals(True) @@ -1044,7 +1064,7 @@ else: annotatedChoices = self.fieldRef.annotatedComboChoices(text) for choice, annot in annotatedChoices: - QtGui.QTreeWidgetItem(self.listView, [choice, annot]) + QTreeWidgetItem(self.listView, [choice, annot]) choices = [choice for (choice, annot) in annotatedChoices] try: self.setCurrentIndex(choices.index(text)) @@ -1124,8 +1144,8 @@ """ if self.fieldRef.autoAddChoices: self.fieldRef.clearChoices() - for node in self.nodeRef.modelRef.root.descendantGen(): - if node.formatName == self.nodeRef.formatName: + for node in self.nodeRef.treeStructureRef().nodeDict.values(): + if node.formatRef == self.nodeRef.formatRef: self.fieldRef.addChoice(node.data.get(self.fieldRef.name, '')) selectList = self.fieldRef.comboActiveChoices(self.currentText()) @@ -1136,7 +1156,7 @@ self.checkBoxDialog.show() pos = self.mapToGlobal(self.rect().bottomRight()) pos.setX(pos.x() - self.checkBoxDialog.width() + 1) - screenBottom = (QtGui.QApplication.desktop().screenGeometry(self). + screenBottom = (QApplication.desktop().screenGeometry(self). bottom()) if pos.y() + self.checkBoxDialog.height() > screenBottom: pos.setY(pos.y() - self.rect().height() - @@ -1158,32 +1178,33 @@ selectList())) -class CombinationDialog(QtGui.QDialog): +class CombinationDialog(QDialog): """A popup dialog box for combination and auto-combination fields. """ - buttonChanged = QtCore.pyqtSignal() + buttonChanged = pyqtSignal() def __init__(self, choiceList, selectList, parent=None): """Initialize the combination dialog. Arguments: choiceList -- a list of text choices + selectList -- a lit of choices to preselect parent -- the parent, if given """ super().__init__(parent) - self.setWindowFlags(QtCore.Qt.Popup) - topLayout = QtGui.QVBoxLayout(self) + self.setWindowFlags(Qt.Popup) + topLayout = QVBoxLayout(self) topLayout.setContentsMargins(0, 0, 0, 0) - scrollArea = QtGui.QScrollArea() - scrollArea.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + scrollArea = QScrollArea() + scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) topLayout.addWidget(scrollArea) - innerWidget = QtGui.QWidget() - innerLayout = QtGui.QVBoxLayout(innerWidget) + innerWidget = QWidget() + innerLayout = QVBoxLayout(innerWidget) selected = set(selectList) - self.buttonGroup = QtGui.QButtonGroup(self) + self.buttonGroup = QButtonGroup(self) self.buttonGroup.setExclusive(False) self.buttonGroup.buttonClicked.connect(self.buttonChanged) for text in choiceList: - button = QtGui.QCheckBox(text, innerWidget) + button = QCheckBox(text, innerWidget) if text in selected: button.setChecked(True) self.buttonGroup.addButton(button) @@ -1216,32 +1237,40 @@ """ super().__init__(parent) self.calendar = None - todayAction = QtGui.QAction(_('Today\'s &Date'), self) - todayAction.triggered.connect(self.setToday) - self.lineEdit().extraMenuActions = [todayAction] + nowAction = QAction(_('Today\'s &Date'), self) + nowAction.triggered.connect(self.setNow) + self.lineEdit().extraMenuActions = [nowAction] + + def editorDate(self): + """Return the date (as a QDate) set in the line editor. + + If none or invalid, return an invalid date. + """ + try: + dateStr = self.fieldRef.storedText(self.currentText()) + except ValueError: + return QDate() + return QDate.fromString(dateStr, Qt.ISODate) def showPopup(self): """Override to show a calendar widget in place of a list view. """ if not self.calendar: - self.calendar = QtGui.QCalendarWidget(self) - self.calendar.setWindowFlags(QtCore.Qt.Popup) - weekStart = optiondefaults.daysOfWeek.index(globalref.genOptions. - getValue('WeekStart')) + self.calendar = QCalendarWidget(self) + self.calendar.setWindowFlags(Qt.Popup) + weekStart = optiondefaults.daysOfWeek.index(globalref. + genOptions['WeekStart']) self.calendar.setFirstDayOfWeek(weekStart + 1) - self.calendar.setVerticalHeaderFormat(QtGui.QCalendarWidget. + self.calendar.setVerticalHeaderFormat(QCalendarWidget. NoVerticalHeader) self.calendar.clicked.connect(self.setDate) - editorFormat = globalref.genOptions.getValue('EditDateFormat') - date = QtCore.QDate.fromString(self.currentText(), editorFormat) + date = self.editorDate() if date.isValid(): - if 1900 <= date.year() < 1950 and 'yyyy' not in editorFormat: - date = date.addYears(100) self.calendar.setSelectedDate(date) self.calendar.show() pos = self.mapToGlobal(self.rect().bottomRight()) pos.setX(pos.x() - self.calendar.width()) - screenBottom = (QtGui.QApplication.desktop().screenGeometry(self). + screenBottom = (QApplication.desktop().screenGeometry(self). bottom()) if pos.y() + self.calendar.height() > screenBottom: pos.setY(pos.y() - self.rect().height() - self.calendar.height()) @@ -1260,16 +1289,575 @@ Arguments: date -- the QDate to be set """ - self.setEditText(date.toString(globalref.genOptions. - getValue('EditDateFormat'))) + dateStr = date.toString(Qt.ISODate) + self.setEditText(self.fieldRef.formatEditorText(dateStr)) self.calendar.hide() - def setToday(self): + def setNow(self): """Set to today's date. """ - self.setEditText(QtCore.QDate.currentDate(). - toString(globalref.genOptions. - getValue('EditDateFormat'))) + dateStr = QDate.currentDate().toString(Qt.ISODate) + self.setEditText(self.fieldRef.formatEditorText(dateStr)) + + +class TimeEditor(ComboEditor): + """An editor widget for time fields. + + Adds a clock popup dialog and a "now" right-click menu action. + """ + def __init__(self, parent=None): + """Initialize the editor class. + + Arguments: + parent -- the parent, if given + """ + super().__init__(parent) + self.dialog = None + nowAction = QAction(_('Set to &Now'), self) + nowAction.triggered.connect(self.setNow) + self.lineEdit().extraMenuActions = [nowAction] + + def showPopup(self): + """Override to show a popup entry widget in place of a list view. + """ + if not self.dialog: + self.dialog = TimeDialog(self) + self.dialog.contentsChanged.connect(self.setTime) + self.dialog.show() + pos = self.mapToGlobal(self.rect().bottomRight()) + pos.setX(pos.x() - self.dialog.width() + 1) + screenBottom = QApplication.desktop().screenGeometry(self).bottom() + if pos.y() + self.dialog.height() > screenBottom: + pos.setY(pos.y() - self.rect().height() - self.dialog.height()) + self.dialog.move(pos) + try: + storedText = self.fieldRef.storedText(self.currentText()) + except ValueError: + storedText = '' + if storedText: + self.dialog.setTimeFromText(storedText) + + def hidePopup(self): + """Override to hide the popup entry widget. + """ + if self.dialog: + self.dialog.hide() + super().hidePopup() + + def setTime(self): + """Set the time fom the dialog. + """ + if self.dialog: + timeStr = self.dialog.timeObject().isoformat() + '.000' + self.setEditText(self.fieldRef.formatEditorText(timeStr)) + + def setNow(self): + """Set to the current time. + """ + timeStr = QTime.currentTime().toString('hh:mm:ss.zzz') + self.setEditText(self.fieldRef.formatEditorText(timeStr)) + + +TimeElem = enum.Enum('TimeElem', 'hour minute second') + +class TimeDialog(QDialog): + """A popup clock dialog for time editing. + """ + contentsChanged = pyqtSignal() + def __init__(self, addCalendar=False, parent=None): + """Initialize the dialog widgets. + + Arguments: + parent -- the dialog's parent widget + """ + super().__init__(parent) + self.focusElem = None + self.setWindowFlags(Qt.Popup) + horizLayout = QHBoxLayout(self) + if addCalendar: + self.calendar = QCalendarWidget() + horizLayout.addWidget(self.calendar) + weekStart = optiondefaults.daysOfWeek.index(globalref. + genOptions['WeekStart']) + self.calendar.setFirstDayOfWeek(weekStart + 1) + self.calendar.setVerticalHeaderFormat(QCalendarWidget. + NoVerticalHeader) + self.calendar.clicked.connect(self.contentsChanged) + vertLayout = QVBoxLayout() + horizLayout.addLayout(vertLayout) + upperLayout = QHBoxLayout() + vertLayout.addLayout(upperLayout) + upperLayout.addStretch(0) + self.hourBox = TimeSpinBox(TimeElem.hour, 1, 12, False) + upperLayout.addWidget(self.hourBox) + self.hourBox.valueChanged.connect(self.signalUpdate) + self.hourBox.focusChanged.connect(self.handleFocusChange) + colon = QLabel(':') + upperLayout.addWidget(colon) + self.minuteBox = TimeSpinBox(TimeElem.minute, 0, 59, True) + upperLayout.addWidget(self.minuteBox) + self.minuteBox.valueChanged.connect(self.signalUpdate) + self.minuteBox.focusChanged.connect(self.handleFocusChange) + colon = QLabel(':') + upperLayout.addWidget(colon) + self.secondBox = TimeSpinBox(TimeElem.second, 0, 59, True) + upperLayout.addWidget(self.secondBox) + self.secondBox.valueChanged.connect(self.signalUpdate) + self.secondBox.focusChanged.connect(self.handleFocusChange) + self.amPmBox = AmPmSpinBox() + upperLayout.addSpacing(4) + upperLayout.addWidget(self.amPmBox) + self.amPmBox.valueChanged.connect(self.signalUpdate) + upperLayout.addStretch(0) + lowerLayout = QHBoxLayout() + vertLayout.addLayout(lowerLayout) + self.clock = ClockWidget() + lowerLayout.addWidget(self.clock, Qt.AlignCenter) + self.clock.numClicked.connect(self.setFromClock) + if addCalendar: + self.calendar.setFocus() + self.updateClock() + else: + self.hourBox.setFocus() + self.hourBox.selectAll() + + def setTimeFromText(self, text): + """Set the time dialog from a string. + + Arguments: + text -- the time in ISO format + """ + time = (datetime.datetime. + strptime(text, fieldformat.TimeField.isoFormat).time()) + hour = time.hour if time.hour <= 12 else time.hour - 12 + self.blockSignals(True) + self.hourBox.setValue(hour) + self.minuteBox.setValue(time.minute) + self.secondBox.setValue(time.second) + amPm = 'AM' if time.hour < 12 else 'PM' + self.amPmBox.setValue(amPm) + self.blockSignals(False) + self.updateClock() + + def setDateFromText(self, text): + """Set the date dialog from a string. + + Arguments: + text -- the date in ISO format + """ + date = QDate.fromString(text, Qt.ISODate) + if date.isValid(): + self.calendar.setSelectedDate(date) + + def timeObject(self): + """Return a datetime time object for the current dialog setting. + """ + hour = self.hourBox.value() + if self.amPmBox.value == 'PM': + if hour < 12: + hour += 12 + elif hour == 12: + hour = 0 + return datetime.time(hour, self.minuteBox.value(), + self.secondBox.value()) + + def updateClock(self): + """Update the clock based on the current time and focused widget. + """ + hands = [self.focusElem] if self.focusElem else [TimeElem.hour, + TimeElem.minute, + TimeElem.second] + self.clock.setDisplay(self.timeObject(), hands) + + def handleFocusChange(self, elemType, isFocused): + """Update clock based on focus changes. + + Arguments: + elemType -- the TimeElem of the focus change + isFocused -- True if focus is gained + """ + if isFocused: + if elemType != self.focusElem: + self.focusElem = elemType + self.updateClock() + elif elemType == self.focusElem: + self.focusElem = None + self.updateClock() + + def setFromClock(self, num): + """Set the active spin box value from a clock click. + + Arguments: + num -- the number clicked + """ + spinBox = getattr(self, self.focusElem.name + 'Box') + spinBox.setValue(num) + spinBox.selectAll() + + def signalUpdate(self): + """Signal a time change and update the clock. + """ + self.updateClock() + self.contentsChanged.emit() + + +class TimeSpinBox(QSpinBox): + """A spin box for time values with optional leading zero. + """ + focusChanged = pyqtSignal(TimeElem, bool) + def __init__(self, elemType, minValue, maxValue, leadZero=True, + parent=None): + """Initialize the spin box. + + Arguments: + elemType -- the TimeElem of this box + minValue -- the minimum allowed value + maxValue -- the maximum allowed value + leadZero -- true if a leading zero used with single digit values + parent -- the box's parent widget + """ + self.elemType = elemType + self.leadZero = leadZero + super().__init__(parent) + self.setMinimum(minValue) + self.setMaximum(maxValue) + self.setWrapping(True) + self.setAlignment(Qt.AlignRight) + + def textFromValue(self, value): + """Override to optionally add leading zero. + + Arguments: + value -- the int value to convert + """ + if self.leadZero and value < 10: + return '0' + repr(value) + return repr(value) + + def focusInEvent(self, event): + """Emit a signal when focused. + + Arguments: + event -- the focus event + """ + super().focusInEvent(event) + self.focusChanged.emit(self.elemType, True) + + def focusOutEvent(self, event): + """Emit a signal if focus is lost. + + Arguments: + event -- the focus event + """ + super().focusOutEvent(event) + self.focusChanged.emit(self.elemType, False) + + +class AmPmSpinBox(QAbstractSpinBox): + """A spin box for AM/PM values. + """ + valueChanged = pyqtSignal() + def __init__(self, parent=None): + """Initialize the spin box. + + Arguments: + parent -- the box's parent widget + """ + super().__init__(parent) + self.value = 'AM' + self.setDisplay() + + def stepBy(self, steps): + """Step the spin box to the alternate value. + + Arguments: + steps -- number of steps (ignored) + """ + self.value = 'PM' if self.value == 'AM' else 'AM' + self.setDisplay() + + def stepEnabled(self): + """Return enabled to show that stepping is always enabled. + """ + return (QAbstractSpinBox.StepUpEnabled | + QAbstractSpinBox.StepDownEnabled) + + def setValue(self, value): + """Set to text value if valid. + + Arguments: + value -- the text value to set + """ + if value in ('AM', 'PM'): + self.value = value + self.setDisplay() + + def setDisplay(self): + """Update display to match value. + """ + self.lineEdit().setText(self.value) + self.valueChanged.emit() + if self.hasFocus(): + self.selectAll() + + def validate(self, inputStr, pos): + """Check if the input string is acceptable. + + Arguments: + inputStr -- the string to check + pos -- the pos in the string (ignored) + """ + inputStr = inputStr.upper() + if inputStr in ('AM', 'A'): + self.value = 'AM' + self.setDisplay() + return (QValidator.Acceptable, 'AM', 2) + if inputStr in ('PM', 'P'): + self.value = 'PM' + self.setDisplay() + return (QValidator.Acceptable, 'PM', 2) + return (QValidator.Invalid, 'xx', 2) + + def sizeHint(self): + """Set prefered size. + """ + return super().sizeHint() + QSize(QFontMetrics(self.font()). + width('AM'), 0) + + def focusInEvent(self, event): + """Set select all when focused. + + Arguments: + event -- the focus event + """ + super().focusInEvent(event) + self.selectAll() + + def focusOutEvent(self, event): + """Remove selection if focus is lost. + + Arguments: + event -- the focus event + """ + super().focusOutEvent(event) + self.lineEdit().deselect() + + +class ClockWidget(QWidget): + """A widget showing a clickable clock face. + """ + radius = 80 + margin = 10 + handLengths = {TimeElem.hour: int(radius * 0.5), + TimeElem.minute: int(radius * 0.9), + TimeElem.second: int(radius * 0.95)} + handWidths = {TimeElem.hour: 7, TimeElem.minute: 5, TimeElem.second: 2} + divisor = {TimeElem.hour: 120, TimeElem.minute: 10, TimeElem.second: 1 / 6} + numClicked = pyqtSignal(int) + def __init__(self, parent=None): + """Initialize the clock. + + Arguments: + parent -- the dialog's parent widget + """ + super().__init__(parent) + self.time = datetime.time() + self.hands = [] + self.highlightAngle = None + self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + self.setMouseTracking(True) + + def setDisplay(self, time, hands): + """Set the clock display. + + Arguments: + time -- a datetime time value + hands -- a list of TimeElem clock hands to show + """ + self.time = time + self.hands = hands + self.highlightAngle = None + self.update() + + def paintEvent(self, event): + """Paint the clock face. + + Arguments: + event -- the paint event + """ + painter = QPainter(self) + painter.save() + painter.setBrush(QApplication.palette().base()) + painter.setPen(Qt.NoPen) + painter.drawEllipse(self.rect()) + painter.translate(ClockWidget.radius + ClockWidget.margin, + ClockWidget.radius + ClockWidget.margin) + for timeElem in self.hands: + painter.save() + painter.setBrush(QApplication.palette().windowText()) + painter.setPen(Qt.NoPen) + seconds = (self.time.hour * 3600 + self.time.minute * 60 + + self.time.second) + angle = seconds / ClockWidget.divisor[timeElem] % 360 + if len(self.hands) == 1: + painter.setBrush(QApplication.palette().highlight()) + if self.hands[0] == TimeElem.hour: + angle = int(angle // 30 * 30) # truncate to whole hour + else: + angle = int(angle // 6 * 6) # truncate to whole min/sec + painter.rotate(angle) + points = (QPoint(0, -ClockWidget.handLengths[timeElem]), + QPoint(ClockWidget.handWidths[timeElem], 8), + QPoint(-ClockWidget.handWidths[timeElem], 8)) + painter.drawConvexPolygon(*points) + painter.restore() + rect = QRect(0, 0, 20, 20) + if len(self.hands) != 1 or self.hands[0] == TimeElem.hour: + labels = [repr(num) for num in range(1, 13)] + else: + labels = ['{0:0>2}'.format(num) for num in range(5, 56, 5)] + labels.append('00') + for ang in range(30, 361, 30): + rect.moveCenter(self.pointOnRadius(ang)) + painter.setPen(QPen()) + if len(self.hands) == 1 and (ang == angle or + ang == self.highlightAngle): + painter.setPen(QPen(QApplication.palette().highlight(), 1)) + painter.drawText(rect, Qt.AlignCenter, labels.pop(0)) + painter.restore() + super().paintEvent(event) + + def sizeHint(self): + """Set prefered size. + """ + width = (ClockWidget.radius + ClockWidget.margin) * 2 + return QSize(width, width) + + def pointOnRadius(self, angle): + """Return a QPoint on the radius at the given angle. + + Arguments: + angle -- the angle in dgrees from vertical (clockwise) + """ + angle = math.radians(angle) + x = round(ClockWidget.radius * math.sin(angle)) + y = 0 - round(ClockWidget.radius * math.cos(angle)) + return QPoint(x, y) + + def pointToPosition(self, point): + """Return a position (1 to 12) based on a screen point. + + Return None if not on a position. + Arguments: + point -- a QPoint screen position + """ + x = point.x() - ClockWidget.radius - ClockWidget.margin + y = point.y() - ClockWidget.radius - ClockWidget.margin + radius = math.sqrt(x**2 + y**2) + if (ClockWidget.radius - 2 * ClockWidget.margin <= radius <= + ClockWidget.radius + 2 * ClockWidget.margin): + angle = math.degrees(math.atan2(-x, y)) + 180 + if angle % 30 <= 10 or angle % 30 >= 20: + pos = round(angle / 30) + if pos == 0: + pos = 12 + return pos + return None + + def mousePressEvent(self, event): + """Signal user clicks on clock numbers if in single hand mode. + + Arguments: + event -- the mouse press event + """ + if len(self.hands) == 1 and event.button() == Qt.LeftButton: + pos = self.pointToPosition(event.pos()) + if pos: + if self.hands[0] != TimeElem.hour: + if pos == 12: + pos = 0 + pos *= 5 + self.numClicked.emit(pos) + super().mousePressEvent(event) + + def mouseMoveEvent(self, event): + """Highlight clickable numbers if in single hand mode. + + Arguments: + event -- the mouse move event + """ + if len(self.hands) == 1: + pos = self.pointToPosition(event.pos()) + if pos: + self.highlightAngle = pos * 30 + self.update() + elif self.highlightAngle != None: + self.highlightAngle = None + self.update() + super().mouseMoveEvent(event) + + +class DateTimeEditor(ComboEditor): + """An editor widget for DateTimeFields. + + Uses a combo box with a clandar widget in place of the list popup. + """ + def __init__(self, parent=None): + """Initialize the editor class. + + Arguments: + parent -- the parent, if given + """ + super().__init__(parent) + self.dialog = None + nowAction = QAction(_('Set to &Now'), self) + nowAction.triggered.connect(self.setNow) + self.lineEdit().extraMenuActions = [nowAction] + + def showPopup(self): + """Override to show a popup entry widget in place of a list view. + """ + if not self.dialog: + self.dialog = TimeDialog(True, self) + self.dialog.contentsChanged.connect(self.setDateTime) + self.dialog.show() + pos = self.mapToGlobal(self.rect().bottomRight()) + pos.setX(pos.x() - self.dialog.width() + 1) + screenBottom = QApplication.desktop().screenGeometry(self).bottom() + if pos.y() + self.dialog.height() > screenBottom: + pos.setY(pos.y() - self.rect().height() - self.dialog.height()) + self.dialog.move(pos) + try: + storedText = self.fieldRef.storedText(self.currentText()) + except ValueError: + storedText = '' + if storedText: + dateText, timeText = storedText.split(' ', 1) + self.dialog.setDateFromText(dateText) + self.dialog.setTimeFromText(timeText) + + def hidePopup(self): + """Override to hide the popup entry widget. + """ + if self.dialog: + self.dialog.hide() + super().hidePopup() + + def setDateTime(self): + """Set the date and time based on a signal from the dialog calendar. + """ + if self.dialog: + dateStr = self.dialog.calendar.selectedDate().toString(Qt.ISODate) + timeStr = self.dialog.timeObject().isoformat() + '.000' + self.setEditText(self.fieldRef.formatEditorText(dateStr + ' ' + + timeStr)) + + def setNow(self): + """Set to the current date and time. + """ + dateTime = QDateTime.currentDateTime() + dateTimeStr = dateTime.toString('yyyy-MM-dd HH:mm:ss.zzz') + self.setEditText(self.fieldRef.formatEditorText(dateTimeStr)) class ExtLinkEditor(ComboEditor): @@ -1287,9 +1875,9 @@ super().__init__(parent) self.setAcceptDrops(True) self.dialog = None - openAction = QtGui.QAction(_('&Open Link'), self) + openAction = QAction(_('&Open Link'), self) openAction.triggered.connect(self.openLink) - folderAction = QtGui.QAction(_('Open &Folder'), self) + folderAction = QAction(_('Open &Folder'), self) folderAction.triggered.connect(self.openFolder) self.lineEdit().extraMenuActions = [openAction, folderAction] self.lineEdit().contextMenuPrep.connect(self.updateActions) @@ -1303,8 +1891,7 @@ self.dialog.show() pos = self.mapToGlobal(self.rect().bottomRight()) pos.setX(pos.x() - self.dialog.width() + 1) - screenBottom = (QtGui.QApplication.desktop().screenGeometry(self). - bottom()) + screenBottom = QApplication.desktop().screenGeometry(self).bottom() if pos.y() + self.dialog.height() > screenBottom: pos.setY(pos.y() - self.rect().height() - self.dialog.height()) self.dialog.move(pos) @@ -1334,8 +1921,8 @@ address = text.strip() if address: if urltools.isRelative(address): - defaultPath = globalref.mainControl.defaultFilePath(True) - address = urltools.toAbsolute(address, defaultPath) + defaultPath = globalref.mainControl.defaultPathObj(True) + address = urltools.toAbsolute(address, str(defaultPath)) openExtUrl(address) def openFolder(self): @@ -1350,8 +1937,8 @@ address = text.strip() if address and urltools.extractScheme(address) in ('', 'file'): if urltools.isRelative(address): - defaultPath = globalref.mainControl.defaultFilePath(True) - address = urltools.toAbsolute(address, defaultPath) + defaultPath = globalref.mainControl.defaultPathObj(True) + address = urltools.toAbsolute(address, str(defaultPath)) address = os.path.dirname(address) openExtUrl(address) @@ -1402,10 +1989,10 @@ _extLinkSchemeDict = {proto.split(':', 1)[0]: proto for proto in _extLinkSchemes} -class ExtLinkDialog(QtGui.QDialog): +class ExtLinkDialog(QDialog): """A popup or normal dialog box for external link editing. """ - contentsChanged = QtCore.pyqtSignal() + contentsChanged = pyqtSignal() def __init__(self, popupDialog=False, parent=None): """Initialize the dialog widgets. @@ -1414,21 +2001,21 @@ parent -- the dialog's parent widget """ super().__init__(parent) - self.setWindowFlags(QtCore.Qt.Dialog | QtCore.Qt.WindowTitleHint | - QtCore.Qt.WindowSystemMenuHint) + self.setWindowFlags(Qt.Dialog | Qt.WindowTitleHint | + Qt.WindowCloseButtonHint) self.setWindowTitle(_('External Link')) - vertLayout = QtGui.QVBoxLayout(self) + vertLayout = QVBoxLayout(self) vertLayout.setSpacing(1) - schemeLabel = QtGui.QLabel(_('Scheme')) + schemeLabel = QLabel(_('Scheme')) vertLayout.addWidget(schemeLabel) - schemeLayout = QtGui.QHBoxLayout() + schemeLayout = QHBoxLayout() vertLayout.addLayout(schemeLayout) schemeLayout.setSpacing(8) - self.schemeButtons = QtGui.QButtonGroup(self) + self.schemeButtons = QButtonGroup(self) self.schemeButtonDict = {} for scheme in _extLinkSchemes: scheme = scheme.split(':', 1)[0] - button = QtGui.QRadioButton(scheme) + button = QRadioButton(scheme) self.schemeButtons.addButton(button) self.schemeButtonDict[scheme] = button schemeLayout.addWidget(button) @@ -1436,51 +2023,51 @@ self.schemeButtons.buttonClicked.connect(self.updateScheme) vertLayout.addSpacing(8) - self.browseButton = QtGui.QPushButton(_('&Browse for File')) + self.browseButton = QPushButton(_('&Browse for File')) self.browseButton.setAutoDefault(False) self.browseButton.clicked.connect(self.fileBrowse) vertLayout.addWidget(self.browseButton) vertLayout.addSpacing(8) - self.pathTypeLabel = QtGui.QLabel(_('File Path Type')) + self.pathTypeLabel = QLabel(_('File Path Type')) vertLayout.addWidget(self.pathTypeLabel) - pathTypeLayout = QtGui.QHBoxLayout() + pathTypeLayout = QHBoxLayout() vertLayout.addLayout(pathTypeLayout) pathTypeLayout.setSpacing(8) - pathTypeButtons = QtGui.QButtonGroup(self) - self.absoluteButton = QtGui.QRadioButton(_('Absolute')) + pathTypeButtons = QButtonGroup(self) + self.absoluteButton = QRadioButton(_('Absolute')) pathTypeButtons.addButton(self.absoluteButton) pathTypeLayout.addWidget(self.absoluteButton) - self.relativeButton = QtGui.QRadioButton(_('Relative')) + self.relativeButton = QRadioButton(_('Relative')) pathTypeButtons.addButton(self.relativeButton) pathTypeLayout.addWidget(self.relativeButton) self.absoluteButton.setChecked(True) pathTypeButtons.buttonClicked.connect(self.updatePathType) vertLayout.addSpacing(8) - addressLabel = QtGui.QLabel(_('Address')) + addressLabel = QLabel(_('Address')) vertLayout.addWidget(addressLabel) - self.addressEdit = QtGui.QLineEdit() + self.addressEdit = QLineEdit() self.addressEdit.textEdited.connect(self.checkAddress) vertLayout.addWidget(self.addressEdit) vertLayout.addSpacing(8) - nameLabel = QtGui.QLabel(_('Display Name')) + nameLabel = QLabel(_('Display Name')) vertLayout.addWidget(nameLabel) - self.nameEdit = QtGui.QLineEdit() + self.nameEdit = QLineEdit() self.nameEdit.textEdited.connect(self.contentsChanged) vertLayout.addWidget(self.nameEdit) if popupDialog: - self.setWindowFlags(QtCore.Qt.Popup) + self.setWindowFlags(Qt.Popup) else: vertLayout.addSpacing(8) - ctrlLayout = QtGui.QHBoxLayout() + ctrlLayout = QHBoxLayout() vertLayout.addLayout(ctrlLayout) ctrlLayout.addStretch(0) - okButton = QtGui.QPushButton(_('&OK')) + okButton = QPushButton(_('&OK')) ctrlLayout.addWidget(okButton) okButton.clicked.connect(self.accept) - cancelButton = QtGui.QPushButton(_('&Cancel')) + cancelButton = QPushButton(_('&Cancel')) ctrlLayout.addWidget(cancelButton) cancelButton.clicked.connect(self.reject) self.addressEdit.setFocus() @@ -1503,22 +2090,6 @@ name = urltools.shortName(address) self.setFromComponents(address, name) - def setFromHtml(self, htmlStr): - """Set the dialog contents from an HTML link. - - Arguments: - htmlStr -- string in HTML link format - """ - name = address = '' - if htmlStr: - linkMatch = fieldformat.linkRegExp.search(htmlStr) - if linkMatch: - address, name = linkMatch.groups() - name = name.strip() - if not name: - name = urltools.shortName(address) - self.setFromComponents(address, name) - def setFromComponents(self, address, name): """Set the dialog contents from separate address and name. @@ -1548,7 +2119,7 @@ return '' name = self.nameEdit.text().strip() if not name: - return address + name = urltools.shortName(address) return '{0} [{1}]'.format(address, name) def htmlText(self): @@ -1602,12 +2173,12 @@ """Update file path based on a change in the absolute/relative control. """ absolute = self.absoluteButton.isChecked() - defaultPath = globalref.mainControl.defaultFilePath(True) + defaultPath = globalref.mainControl.defaultPathObj(True) address = self.addressEdit.text().strip() if absolute: - address = urltools.toAbsolute(address, defaultPath) + address = urltools.toAbsolute(address, str(defaultPath)) else: - address = urltools.toRelative(address, defaultPath) + address = urltools.toRelative(address, str(defaultPath)) self.addressEdit.setText(address) self.contentsChanged.emit() @@ -1625,7 +2196,7 @@ Adjust based on absolute or relative path settings. """ - refPath = globalref.mainControl.defaultFilePath(True) + refPath = str(globalref.mainControl.defaultPathObj(True)) defaultPath = refPath oldAddress = self.addressEdit.text().strip() oldScheme = urltools.extractScheme(oldAddress) @@ -1635,7 +2206,7 @@ oldAddress = urltools.extractAddress(oldAddress) if os.access(oldAddress, os.F_OK): defaultPath = oldAddress - address = QtGui.QFileDialog.getOpenFileName(self, + address, selFltr = QFileDialog.getOpenFileName(self, _('TreeLine - External Link File'), defaultPath, globalref.fileFilters['all']) @@ -1650,9 +2221,9 @@ class IntLinkEditor(ComboEditor): """An editor widget for internal link fields. - Uses a combo box with a link entry box in place of the list popup. + Uses a combo box with a link select dialog in place of the list popup. """ - inLinkSelectMode = QtCore.pyqtSignal(bool) + inLinkSelectMode = pyqtSignal(bool) def __init__(self, parent=None): """Initialize the editor class. @@ -1660,95 +2231,184 @@ parent -- the parent, if given """ super().__init__(parent) + self.address = '' self.intLinkDialog = None - self.addedIntLinkFlag = False - openAction = QtGui.QAction(_('&Go to Target'), self) + self.setLineEdit(PartialLineEditor(self)) + openAction = QAction(_('&Go to Target'), self) openAction.triggered.connect(self.openLink) - self.lineEdit().extraMenuActions = [openAction] + clearAction = QAction(_('Clear &Link'), self) + clearAction.triggered.connect(self.clearLink) + self.lineEdit().extraMenuActions = [openAction, clearAction] + + def setContents(self, text): + """Set the contents of the editor to text. + + Arguments: + text - the new text contents for the editor + """ + super().setContents(text) + if not text: + self.lineEdit().staticLength = 0 + self.address = '' + return + try: + self.address, name = self.fieldRef.addressAndName(self.nodeRef. + data.get(self.fieldRef.name, '')) + except ValueError: + self.address = '' + self.address = self.address.lstrip('#') + nameMatch = fieldformat.linkSeparateNameRegExp.match(text) + if nameMatch: + link = nameMatch.group(1) + self.lineEdit().staticLength = len(link) + 1 + else: + self.lineEdit().staticLength = 0 + + def contents(self): + """Return the editor contents in "address [name]" format. + """ + if not self.address: + return self.currentText() + nameMatch = fieldformat.linkSeparateNameRegExp.match(self. + currentText()) + if nameMatch: + name = nameMatch.group(2) + else: + name = '' + return '{0} [{1}]'.format(self.address, name.strip()) + + def clearLink(self): + """Clear the contents of the editor. + """ + self.setContents('') + self.signalUpdate() def showPopup(self): """Override to show a popup entry widget in place of a list view. """ if not self.intLinkDialog: self.intLinkDialog = IntLinkDialog(True, self) - self.intLinkDialog.contentsChanged.connect(self.setLink) self.intLinkDialog.show() pos = self.mapToGlobal(self.rect().bottomRight()) pos.setX(pos.x() - self.intLinkDialog.width() + 1) - screenBottom = (QtGui.QApplication.desktop().screenGeometry(self). + screenBottom = (QApplication.desktop().screenGeometry(self). bottom()) if pos.y() + self.intLinkDialog.height() > screenBottom: pos.setY(pos.y() - self.rect().height() - self.intLinkDialog.height()) self.intLinkDialog.move(pos) - self.intLinkDialog.setFromEditor(self.currentText()) + self.inLinkSelectMode.emit(True) def hidePopup(self): """Override to hide the popup entry widget. """ if self.intLinkDialog: - if self.intLinkDialog.targetButton.isChecked(): - self.intLinkDialog.toggleTargetClick(False) self.intLinkDialog.hide() + self.inLinkSelectMode.emit(False) super().hidePopup() - def signalUpdate(self): - """Signal the delegate to update the model based on an editor change. - - Also checks for invalid link destination. - """ - self.modified = True - self.lineEdit().errorFlag = False - text = self.currentText() - if text: - nameMatch = fieldformat.linkSeparateNameRegExp.match(text) - if nameMatch: - address = nameMatch.group(1).strip() - else: - address = text.strip() - if address: - self.addedIntLinkFlag = True - editView = self.parent().parent() - if address not in editView.selectModel.model().nodeIdDict: - self.lineEdit().errorFlag = True - self.contentsChanged.emit(self) - - def setLink(self): - """Set the current link from the popup dialog. - """ - self.setEditText(self.intLinkDialog.editorText()) - def setLinkFromNode(self, node): """Set the current link from a clicked node. Arguments: node -- the node to set the unique ID from """ - if self.intLinkDialog: - self.intLinkDialog.setFromNode(node) - self.setLink() + self.hidePopup() + self.address = node.uId + linkTitle = node.title() + nameMatch = fieldformat.linkSeparateNameRegExp.match(self. + currentText()) + if nameMatch: + name = nameMatch.group(2) + else: + name = linkTitle + self.setEditText('LinkTo: {0} [{1}]'.format(linkTitle, name)) + self.lineEdit().staticLength = len(linkTitle) + 9 def openLink(self): """Open the link in a web browser. """ - text = self.currentText() - if text: - nameMatch = fieldformat.linkSeparateNameRegExp.match(text) - if nameMatch: - address = nameMatch.group(1).strip() - if address: - editView = self.parent().parent() - editView.selectModel.selectNodeById(address) + if self.address: + editView = self.parent().parent() + editView.treeView.selectionModel().selectNodeById(self.address) + def setCursorPoint(self, point): + """Set the cursor to the given point. -_targetButtonLabel = {False: _('Enable click-on-&target'), - True: _('Disable click-on-&target')} + Arguments: + point -- the QPoint for the new cursor position + """ + self.lineEdit().setCursorPoint(point) + self.lineEdit().fixSelection() -class IntLinkDialog(QtGui.QDialog): - """A popup or normal dialog box for internal link editing. + +class PartialLineEditor(LineEditor): + """A line used in internal link combo editors. + + Only allows the name portion to be selected or editd. """ - contentsChanged = QtCore.pyqtSignal() - targetClickDialogRef = None + def __init__(self, parent=None): + """Initialize the editor class. + + Arguments: + parent -- the parent, if given + """ + super().__init__(parent, True) + self.staticLength = 0 + + def fixSelection(self): + """Fix the selection and cursor to not include static portion of text. + """ + cursorPos = self.cursorPosition() + if -1 < self.selectionStart() < self.staticLength: + endPos = self.selectionStart() + len(self.selectedText()) + if endPos > self.staticLength: + if cursorPos >= self.staticLength: + self.setSelection(self.staticLength, + endPos - self.staticLength) + else: + # reverse select to get cursor at selection start + self.setSelection(endPos, self.staticLength - endPos) + return + self.deselect() + if cursorPos < self.staticLength: + self.setCursorPosition(self.staticLength) + + def selectAll(self): + """Select all editable text. + """ + self.setSelection(self.staticLength, len(self.text())) + + def mouseReleaseEvent(self, event): + """Fix selection if required after mouse release. + + Arguments: + event -- the mouse release event + """ + super().mouseReleaseEvent(event) + self.fixSelection() + + def keyPressEvent(self, event): + """Avoid edits or cursor movements to the statis portion of the text. + + Arguments: + event -- the mouse release event + """ + if (event.key() == Qt.Key_Backspace and + (self.cursorPosition() <= self.staticLength and + not self.hasSelectedText())): + return + if event.key() in (Qt.Key_Left, Qt.Key_Home): + super().keyPressEvent(event) + self.fixSelection() + return + super().keyPressEvent(event) + + +class IntLinkDialog(QDialog): + """A popup dialog box for internal link editing. + """ + contentsChanged = pyqtSignal() def __init__(self, popupDialog=False, parent=None): """Initialize the dialog widgets. @@ -1757,76 +2417,67 @@ parent -- the dialog's parent widget """ super().__init__(parent) - self.setWindowFlags(QtCore.Qt.Dialog | QtCore.Qt.WindowTitleHint | - QtCore.Qt.WindowSystemMenuHint) + self.setWindowFlags(Qt.Dialog | Qt.FramelessWindowHint) + layout = QVBoxLayout(self) + label = QLabel(_('(Click link target in tree)')) + layout.addWidget(label) + + +class EmbedIntLinkDialog(QDialog): + """A popup or normal dialog box for internal link editing. + """ + contentsChanged = pyqtSignal() + targetClickDialogRef = None + def __init__(self, structRef, parent=None): + """Initialize the dialog widgets. + + Arguments: + structRef -- a ref to the tree structure + parent -- the dialog's parent widget + """ + super().__init__(parent) + self.structRef = structRef + self.address = '' + self.setWindowFlags(Qt.Dialog | Qt.WindowTitleHint | + Qt.WindowCloseButtonHint) self.setWindowTitle(_('Internal Link')) - self.popupDialog = popupDialog - self.targetMode = False - vertLayout = QtGui.QVBoxLayout(self) + vertLayout = QVBoxLayout(self) vertLayout.setSpacing(1) - self.targetButton = QtGui.QPushButton(_targetButtonLabel[False]) - self.targetButton.setCheckable(True) - self.targetButton.clicked.connect(self.toggleTargetClick) - vertLayout.addWidget(self.targetButton) - vertLayout.addSpacing(8) - addressLabel = QtGui.QLabel(_('Unique ID')) - vertLayout.addWidget(addressLabel) - self.addressEdit = QtGui.QLineEdit() - self.addressEdit.textEdited.connect(self.contentsChanged) - vertLayout.addWidget(self.addressEdit) + self.linkLabel = QLabel() + vertLayout.addWidget(self.linkLabel) + infoLabel = QLabel(_('(Click link target in tree)')) + vertLayout.addWidget(infoLabel) vertLayout.addSpacing(8) - nameLabel = QtGui.QLabel(_('Display Name')) + nameLabel = QLabel(_('Display Name')) vertLayout.addWidget(nameLabel) - self.nameEdit = QtGui.QLineEdit() + self.nameEdit = QLineEdit() self.nameEdit.textEdited.connect(self.contentsChanged) vertLayout.addWidget(self.nameEdit) - if self.popupDialog: - self.setWindowFlags(QtCore.Qt.Popup) - else: - vertLayout.addSpacing(8) - ctrlLayout = QtGui.QHBoxLayout() - vertLayout.addLayout(ctrlLayout) - ctrlLayout.addStretch(0) - okButton = QtGui.QPushButton(_('&OK')) - ctrlLayout.addWidget(okButton) - okButton.setDefault(True) - okButton.clicked.connect(self.accept) - cancelButton = QtGui.QPushButton(_('&Cancel')) - ctrlLayout.addWidget(cancelButton) - cancelButton.clicked.connect(self.reject) - - def setFromEditor(self, editorText): - """Set the dialog contents from a string in editor format. - - Arguments: - editorText -- string in "link [name]" format - """ - name = address = '' - if editorText: - nameMatch = fieldformat.linkSeparateNameRegExp.match(editorText) - if nameMatch: - address, name = nameMatch.groups() - else: - name = address = editorText - address = address.strip() - self.setFromComponents(address, name) - - def setFromHtml(self, htmlStr): - """Set the dialog contents from an HTML link. + vertLayout.addSpacing(8) + ctrlLayout = QHBoxLayout() + vertLayout.addLayout(ctrlLayout) + ctrlLayout.addStretch(0) + self.okButton = QPushButton(_('&OK')) + ctrlLayout.addWidget(self.okButton) + self.okButton.setDefault(True) + self.okButton.clicked.connect(self.accept) + cancelButton = QPushButton(_('&Cancel')) + ctrlLayout.addWidget(cancelButton) + cancelButton.clicked.connect(self.reject) - Arguments: - htmlStr -- string in HTML link format + def updateLinkText(self): + """Update the link label using the current address. """ - name = address = '' - if htmlStr: - linkMatch = fieldformat.linkRegExp.search(htmlStr) - if linkMatch: - address, name = linkMatch.groups() - address = address.lstrip('#') - name = name.strip() + title = '' + name = self.nameEdit.text().strip() + if self.address: + targetNode = self.structRef.nodeDict.get(self.address, None) + if targetNode: + title = targetNode.title() if not name: - name = address - self.setFromComponents(address, name) + self.nameEdit.setText(title) + self.linkLabel.setText('LinkTo: {0}'.format(title)) + self.okButton.setEnabled(len(self.address) > 0) def setFromNode(self, node): """Set the dialog contents from a clicked node. @@ -1834,13 +2485,8 @@ Arguments: node -- the node to set the unique ID from """ - if self.targetMode: - address = node.uniqueId - name = self.nameEdit.text().strip() - if not name: - name = node.title() - self.setFromComponents(address, name) - self.toggleTargetClick(False) + self.address = node.uId + self.updateLinkText() def setFromComponents(self, address, name): """Set the dialog contents from separate address and name. @@ -1849,71 +2495,17 @@ address -- the link address, including the protocol prefix name -- the displayed name for the link """ - self.addressEdit.setText(address) + self.address = address self.nameEdit.setText(name) - - def editorText(self): - """Return the dialog contents in data editor format ("link [name]"). - """ - address = self.addressEdit.text().strip() - name = self.nameEdit.text().strip() - if not name: - return address - return '{0} [{1}]'.format(address, name) + self.updateLinkText() def htmlText(self): """Return the dialog contents in HTML link format. """ - address = self.addressEdit.text().strip() name = self.nameEdit.text().strip() if not name: - name = address - return '{1}'.format(address, name) - - def address(self): - """Return the address from the dialog contents. - """ - return self.addressEdit.text().strip() - - def toggleTargetClick(self, enabled): - """Toggle state of clicking on target nodes. - - Arguments: - enabled -- True if clicking enabled - """ - self.targetMode = enabled - self.targetButton.setChecked(enabled) - self.targetButton.setText(_targetButtonLabel[enabled]) - self.addressEdit.setEnabled(not enabled) - if self.popupDialog: - if enabled: - self.setWindowFlags(QtCore.Qt.Dialog | - QtCore.Qt.FramelessWindowHint) - if (IntLinkDialog.targetClickDialogRef and - IntLinkDialog.targetClickDialogRef != self): - IntLinkDialog.targetClickDialogRef.parent().hidePopup() - IntLinkDialog.targetClickDialogRef = self - else: - self.setWindowFlags(QtCore.Qt.Popup) - IntLinkDialog.targetClickDialogRef = None - else: - self.setModal(not enabled) - self.show() - self.parent().inLinkSelectMode.emit(enabled) - - def accept(self): - """Reset target click mode if necessary after the user clicks OK. - """ - if self.targetMode: - self.toggleTargetClick(False) - super().accept() - - def reject(self): - """Reset target click mode if necessary after the user cancels. - """ - if self.targetMode: - self.toggleTargetClick(False) - super().reject() + name = _('link') + return '{1}'.format(self.address, name) class PictureLinkEditor(ComboEditor): @@ -1930,7 +2522,7 @@ """ super().__init__(parent) self.dialog = None - openAction = QtGui.QAction(_('&Open Picture'), self) + openAction = QAction(_('&Open Picture'), self) openAction.triggered.connect(self.openPicture) self.lineEdit().extraMenuActions = [openAction] @@ -1943,7 +2535,7 @@ self.dialog.show() pos = self.mapToGlobal(self.rect().bottomRight()) pos.setX(pos.x() - self.dialog.width() + 1) - screenBottom = (QtGui.QApplication.desktop().screenGeometry(self). + screenBottom = (QApplication.desktop().screenGeometry(self). bottom()) if pos.y() + self.dialog.height() > screenBottom: pos.setY(pos.y() - self.rect().height() - self.dialog.height()) @@ -1968,8 +2560,8 @@ address = self.currentText() if address: if urltools.isRelative(address): - defaultPath = globalref.mainControl.defaultFilePath(True) - address = urltools.toAbsolute(address, defaultPath) + defaultPath = globalref.mainControl.defaultPathObj(True) + address = urltools.toAbsolute(address, str(defaultPath)) openExtUrl(address) def addDroppedUrl(self, urlText): @@ -1981,11 +2573,11 @@ self.setEditText(urlText) -class PictureLinkDialog(QtGui.QDialog): +class PictureLinkDialog(QDialog): """A popup or normal dialog box for picture link editing. """ - thumbnailSize = QtCore.QSize(250, 100) - contentsChanged = QtCore.pyqtSignal() + thumbnailSize = QSize(250, 100) + contentsChanged = pyqtSignal() def __init__(self, popupDialog=False, parent=None): """Initialize the dialog widgets. @@ -1994,59 +2586,59 @@ parent -- the dialog's parent widget """ super().__init__(parent) - self.setWindowFlags(QtCore.Qt.Dialog | QtCore.Qt.WindowTitleHint | - QtCore.Qt.WindowSystemMenuHint) + self.setWindowFlags(Qt.Dialog | Qt.WindowTitleHint | + Qt.WindowCloseButtonHint) self.setWindowTitle(_('Picture Link')) self.setMinimumWidth(self.thumbnailSize.width()) - vertLayout = QtGui.QVBoxLayout(self) + vertLayout = QVBoxLayout(self) vertLayout.setSpacing(1) - self.thumbnail = QtGui.QLabel() - pixmap = QtGui.QPixmap(self.thumbnailSize) + self.thumbnail = QLabel() + pixmap = QPixmap(self.thumbnailSize) pixmap.fill() self.thumbnail.setPixmap(pixmap) - vertLayout.addWidget(self.thumbnail, 0, QtCore.Qt.AlignHCenter) + vertLayout.addWidget(self.thumbnail, 0, Qt.AlignHCenter) vertLayout.addSpacing(8) - self.browseButton = QtGui.QPushButton(_('&Browse for File')) + self.browseButton = QPushButton(_('&Browse for File')) self.browseButton.setAutoDefault(False) self.browseButton.clicked.connect(self.fileBrowse) vertLayout.addWidget(self.browseButton) vertLayout.addSpacing(8) - self.pathTypeLabel = QtGui.QLabel(_('File Path Type')) + self.pathTypeLabel = QLabel(_('File Path Type')) vertLayout.addWidget(self.pathTypeLabel) - pathTypeLayout = QtGui.QHBoxLayout() + pathTypeLayout = QHBoxLayout() vertLayout.addLayout(pathTypeLayout) pathTypeLayout.setSpacing(8) - pathTypeButtons = QtGui.QButtonGroup(self) - self.absoluteButton = QtGui.QRadioButton(_('Absolute')) + pathTypeButtons = QButtonGroup(self) + self.absoluteButton = QRadioButton(_('Absolute')) pathTypeButtons.addButton(self.absoluteButton) pathTypeLayout.addWidget(self.absoluteButton) - self.relativeButton = QtGui.QRadioButton(_('Relative')) + self.relativeButton = QRadioButton(_('Relative')) pathTypeButtons.addButton(self.relativeButton) pathTypeLayout.addWidget(self.relativeButton) self.absoluteButton.setChecked(True) pathTypeButtons.buttonClicked.connect(self.updatePathType) vertLayout.addSpacing(8) - addressLabel = QtGui.QLabel(_('Address')) + addressLabel = QLabel(_('Address')) vertLayout.addWidget(addressLabel) - self.addressEdit = QtGui.QLineEdit() + self.addressEdit = QLineEdit() self.addressEdit.textEdited.connect(self.checkAddress) vertLayout.addWidget(self.addressEdit) vertLayout.addSpacing(8) if popupDialog: - self.setWindowFlags(QtCore.Qt.Popup) + self.setWindowFlags(Qt.Popup) else: vertLayout.addSpacing(8) - ctrlLayout = QtGui.QHBoxLayout() + ctrlLayout = QHBoxLayout() vertLayout.addLayout(ctrlLayout) ctrlLayout.addStretch(0) - okButton = QtGui.QPushButton(_('&OK')) + okButton = QPushButton(_('&OK')) ctrlLayout.addWidget(okButton) okButton.clicked.connect(self.accept) - cancelButton = QtGui.QPushButton(_('&Cancel')) + cancelButton = QPushButton(_('&Cancel')) ctrlLayout.addWidget(cancelButton) cancelButton.clicked.connect(self.reject) self.addressEdit.setFocus() @@ -2104,12 +2696,12 @@ """Update path based on a change in the absolute/relative control. """ absolute = self.absoluteButton.isChecked() - defaultPath = globalref.mainControl.defaultFilePath(True) + defaultPath = globalref.mainControl.defaultPathObj(True) address = self.addressEdit.text().strip() if absolute: - address = urltools.toAbsolute(address, defaultPath, False) + address = urltools.toAbsolute(address, str(defaultPath), False) else: - address = urltools.toRelative(address, defaultPath) + address = urltools.toRelative(address, str(defaultPath)) self.addressEdit.setText(address) self.updateThumbnail() self.contentsChanged.emit() @@ -2119,15 +2711,15 @@ """ address = self.addressEdit.text().strip() if urltools.isRelative(address): - refPath = globalref.mainControl.defaultFilePath(True) + refPath = str(globalref.mainControl.defaultPathObj(True)) address = urltools.toAbsolute(address, refPath, False) - pixmap = QtGui.QPixmap(address) + pixmap = QPixmap(address) if pixmap.isNull(): - pixmap = QtGui.QPixmap(self.thumbnailSize) + pixmap = QPixmap(self.thumbnailSize) pixmap.fill() else: pixmap = pixmap.scaled(self.thumbnailSize, - QtCore.Qt.KeepAspectRatio) + Qt.KeepAspectRatio) self.thumbnail.setPixmap(pixmap) def fileBrowse(self): @@ -2135,7 +2727,7 @@ Adjust based on absolute or relative path settings. """ - refPath = globalref.mainControl.defaultFilePath(True) + refPath = str(globalref.mainControl.defaultPathObj(True)) defaultPath = refPath oldAddress = self.addressEdit.text().strip() if oldAddress: @@ -2144,10 +2736,10 @@ oldAddress = urltools.extractAddress(oldAddress) if os.access(oldAddress, os.F_OK): defaultPath = oldAddress - address = QtGui.QFileDialog.getOpenFileName(self, - _('TreeLine - Picture File'), - defaultPath, - globalref.fileFilters['all']) + address, selFltr = QFileDialog.getOpenFileName(self, + _('TreeLine - Picture File'), + defaultPath, + globalref.fileFilters['all']) if address: if self.relativeButton.isChecked(): address = urltools.toRelative(address, refPath) diff -Nru treeline-2.0.2/source/dataeditview.py treeline-3.0.1/source/dataeditview.py --- treeline-2.0.2/source/dataeditview.py 2015-10-03 18:20:31.000000000 +0000 +++ treeline-3.0.1/source/dataeditview.py 2018-06-08 02:08:26.000000000 +0000 @@ -4,7 +4,7 @@ # dataeditview.py, provides a class for the data edit right-hand view # # TreeLine, an information storage program -# Copyright (C) 2015, Douglas W. Bell +# Copyright (C) 2018, 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 @@ -12,8 +12,13 @@ # but WITTHOUT ANY WARRANTY. See the included LICENSE file for details. #****************************************************************************** -import os.path -from PyQt4 import QtCore, QtGui +from PyQt5.QtCore import QEvent, QPointF, QRectF, QSize, Qt, pyqtSignal +from PyQt5.QtGui import (QKeySequence, QPainterPath, QPalette, QPen, + QSyntaxHighlighter, QTextCharFormat, QTextCursor, + QTextDocument) +from PyQt5.QtWidgets import (QAbstractItemView, QApplication, + QStyledItemDelegate, QTableWidget, + QTableWidgetItem) import treenode import undo import urltools @@ -23,29 +28,28 @@ _minColumnWidth = 80 defaultFont = None -class DataEditCell(QtGui.QTableWidgetItem): +class DataEditCell(QTableWidgetItem): """Class override for data edit view cells. Used for the cells with editable content. """ - def __init__(self, node, field, titleCellRef, typeCellRef, idCellRef=None): + def __init__(self, spot, field, titleCellRef, typeCellRef): """Initialize the editable cells in the data edit view. Arguments: - node -- the node referenced by this cell + spot -- the spot referenced by this cell field -- the field object referenced by this cell titleCellRef -- the title cell to update based on data changes typeCellRef -- the format type cell to update based on type changes - idCellRef -- the unique ID cell to update based on data changes """ super().__init__() - self.node = node + self.spot = spot + self.node = spot.nodeRef self.field = field self.titleCellRef = titleCellRef self.typeCellRef = typeCellRef - self.idCellRef = idCellRef self.errorFlag = False # store doc to speed up delegate sizeHint and paint calls - self.doc = QtGui.QTextDocument() + self.doc = QTextDocument() self.doc.setDefaultFont(defaultFont) self.doc.setDocumentMargin(6) self.updateText() @@ -62,13 +66,13 @@ else: self.setText(self.node.data.get(self.field.name, '')) self.errorFlag = True - if self.field.useRichText: + if self.field.showRichTextInCell: self.doc.setHtml(self.text()) else: self.doc.setPlainText(self.text()) -class DataEditDelegate(QtGui.QStyledItemDelegate): +class DataEditDelegate(QStyledItemDelegate): """Class override for display and editing of DataEditCells. """ def __init__(self, parent=None): @@ -98,20 +102,19 @@ doc = cell.doc doc.setTextWidth(styleOption.rect.width()) painter.translate(styleOption.rect.topLeft()) - paintRect = QtCore.QRectF(0, 0, styleOption.rect.width(), - styleOption.rect.height()) + paintRect = QRectF(0, 0, styleOption.rect.width(), + styleOption.rect.height()) painter.setClipRect(paintRect) - painter.fillRect(paintRect, QtGui.QApplication.palette().base()) - painter.setPen(QtGui.QPen(QtGui.QApplication.palette().text(), 1)) + painter.fillRect(paintRect, QApplication.palette().base()) + painter.setPen(QPen(QApplication.palette().text(), 1)) painter.drawRect(paintRect.adjusted(0, 0, -1, -1)) doc.drawContents(painter) if cell.errorFlag: - path = QtGui.QPainterPath(QtCore.QPointF(0, 0)) + path = QPainterPath(QPointF(0, 0)) path.lineTo(0, 10) path.lineTo(10, 0) path.closeSubpath() - painter.fillPath(path, - QtGui.QApplication.palette().highlight()) + painter.fillPath(path, QApplication.palette().highlight()) painter.restore() else: super().paint(painter, styleOption, modelIndex) @@ -133,13 +136,13 @@ if size.height() > maxHeight: size.setHeight(maxHeight) if cell.field.numLines > 1: - minDoc = QtGui.QTextDocument('\n' * (cell.field.numLines - 1)) - minDoc.setDefaultFont(defaultFont) + minDoc = QTextDocument('\n' * (cell.field.numLines - 1)) + minDoc.setDefaultFont(cell.doc.defaultFont()) minHeight = (minDoc.documentLayout().documentSize().toSize(). height()) if minHeight > size.height(): size.setHeight(minHeight) - return size + QtCore.QSize(0, 4) + return size + QSize(0, 4) return super().sizeHint(styleOption, modelIndex) def createEditor(self, parent, styleOption, modelIndex): @@ -152,7 +155,7 @@ """ cell = self.parent().item(modelIndex.row(), modelIndex.column()) if isinstance(cell, DataEditCell): - editor = cell.field.editorClass(parent) + editor = getattr(dataeditors, cell.field.editorClassName)(parent) editor.setFont(cell.doc.defaultFont()) if hasattr(editor, 'fieldRef'): editor.fieldRef = cell.field @@ -160,14 +163,15 @@ editor.nodeRef = cell.node if cell.errorFlag: editor.setErrorFlag() - self.parent().setFocusProxy(editor) + # self.parent().setFocusProxy(editor) editor.contentsChanged.connect(self.commitData) if hasattr(editor, 'inLinkSelectMode'): - editor.inLinkSelectMode.connect(self.parent().inLinkSelectMode) + editor.inLinkSelectMode.connect(self.parent(). + changeInLinkSelectMode) if hasattr(editor, 'setLinkFromNode'): self.parent().internalLinkSelected.connect(editor. setLinkFromNode) - # viewport filter required to catch mouse events for undo limits + # viewport filter required to catch editor events try: editor.viewport().installEventFilter(self) except AttributeError: @@ -176,6 +180,7 @@ except AttributeError: pass self.lastEditor = editor + editor.setFocus() return editor return super().createEditor(parent, styleOption, modelIndex) @@ -221,23 +226,16 @@ numLines = newText.count('\n') skipUndoAvail = numLines == self.prevNumLines self.prevNumLines = numLines - undo.DataUndo(cell.node.modelRef.undoList, cell.node, + treeStructure = globalref.mainControl.activeControl.structure + undo.DataUndo(treeStructure.undoList, cell.node, False, False, skipUndoAvail, cell.field.name) try: cell.node.setData(cell.field, newText) except ValueError: editor.setErrorFlag() self.parent().nodeModified.emit(cell.node) - cell.titleCellRef.setText(cell.node.title()) - cell.typeCellRef.setText(cell.node.formatName) - linkRefCollect = cell.node.modelRef.linkRefCollect - if (hasattr(editor, 'addedIntLinkFlag') and - (editor.addedIntLinkFlag or - linkRefCollect.linkCount(cell.node, cell.field.name))): - linkRefCollect.searchForLinks(cell.node, cell.field.name) - editor.addedIntLinkFlag = False - if cell.idCellRef: - cell.idCellRef.setText(cell.node.uniqueId) + cell.titleCellRef.setText(cell.node.title(cell.spot)) + cell.typeCellRef.setText(cell.node.formatRef.name) editor.modified = False else: super().setModelData(editor, styleOption, modelIndex) @@ -262,7 +260,7 @@ styleOption -- the data for styles and geometry (not used) modelIndex -- the index of the cell (not used) """ - if event.type() == QtCore.QEvent.MouseButtonPress: + if event.type() == QEvent.MouseButtonPress: self.editorClickPos = event.globalPos() return super().editorEvent(event, model, styleOption, modelIndex) @@ -276,81 +274,79 @@ editor -- the editor that Qt installed a filter on event -- the key press event """ - if event.type() == QtCore.QEvent.KeyPress: + if event.type() == QEvent.KeyPress: view = self.parent() - if (event.key() == QtCore.Qt.Key_Tab and + if (event.key() == Qt.Key_Tab and view.currentRow() == view.rowCount() - 1): view.focusOtherView.emit(True) return True - if event.key() == QtCore.Qt.Key_Backtab: - startRow = 1 - if globalref.genOptions.getValue('ShowUniqueID'): - startRow = 2 - if view.currentRow() == startRow: - view.focusOtherView.emit(False) - return True - if (event.modifiers() == QtCore.Qt.ControlModifier and - QtCore.Qt.Key_A <= event.key() <= QtCore.Qt.Key_Z): - key = QtGui.QKeySequence(event.modifiers() | event.key()) + if (event.key() == Qt.Key_Backtab and view.currentRow() == 1): + view.focusOtherView.emit(False) + return True + if (event.modifiers() == Qt.ControlModifier and + Qt.Key_A <= event.key() <= Qt.Key_Z): + key = QKeySequence(event.modifiers() | event.key()) view.shortcutEntered.emit(key) return True - if event.type() == QtCore.QEvent.MouseButtonPress: - self.prevNumLines = -1 # reset undo avail for mose cursor changes - if event.type() == QtCore.QEvent.FocusOut: + if event.type() == QEvent.MouseButtonPress: + self.prevNumLines = -1 # reset undo avail for mouse cursor changes + if event.type() == QEvent.FocusOut: self.prevNumLines = -1 # reset undo avail for any focus loss - if (event.reason() in (QtCore.Qt.MouseFocusReason, - QtCore.Qt.TabFocusReason, - QtCore.Qt.BacktabFocusReason) and - (not hasattr(editor, 'calendar') or - not editor.calendar or not editor.calendar.isVisible()) and + if (event.reason() in (Qt.MouseFocusReason, Qt.TabFocusReason, + Qt.BacktabFocusReason) + and (not hasattr(editor, 'calendar') or + not editor.calendar or not editor.calendar.isVisible()) and (not hasattr(editor, 'intLinkDialog') or not editor.intLinkDialog or not editor.intLinkDialog.isVisible())): - self.parent().setCurrentCell(-1, -1) + self.parent().endEditor() return True return super().eventFilter(editor, event) -class DataEditView(QtGui.QTableWidget): +class DataEditView(QTableWidget): """Class override for the table-based data edit view. Sets view defaults and updates the content. """ - nodeModified = QtCore.pyqtSignal(treenode.TreeNode) - inLinkSelectMode = QtCore.pyqtSignal(bool) - internalLinkSelected = QtCore.pyqtSignal(treenode.TreeNode) - focusOtherView = QtCore.pyqtSignal(bool) - shortcutEntered = QtCore.pyqtSignal(QtGui.QKeySequence) - def __init__(self, selectModel, allActions, isChildView=True, + nodeModified = pyqtSignal(treenode.TreeNode) + inLinkSelectMode = pyqtSignal(bool) + internalLinkSelected = pyqtSignal(treenode.TreeNode) + focusOtherView = pyqtSignal(bool) + hoverFocusActive = pyqtSignal() + shortcutEntered = pyqtSignal(QKeySequence) + def __init__(self, treeView, allActions, isChildView=True, parent=None): """Initialize the data edit view default view settings. Arguments: - selectModel - the tree view's selection model + treeView - the tree view, needed for the current selection model allActions -- a dict containing actions for the editor context menu isChildView -- shows selected nodes if false, child nodes if true parent -- the parent main window """ super().__init__(0, 2, parent) - self.selectModel = selectModel + self.treeView = treeView self.allActions = allActions self.isChildView = isChildView - self.hideChildView = not globalref.genOptions.getValue('ShowChildPane') + self.hideChildView = not globalref.genOptions['InitShowChildPane'] + self.prevHoverCell = None + self.inLinkSelectActive = False self.setAcceptDrops(True) + self.setMouseTracking(globalref.genOptions['EditorOnHover']) self.horizontalHeader().hide() self.verticalHeader().hide() - self.setVerticalScrollMode(QtGui.QAbstractItemView.ScrollPerPixel) - self.setHorizontalScrollMode(QtGui.QAbstractItemView.ScrollPerPixel) - self.setSelectionMode(QtGui.QAbstractItemView.SingleSelection) + self.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel) + self.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel) + self.setSelectionMode(QAbstractItemView.SingleSelection) self.setItemDelegate(DataEditDelegate(self)) - self.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers) + self.setEditTriggers(QAbstractItemView.NoEditTriggers) self.setShowGrid(False) - self.setTabKeyNavigation(False) pal = self.palette() - pal.setBrush(QtGui.QPalette.Base, - QtGui.QApplication.palette().window()) - pal.setBrush(QtGui.QPalette.Text, - QtGui.QApplication.palette().windowText()) + pal.setBrush(QPalette.Base, + QApplication.palette().window()) + pal.setBrush(QPalette.Text, + QApplication.palette().windowText()) self.setPalette(pal) self.currentItemChanged.connect(self.moveEditor) @@ -359,61 +355,63 @@ Avoids update if view is not visible or has zero height or width. """ - selNodes = self.selectModel.selectedNodes() - if self.isChildView and (len(selNodes) != 1 or self.hideChildView or - not selNodes[0].childList): + selSpots = self.treeView.selectionModel().selectedSpots() + if self.isChildView: + if (len(selSpots) > 1 or self.hideChildView or + (selSpots and not selSpots[0].nodeRef.childList)): + self.hide() + return + if not selSpots: + # use top node childList from tree structure + selSpots = [globalref.mainControl.activeControl.structure. + structSpot()] + elif not selSpots: self.hide() - else: - self.show() + return + self.show() if not self.isVisible() or self.height() == 0 or self.width() == 0: return if self.isChildView: - selNodes = selNodes[0].childList + selSpots = selSpots[0].childSpots() self.clear() - self.clearSpans() - if selNodes: + if selSpots: self.hide() # 2nd update very slow if shown during update self.setRowCount(100000) rowNum = -2 - for node in selNodes: - rowNum = self.addNodeData(node, rowNum + 2) + for spot in selSpots: + rowNum = self.addNodeData(spot, rowNum + 2) self.setRowCount(rowNum + 1) self.adjustSizes() + self.scrollToTop() self.show() - def addNodeData(self, node, startRow): + def addNodeData(self, spot, startRow): """Populate the view with the data from the given node. Returns the last row number used. Arguments: - node -- the node to add + spot -- the spot to add startRow -- the row offset """ - typeCell = self.createInactiveCell(node.formatName) + node = spot.nodeRef + formatName = node.formatRef.name + typeCell = self.createInactiveCell(formatName) self.setItem(startRow, 0, typeCell) - titleCell = self.createInactiveCell(node.title()) + titleCell = self.createInactiveCell(node.title(spot)) self.setItem(startRow, 1, titleCell) - idCell = None - if globalref.genOptions.getValue('ShowUniqueID'): - startRow += 1 - self.setItem(startRow, 0, self.createInactiveCell(_('Unique ID'), - QtCore.Qt.AlignRight | - QtCore.Qt.AlignVCenter)) - idCell = self.createInactiveCell(node.uniqueId) - self.setItem(startRow, 1, idCell) - fields = node.nodeFormat().fields() - if not globalref.genOptions.getValue('EditNumbering'): + fields = node.formatRef.fields() + if not globalref.genOptions['EditNumbering']: fields = [field for field in fields if field.typeName != 'Numbering'] - if not globalref.genOptions.getValue('ShowMath'): + if not globalref.genOptions['ShowMath']: fields = [field for field in fields if field.typeName != 'Math'] for row, field in enumerate(fields, startRow + 1): self.setItem(row, 0, self.createInactiveCell(field.name, - QtCore.Qt.AlignRight | - QtCore.Qt.AlignVCenter)) - self.setItem(row, 1, DataEditCell(node, field, titleCell, typeCell, - idCell)) + Qt.AlignRight | + Qt.AlignVCenter)) + self.setItem(row, 1, DataEditCell(spot, field, titleCell, + typeCell)) self.setItem(row + 1, 0, self.createInactiveCell('')) self.setItem(row + 1, 1, self.createInactiveCell('')) return row @@ -423,14 +421,20 @@ """ if not self.isVisible() or self.height() == 0 or self.width() == 0: return - selNodes = self.selectModel.selectedNodes() + selSpots = self.treeView.selectionModel().selectedSpots() if self.isChildView: - selNodes = selNodes[0].childList + if not selSpots: + # use top node childList from tree structure + selSpots = [globalref.mainControl.activeControl.structure. + structSpot()] + selSpots = selSpots[0].childSpots() + elif not selSpots: + return rowNum = -2 - for node in selNodes: - rowNum = self.refreshNodeData(node, rowNum + 2) + for spot in selSpots: + rowNum = self.refreshNodeData(spot, rowNum + 2) - def refreshNodeData(self, node, startRow): + def refreshNodeData(self, spot, startRow): """Refresh the data in active cells for this node. Returns the last row number used. @@ -438,20 +442,18 @@ node -- the node to add startRow -- the row offset """ - self.item(startRow, 1).setText(node.title()) - if globalref.genOptions.getValue('ShowUniqueID'): - startRow += 1 - self.item(startRow, 1).setText(node.uniqueId) - fields = node.nodeFormat().fields() - if not globalref.genOptions.getValue('EditNumbering'): + node = spot.nodeRef + self.item(startRow, 1).setText(node.title(spot)) + fields = node.formatRef.fields() + if not globalref.genOptions['EditNumbering']: fields = [field for field in fields if field.typeName != 'Numbering'] - if not globalref.genOptions.getValue('ShowMath'): + if not globalref.genOptions['ShowMath']: fields = [field for field in fields if field.typeName != 'Math'] for row, field in enumerate(fields, startRow + 1): cell = self.item(row, 1) - if not cell.isSelected() or not self.hasFocus(): + if not cell.isSelected(): cell.updateText() return row @@ -463,8 +465,8 @@ text -- the initial text string for the cell alignment -- the text alignment QT constant (None for default) """ - cell = QtGui.QTableWidgetItem(text) - cell.setFlags(QtCore.Qt.NoItemFlags) + cell = QTableWidgetItem(text) + cell.setFlags(Qt.NoItemFlags) if alignment: cell.setTextAlignment(alignment) return cell @@ -483,6 +485,11 @@ if newCell: self.openPersistentEditor(newCell) + def endEditor(self): + """End persistent editors by changing active cells. + """ + self.setCurrentCell(-1, -1) + def setFont(self, font): """Override to avoid setting fonts of inactive cells. @@ -492,6 +499,25 @@ global defaultFont defaultFont = font + def changeInLinkSelectMode(self, active=True): + """Change the internal link select mode. + + Changes the internal variable (controlling hover) and signals the tree. + Arguments: + active -- if True, starts the mode, o/w ends + """ + self.inLinkSelectActive = active + self.inLinkSelectMode.emit(active) + + def updateInLinkSelectMode(self, active=True): + """Update the internal link select mode. + + Updates the internal variable (controlling hover). + Arguments: + active -- if True, starts the mode, o/w ends + """ + self.inLinkSelectActive = active + def highlightSearch(self, wordList=None, regExpList=None): """Highlight any found search terms. @@ -499,28 +525,29 @@ wordList -- list of words to highlight regExpList -- a list of regular expression objects to highlight """ - backColor = self.palette().brush(QtGui.QPalette.Active, - QtGui.QPalette.Highlight) - foreColor = self.palette().brush(QtGui.QPalette.Active, - QtGui.QPalette.HighlightedText) - charFormat = QtGui.QTextCharFormat() + backColor = self.palette().brush(QPalette.Active, + QPalette.Highlight) + foreColor = self.palette().brush(QPalette.Active, + QPalette.HighlightedText) + charFormat = QTextCharFormat() charFormat.setBackground(backColor) charFormat.setForeground(foreColor) - node = self.selectModel.selectedNodes()[0] + spot = self.treeView.selectionModel().selectedSpots()[0] if wordList is None: wordList = [] if regExpList is None: regExpList = [] for regExp in regExpList: - for match in regExp.finditer('\n'.join(node.formatOutput())): + for match in regExp.finditer('\n'.join(spot.nodeRef. + output(spotRef=spot))): matchText = match.group().lower() if matchText not in wordList: wordList.append(matchText) cells = [] completedCells = [] for word in wordList: - cells.extend(self.findItems(word, QtCore.Qt.MatchFixedString | - QtCore.Qt.MatchContains)) + cells.extend(self.findItems(word, Qt.MatchFixedString | + Qt.MatchContains)) for cell in cells: if hasattr(cell, 'doc') and cell not in completedCells: highlighter = SearchHighlighter(wordList, charFormat, cell.doc) @@ -536,16 +563,14 @@ cellNum -- the vertical position (field number) of the cell skipMatches -- number of previous matches to skip in this field """ - backColor = self.palette().brush(QtGui.QPalette.Active, - QtGui.QPalette.Highlight) - foreColor = self.palette().brush(QtGui.QPalette.Active, - QtGui.QPalette.HighlightedText) - charFormat = QtGui.QTextCharFormat() + backColor = self.palette().brush(QPalette.Active, + QPalette.Highlight) + foreColor = self.palette().brush(QPalette.Active, + QPalette.HighlightedText) + charFormat = QTextCharFormat() charFormat.setBackground(backColor) charFormat.setForeground(foreColor) cellNum += 1 # skip title line - if globalref.genOptions.getValue('ShowUniqueID'): - cellNum += 1 cell = self.item(cellNum, 1) highlighter = MatchHighlighter(cell.doc, charFormat, searchText, regExpObj, skipMatches) @@ -567,24 +592,19 @@ Arguments: event -- the focus in event """ - if event.reason() == QtCore.Qt.TabFocusReason: + if event.reason() == Qt.TabFocusReason: for row in range(self.rowCount()): cell = self.item(row, 1) if hasattr(cell, 'doc'): self.setCurrentItem(cell) - self.setFocus() - event.accept() - return - elif event.reason() == QtCore.Qt.BacktabFocusReason: + break + elif event.reason() == Qt.BacktabFocusReason: for row in range(self.rowCount() - 1, -1, -1): cell = self.item(row, 1) if hasattr(cell, 'doc'): self.setCurrentItem(cell) - self.setFocus() - event.accept() - return - else: - super().focusInEvent(event) + break + super().focusInEvent(event) def resizeEvent(self, event): """Update view if was collaped by splitter. @@ -612,7 +632,7 @@ """ cell = self.itemAt(event.pos()) if (isinstance(cell, DataEditCell) and - cell.field.editorClass.dragLinkEnabled): + getattr(dataeditors, cell.field.editorClassName).dragLinkEnabled): event.accept() else: event.ignore() @@ -637,36 +657,64 @@ Arguments: event -- the mouse event """ - if (event.button() == QtCore.Qt.LeftButton and - event.modifiers() == QtCore.Qt.ControlModifier): + if (event.button() == Qt.LeftButton and + event.modifiers() == Qt.ControlModifier): cell = self.itemAt(event.pos()) if cell and isinstance(cell, DataEditCell): xOffest = (event.pos().x() - self.columnViewportPosition(cell.column())) yOffset = (event.pos().y() - self.rowViewportPosition(cell.row())) - pt = QtCore.QPointF(xOffest, yOffset) - pos = cell.doc.documentLayout().hitTest(pt, QtCore.Qt.ExactHit) + pt = QPointF(xOffest, yOffset) + pos = cell.doc.documentLayout().hitTest(pt, Qt.ExactHit) if pos >= 0: - cursor = QtGui.QTextCursor(cell.doc) + cursor = QTextCursor(cell.doc) cursor.setPosition(pos) address = cursor.charFormat().anchorHref() if address: if address.startswith('#'): - self.selectModel.selectNodeById(address[1:]) + (self.treeView.selectionModel(). + selectNodeById(address[1:])) else: # check for relative path if urltools.isRelative(address): defaultPath = (globalref.mainControl. - defaultFilePath(True)) + defaultPathObj(True)) address = urltools.toAbsolute(address, - defaultPath) + str(defaultPath)) dataeditors.openExtUrl(address) event.accept() else: super().mousePressEvent(event) + def mouseMoveEvent(self, event): + """Handle mouse move event to create editors on hover. + + Arguments: + event -- the mouse event + """ + cell = self.itemAt(event.pos()) + if cell and hasattr(cell, 'doc'): + oldCell = self.currentItem() + if (cell != oldCell and cell != self.prevHoverCell and + not self.inLinkSelectActive): + self.prevHoverCell = cell + self.hoverFocusActive.emit() + self.setFocus() + if oldCell and hasattr(oldCell, 'doc'): + # these lines result in two calls to moveEditor, but seems + # to be necessary to avoid race that leaves stray editors + self.moveEditor(None, oldCell) + self.setCurrentItem(None) + try: + self.setCurrentItem(cell) + except RuntimeError: + # catch error if view updates due to node rename ending + pass + else: + self.prevHoverCell = None + -class SearchHighlighter(QtGui.QSyntaxHighlighter): +class SearchHighlighter(QSyntaxHighlighter): """Class override to highlight search terms in cell text. Used to highlight search words from a list. @@ -696,7 +744,7 @@ pos = text.lower().find(word, pos + len(word)) -class MatchHighlighter(QtGui.QSyntaxHighlighter): +class MatchHighlighter(QSyntaxHighlighter): """Class override to highlight a specific search result in cell text. Used to highlight a text or reg exp match. @@ -733,6 +781,6 @@ else: match = self.regExpObj.search(text, pos) pos = match.start() if match else -1 - matchLen = len(match.group()) + matchLen = len(match.group()) if match else 0 if pos >= 0: self.setFormat(pos, matchLen, self.charFormat) diff -Nru treeline-2.0.2/source/exports.py treeline-3.0.1/source/exports.py --- treeline-2.0.2/source/exports.py 2015-03-05 02:29:09.000000000 +0000 +++ treeline-3.0.1/source/exports.py 2018-04-08 19:19:50.000000000 +0000 @@ -4,7 +4,7 @@ # exports.py, provides classes for a file export dialog and export functions # # TreeLine, an information storage program -# Copyright (C) 2015, Douglas W. Bell +# Copyright (C) 2018, 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 @@ -13,16 +13,39 @@ #****************************************************************************** import os.path +import pathlib +import re +import json import copy import io import zipfile +import csv +import shutil from xml.etree import ElementTree -from PyQt4 import QtCore, QtGui +from PyQt5.QtCore import Qt +from PyQt5.QtGui import QFontInfo +from PyQt5.QtWidgets import (QApplication, QButtonGroup, QCheckBox, QDialog, + QFileDialog, QGroupBox, QHBoxLayout, QLabel, + QMessageBox, QRadioButton, QSpinBox, QVBoxLayout, + QWizard, QWizardPage) +import treestructure import treenode import treeformats -import treemodel +import nodeformat +import fieldformat import treeoutput +import treemaincontrol +import imports +import urltools import globalref +try: + from __main__ import __version__ +except ImportError: + __version__ = '' +try: + from __main__ import templatePath +except ImportError: + templatePath = None _bookmarkTitle = _('Bookmarks') _odfNamespace = {'fo': @@ -34,21 +57,28 @@ 'text': 'urn:oasis:names:tc:opendocument:xmlns:text:1.0', 'manifest': 'urn:oasis:names:tc:opendocument:xmlns:manifest:1.0'} +_intLinkRe = re.compile(r']*href="#(.*?)"[^>]*>.*?', re.I | re.S) +_genLinkRe = re.compile(r']*href="(.*?)"[^>]*>.*?', re.I | re.S) +_imgLinkRe = re.compile(r']*src="(.*?)"[^>]*>.*?', re.I | re.S) +_idReplaceCharsRe = re.compile(r'[^a-zA-Z0-9_-]+') class ExportControl: """Control to do file exports for tree branches and nodes. """ - def __init__(self, rootNode, selectedNodes, defaultFilePath=''): + def __init__(self, structure, selectionModel, defaultPathObj, printData): """Initialize export control object. Arguments: - rootNode -- the root node for exporting the entire tree - selectedNodes -- the selection for exporting partial trees - defaultFilePath -- path or path/name to use as file dialog default - """ - self.rootNode = rootNode - self.selectedNodes = selectedNodes - self.defaultFilePath = os.path.splitext(defaultFilePath)[0] + structure -- the tree structure ref for exporting the entire tree + selectionModel -- the selection model for partial exports + defaultPathObj -- path object to use as file dialog default + printData -- a ref to print data for old treeline exports + """ + self.structure = structure + self.selectedSpots = selectionModel.selectedSpots() + self.selectedNodes = selectionModel.selectedNodes() + self.defaultPathObj = defaultPathObj + self.printData = printData def interactiveExport(self): """Prompt the user for types, options, filename & proceed with export. @@ -59,24 +89,29 @@ 'htmlNavSingle': self.exportHtmlNavSingle, 'htmlPages': self.exportHtmlPages, 'htmlTables': self.exportHtmlTables, + 'htmlLiveLink': self.exportHtmlLiveLink, + 'htmlLiveSingle': self.exportHtmlLiveSingle, 'textTitles': self.exportTextTitles, 'textPlain': self.exportTextPlain, - 'textTables': self.exportTextTables, + 'textTableMultiCsv': self.exportTextTableMultiCsv, + 'textTableCsv': self.exportTextTableCsv, + 'textTableTab': self.exportTextTableTab, + 'oldTreeLine': self.exportOldTreeLine, + 'treeLineSubtree': self.exportSubtree, 'xmlGeneric': self.exportXmlGeneric, - 'xmlSubtree': self.exportXmlSubtree, 'odfText': self.exportOdfText, 'bookmarksHtml': self.exportBookmarksHtml, 'bookmarksXbel': self.exportBookmarksXbel} exportDialog = ExportDialog(len(self.selectedNodes), - QtGui.QApplication.activeWindow()) - if exportDialog.exec_() == QtGui.QDialog.Accepted: + QApplication.activeWindow()) + if exportDialog.exec_() == QDialog.Accepted: result = exportMethods[ExportDialog.currentSubtype]() - QtGui.QApplication.restoreOverrideCursor() + QApplication.restoreOverrideCursor() return result return False def getFileName(self, dialogTitle, defaultExt='txt'): - """Prompt the user for a filename and return it. + """Prompt the user for a filename and return a path object. Arguments: dialogTitle -- the title for use on the dialog window @@ -84,44 +119,51 @@ """ filters = ';;'.join((globalref.fileFilters[defaultExt], globalref.fileFilters['all'])) - if os.path.basename(self.defaultFilePath): - self.defaultFilePath = '{0}.{1}'.format(self.defaultFilePath, - defaultExt) - filePath = QtGui.QFileDialog.getSaveFileName(QtGui.QApplication. - activeWindow(), - dialogTitle, - self.defaultFilePath, - filters) - if filePath and not os.path.splitext(filePath)[1]: - filePath = '{0}.{1}'.format(filePath, defaultExt) - return filePath + defaultExt = defaultExt[:4] + if self.defaultPathObj.name: + self.defaultPathObj = self.defaultPathObj.with_suffix('.' + + defaultExt) + filePath, selectFilter = QFileDialog.getSaveFileName(QApplication. + activeWindow(), + dialogTitle, + str(self.defaultPathObj), + filters) + if filePath: + pathObj = pathlib.Path(filePath) + if not pathObj.suffix: + pathObj = pathObj.with_suffix('.' + defaultExt) + return pathObj + return None - def exportHtmlSingle(self, filePath=''): + + def exportHtmlSingle(self, pathObj=None): """Export to a single web page, use ExportDialog options. Prompt user for path if not given in argument. Return True on successful export. Arguments: - filePath -- use if given, otherwise prompt user + pathObj -- use if given, otherwise prompt user """ - if not filePath: - filePath = self.getFileName(_('TreeLine - Export HTML'), 'html') - if not filePath: + if not pathObj: + pathObj = self.getFileName(_('TreeLine - Export HTML'), 'html') + if not pathObj: return False - QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) + QApplication.setOverrideCursor(Qt.WaitCursor) if ExportDialog.exportWhat == ExportDialog.entireTree: - self.selectedNodes = [self.rootNode] - outputGroup = treeoutput.OutputGroup(self.selectedNodes, + self.selectedSpots = self.structure.rootSpots() + outputGroup = treeoutput.OutputGroup(self.selectedSpots, ExportDialog.includeRoot, ExportDialog.exportWhat != ExportDialog.selectNode, - ExportDialog.openOnly, True) + ExportDialog.openOnly) + outputGroup.addAnchors() + if outputGroup.hasPrefixes(): + outputGroup.combineAllSiblings() outputGroup.addBlanksBetween() outputGroup.addIndents() - outputGroup.addSiblingPrefixes() outGroups = outputGroup.splitColumns(ExportDialog.numColumns) - htmlTitle = os.path.splitext(os.path.basename(filePath))[0] - indent = globalref.genOptions.getValue('IndentOffset') + htmlTitle = pathObj.stem + indent = globalref.genOptions['IndentOffset'] lines = ['', '', '', '', '', '', ''] prevLevel = 0 - for parent in self.selectedNodes: - for node, level in parent.levelDescendantGen(ExportDialog. - includeRoot, - ExportDialog. - navPaneLevels, - ExportDialog. - openOnly): + treeView = globalref.mainControl.activeControl.activeWindow.treeView + for parentSpot in self.selectedSpots: + for spot, level in parentSpot.levelSpotDescendantGen(treeView, + ExportDialog. + includeRoot, + ExportDialog. + navPaneLevels, + ExportDialog. + openOnly): if level > prevLevel: lines.append('
') while level < prevLevel: lines.append('
') prevLevel -= 1 + node = spot.nodeRef lines.append('• {1}
'. - format(node.uniqueId, node.title())) + format(node.uId, node.title(spot))) prevLevel = level while level > 0: lines.append('') @@ -225,30 +271,31 @@ if footerText: lines.append(footerText) lines.extend(['', '', '']) - with open(filePath, 'w', encoding='utf-8') as f: + with pathObj.open('w', encoding='utf-8') as f: f.writelines([(line + '\n') for line in lines]) return True - def exportHtmlPages(self, filePath=''): + def exportHtmlPages(self, pathObj=None): """Export multiple web pages with navigation, use ExportDialog options. Prompt user for path if not given in argument. Return True on successful export. Arguments: - filePath -- use if given, otherwise prompt user + pathObj -- use if given, otherwise prompt user """ - if not filePath: - filePath = QtGui.QFileDialog.getExistingDirectory(QtGui. - QApplication.activeWindow(), + if not pathObj: + path = QFileDialog.getExistingDirectory(QApplication. + activeWindow(), _('TreeLine - Export HTML'), - self.defaultFilePath) - if not filePath: + str(self.defaultPathObj)) + if not path: return False - QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) + pathObj = pathlib.Path(path) + QApplication.setOverrideCursor(Qt.WaitCursor) oldDir = os.getcwd() - os.chdir(filePath) - indent = globalref.genOptions.getValue('IndentOffset') + os.chdir(str(pathObj)) + indent = globalref.genOptions['IndentOffset'] cssLines = ['#sidebar {', ' width: 16em;', ' float: left;', @@ -262,383 +309,666 @@ '}'] with open('default.css', 'w', encoding='utf-8') as f: f.writelines([(line + '\n') for line in cssLines]) - if ExportDialog.exportWhat == ExportDialog.entireTree: - self.selectedNodes = [self.rootNode] - if len(self.selectedNodes) > 1: - modelRef = self.selectedNodes[0].modelRef - dummyFormat = modelRef.formats.addDummyRootType() - root = treenode.TreeNode(None, dummyFormat.name, modelRef) - name = os.path.basename(self.defaultFilePath) - if not name: - name = treemodel.defaultRootName - root.setTitle(name) - for node in self.selectedNodes: - root.childList.append(copy.copy(node)) - root.childList[-1].parent = root - else: - root = self.selectedNodes[0] - root.exportHtmlPage() - root.modelRef.formats.removeDummyRootType() + if ExportDialog.exportWhat != ExportDialog.entireTree: + self.structure = treestructure.TreeStructure(topNodes=self. + selectedNodes, + addSpots=False) + if len(self.structure.childList) > 1: + self.structure = treestructure.TreeStructure(topNodes=self. + structure.childList, + addSpots=False) + rootType = nodeformat.NodeFormat(treeformats.defaultTypeName, + self.structure.treeFormats, + addDefaultField=True) + self.structure.treeFormats.addTypeIfMissing(rootType) + root = treenode.TreeNode(self.structure. + treeFormats[treeformats.defaultTypeName]) + root.setTitle(treestructure.defaultRootTitle) + self.structure.addNodeDictRef(root) + root.childList = self.structure.childList + self.structure.childList = [root] + pathDict = {} + _setHtmlDirectories(self.structure.childList[0], pathDict, pathObj, + set()) + _writeHtmlPage(self.structure.childList[0], None, None, pathDict) os.chdir(oldDir) return True - def exportHtmlTables(self, filePath=''): + def exportHtmlTables(self, pathObj=None): """Export to multiple web page tables, use ExportDialog options. Prompt user for path if not given in argument. Return True on successful export. Arguments: - filePath -- use if given, otherwise prompt user + pathObj -- use if given, otherwise prompt user """ - if not filePath: - filePath = QtGui.QFileDialog.getExistingDirectory(QtGui. - QApplication.activeWindow(), + if not pathObj: + path = QFileDialog.getExistingDirectory(QApplication. + activeWindow(), _('TreeLine - Export HTML'), - self.defaultFilePath) - if not filePath: + str(self.defaultPathObj)) + if not path: return False - QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) + pathObj = pathlib.Path(path) + QApplication.setOverrideCursor(Qt.WaitCursor) oldDir = os.getcwd() - os.chdir(filePath) - if ExportDialog.exportWhat == ExportDialog.entireTree: - self.selectedNodes = [self.rootNode] - if len(self.selectedNodes) > 1: - modelRef = self.selectedNodes[0].modelRef - dummyFormat = modelRef.formats.addDummyRootType() - root = treenode.TreeNode(None, dummyFormat.name, modelRef) - name = os.path.basename(self.defaultFilePath) + os.chdir(str(pathObj)) + if ExportDialog.exportWhat != ExportDialog.entireTree: + self.structure = treestructure.TreeStructure(topNodes=self. + selectedNodes, + addSpots=False) + if len(self.structure.childList) > 1: + self.structure = treestructure.TreeStructure(topNodes=self. + structure.childList, + addSpots=False) + rootType = nodeformat.NodeFormat(treeformats.defaultTypeName, + self.structure.treeFormats, + addDefaultField=True) + self.structure.treeFormats.addTypeIfMissing(rootType) + root = treenode.TreeNode(self.structure. + treeFormats[treeformats.defaultTypeName]) + name = self.defaultPathObj.stem if not name: - name = treemodel.defaultRootName + name = treestructure.defaultRootTitle root.setTitle(name) - for node in self.selectedNodes: - root.childList.append(copy.copy(node)) - root.childList[-1].parent = root - else: - root = self.selectedNodes[0] - root.exportHtmlTable() - root.modelRef.formats.removeDummyRootType() + self.structure.addNodeDictRef(root) + root.childList = self.structure.childList + self.structure.childList = [root] + pathDict = {} + _setHtmlDirectories(self.structure.childList[0], pathDict, pathObj, + set(), False) + _writeHtmlTable(self.structure.childList[0], None, pathDict) os.chdir(oldDir) - return False + return True - def exportTextTitles(self, filePath=''): + def exportHtmlLiveLink(self, pathObj=None): + """Export a live tree view, linked back to the source file. + + Prompt user for path if not given in argument. + Return True on successful export. + Arguments: + pathObj -- use if given, otherwise prompt user + """ + if not pathObj: + path = QFileDialog.getExistingDirectory(QApplication. + activeWindow(), + _('TreeLine - Export HTML'), + str(self.defaultPathObj)) + if not path: + return False + pathObj = pathlib.Path(path) + QApplication.setOverrideCursor(Qt.WaitCursor) + control = globalref.mainControl + prefPath = templatePath + '/exports' if templatePath else '' + htmlPath = control.findResourceFile('live_tree_export.html', + 'templates/exports', prefPath) + jsPath = control.findResourceFile('live_tree_export.js', + 'templates/exports', prefPath) + cssPath = control.findResourceFile('live_tree_export.css', + 'templates/exports', prefPath) + if not htmlPath or not jsPath or not cssPath: + QApplication.restoreOverrideCursor() + QMessageBox.warning(QApplication.activeWindow(), 'TreeLine', + _('Error - export template files not found.\n' + 'Check your TreeLine installation.')) + return False + refPath = globalref.mainControl.activeControl.filePathObj + if not refPath: + QApplication.restoreOverrideCursor() + QMessageBox.warning(QApplication.activeWindow(), 'TreeLine', + _('Error - cannot link to unsaved TreeLine ' + 'file.\nSave the file and retry.')) + return False + try: + refPath = pathlib.Path(os.path.relpath(str(refPath), str(pathObj))) + except ValueError: + QApplication.restoreOverrideCursor() + msg = _('Warning - no relative path from "{0}" to "{1}".\n' + 'Continue with absolute path?').format(pathObj.as_posix(), + refPath.as_posix()) + ans = QMessageBox.warning(QApplication.activeWindow(), 'TreeLine', + msg, QMessageBox.Yes | QMessageBox.No, + QMessageBox.Yes) + if ans == QMessageBox.No: + return False + QApplication.setOverrideCursor(Qt.WaitCursor) + fileStem = refPath.stem + outPath = pathObj / (fileStem + '.html') + with htmlPath.open(encoding='utf-8') as fileIn: + with outPath.open('w', encoding='utf-8') as fileOut: + for line in fileIn: + if '' in line: + line = re.sub(r'<title>.*', + '{0}'.format(fileStem), + line) + elif 'dataFilePath' in line: + line = line.replace('""', '"{0}"'. + format(refPath.parent.as_posix())) + elif 'dataFileName' in line: + line = line.replace('""', '"{0}"'.format(refPath.name)) + fileOut.write(line) + shutil.copy(str(jsPath), str(pathObj)) + shutil.copy(str(cssPath), str(pathObj)) + return True + + def exportHtmlLiveSingle(self, pathObj=None): + """Export a live tree view to a single file (embedded data). + + Prompt user for path if not given in argument. + Return True on successful export. + Arguments: + pathObj -- use if given, otherwise prompt user + """ + if not pathObj: + pathObj = self.getFileName(_('TreeLine - Export HTML'), 'html') + if not pathObj: + return False + QApplication.setOverrideCursor(Qt.WaitCursor) + control = globalref.mainControl + prefPath = templatePath + '/exports' if templatePath else '' + htmlPath = control.findResourceFile('live_tree_export.html', + 'templates/exports', prefPath) + jsPath = control.findResourceFile('live_tree_export.js', + 'templates/exports', prefPath) + cssPath = control.findResourceFile('live_tree_export.css', + 'templates/exports', prefPath) + if not htmlPath or not jsPath or not cssPath: + QApplication.restoreOverrideCursor() + QMessageBox.warning(QApplication.activeWindow(), 'TreeLine', + _('Error - export template files not found.\n' + 'Check your TreeLine installation.')) + return False + if ExportDialog.exportWhat == ExportDialog.entireTree: + fileData = self.structure.fileData() + else: + self.structure = treestructure.TreeStructure(topNodes=self. + selectedNodes, + addSpots=False) + fileData = self.structure.fileData() + if ExportDialog.exportWhat == ExportDialog.selectNode: + topNodeIds = set([node.uId for node in + self.structure.childList]) + nodeData = [data for data in fileData['nodes'] if data['uid'] + in topNodeIds] + for data in nodeData: + data['children'] = [] + fileData['nodes'] = nodeData + with htmlPath.open(encoding='utf-8') as htmlIn: + with pathObj.open('w', encoding='utf-8') as htmlOut: + for line in htmlIn: + if 'stylesheet' in line: + htmlOut.write('') + elif '' in line: + line = re.sub(r'<title>.*', + '{0}'. + format(pathObj.stem), line) + htmlOut.write(line) + elif 'application/json' in line: + htmlOut.write(line) + json.dump(fileData, htmlOut, indent=2, sort_keys=True) + elif 'dataFileName' in line: + htmlOut.write(line) + with jsPath.open(encoding='utf-8') as jsIn: + htmlOut.write(jsIn.read()) + elif 'script src=' not in line: + htmlOut.write(line) + return True + + def exportTextTitles(self, pathObj=None): """Export tabbed title text, use ExportDialog options. Prompt user for path if not given in argument. Return True on successful export. Arguments: - filePath -- use if given, otherwise prompt user + pathObj -- use if given, otherwise prompt user """ - if not filePath: - filePath = self.getFileName(_('TreeLine - Export Text Titles'), - 'txt') - if not filePath: + if not pathObj: + pathObj = self.getFileName(_('TreeLine - Export Text Titles'), + 'txt') + if not pathObj: return False - QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) + QApplication.setOverrideCursor(Qt.WaitCursor) if ExportDialog.exportWhat == ExportDialog.entireTree: - self.selectedNodes = [self.rootNode] + self.selectedSpots = self.structure.rootSpots() if ExportDialog.exportWhat == ExportDialog.selectNode: - lines = [node.title() for node in self.selectedNodes] + lines = [spot.nodeRef.title(spot) for spot in self.selectedSpots] else: + treeView = (globalref.mainControl.activeControl.activeWindow. + treeView) lines = [] - initLevel = 0 if ExportDialog.includeRoot else -1 - for node in self.selectedNodes: - text = node.exportTitleText(initLevel, ExportDialog.openOnly) - if not ExportDialog.includeRoot: - del text[0] - lines.extend(text) - with open(filePath, 'w', encoding=globalref.localTextEncoding) as f: + for rootSpot in self.selectedSpots: + for spot, level in rootSpot.levelSpotDescendantGen(treeView, + ExportDialog.includeRoot, + None, ExportDialog.openOnly): + lines.append('\t' * level + spot.nodeRef.title(spot)) + with pathObj.open('w', encoding=globalref.localTextEncoding) as f: f.writelines([(line + '\n') for line in lines]) return True - def exportTextPlain(self, filePath=''): + def exportTextPlain(self, pathObj=None): """Export unformatted text for all output, use ExportDialog options. Prompt user for path if not given in argument. Return True on successful export. Arguments: - filePath -- use if given, otherwise prompt user + pathObj -- use if given, otherwise prompt user """ - if not filePath: - filePath = self.getFileName(_('TreeLine - Export Plain Text'), - 'txt') - if not filePath: + if not pathObj: + pathObj = self.getFileName(_('TreeLine - Export Plain Text'), + 'txt') + if not pathObj: return False - QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) + QApplication.setOverrideCursor(Qt.WaitCursor) if ExportDialog.exportWhat == ExportDialog.entireTree: - self.selectedNodes = [self.rootNode] + self.selectedSpots = self.structure.rootSpots() lines = [] - for root in self.selectedNodes: - if ExportDialog.includeRoot: - lines.extend(root.formatOutput(True)) - if root.nodeFormat().spaceBetween: + if ExportDialog.exportWhat == ExportDialog.selectNode: + for rootSpot in self.selectedSpots: + lines.extend(rootSpot.nodeRef.output(True, False, rootSpot)) + if rootSpot.nodeRef.formatRef.spaceBetween: lines.append('') - if not ExportDialog.exportWhat == ExportDialog.selectNode: - for node in (root. - selectiveDescendantGen(ExportDialog.openOnly)): - lines.extend(node.formatOutput(True)) - if node.nodeFormat().spaceBetween: + else: + treeView = (globalref.mainControl.activeControl.activeWindow. + treeView) + for rootSpot in self.selectedSpots: + for spot, level in rootSpot.levelSpotDescendantGen(treeView, + ExportDialog.includeRoot, + None, ExportDialog.openOnly): + lines.extend(spot.nodeRef.output(True, False, spot)) + if spot.nodeRef.formatRef.spaceBetween: lines.append('') - with open(filePath, 'w', encoding=globalref.localTextEncoding) as f: + with pathObj.open('w', encoding=globalref.localTextEncoding) as f: f.writelines([(line + '\n') for line in lines]) return True - def exportTextTables(self, filePath=''): + def exportTextTableMultiCsv(self, pathObj=None): + """Export descendant CSV delimited text table with level numbers. + + Prompt user for path if not given in argument. + Return True on successful export. + Arguments: + pathObj -- use if given, otherwise prompt user + """ + if not pathObj: + pathObj = self.getFileName(_('TreeLine - Export Text Tables'), + 'csv') + if not pathObj: + return False + QApplication.setOverrideCursor(Qt.WaitCursor) + if ExportDialog.exportWhat == ExportDialog.entireTree: + self.selectedSpots = self.structure.rootSpots() + treeView = (globalref.mainControl.activeControl.activeWindow.treeView) + types = set() + headings = [] + for rootSpot in self.selectedSpots: + for spot, level in rootSpot.levelSpotDescendantGen(treeView, + ExportDialog.includeRoot, + None, ExportDialog.openOnly): + nodeFormat = spot.nodeRef.formatRef + if nodeFormat not in types: + for fieldName in nodeFormat.fieldNames(): + if fieldName not in headings: + headings.append(fieldName) + types.add(nodeFormat) + lines = [['Level'] + headings] + for rootSpot in self.selectedSpots: + for spot, level in rootSpot.levelSpotDescendantGen(treeView, + ExportDialog.includeRoot, + None, ExportDialog.openOnly): + newLine = [spot.nodeRef.data.get(head, '') for head in + headings] + lines.append([repr(level)] + newLine) + with pathObj.open('w', newline='', + encoding=globalref.localTextEncoding) as f: + writer = csv.writer(f) + writer.writerows(lines) + return True + + def exportTextTableCsv(self, pathObj=None): + """Export child CSV delimited text table, use ExportDialog options. + + Prompt user for path if not given in argument. + Return True on successful export. + Arguments: + pathObj -- use if given, otherwise prompt user + """ + if not pathObj: + pathObj = self.getFileName(_('TreeLine - Export Text Tables'), + 'csv') + if not pathObj: + return False + QApplication.setOverrideCursor(Qt.WaitCursor) + if ExportDialog.exportWhat == ExportDialog.selectNode: + nodeList = self.selectedNodes + else: + nodeList = [] + for node in self.selectedNodes: + nodeList.extend(node.childList) + types = set() + headings = [] + for node in nodeList: + nodeFormat = node.formatRef + if nodeFormat not in types: + for fieldName in nodeFormat.fieldNames(): + if fieldName not in headings: + headings.append(fieldName) + types.add(nodeFormat) + lines = [headings] + for node in nodeList: + lines.append([node.data.get(head, '') for head in headings]) + with pathObj.open('w', newline='', + encoding=globalref.localTextEncoding) as f: + writer = csv.writer(f) + writer.writerows(lines) + return True + + def exportTextTableTab(self, pathObj=None): """Export child tab delimited text table, use ExportDialog options. Prompt user for path if not given in argument. Return True on successful export. Arguments: - filePath -- use if given, otherwise prompt user + pathObj -- use if given, otherwise prompt user """ - if not filePath: - filePath = self.getFileName(_('TreeLine - Export Text Tables'), - 'txt') - if not filePath: + if not pathObj: + pathObj = self.getFileName(_('TreeLine - Export Text Tables'), + 'txt') + if not pathObj: return False - QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) + QApplication.setOverrideCursor(Qt.WaitCursor) if ExportDialog.exportWhat == ExportDialog.selectNode: nodeList = self.selectedNodes else: nodeList = [] for node in self.selectedNodes: nodeList.extend(node.childList) - typeList = [] + types = set() headings = [] for node in nodeList: - nodeFormat = node.nodeFormat() - if nodeFormat not in typeList: + nodeFormat = node.formatRef + if nodeFormat not in types: for fieldName in nodeFormat.fieldNames(): if fieldName not in headings: headings.append(fieldName) - typeList.append(nodeFormat) + types.add(nodeFormat) lines = ['\t'.join(headings)] for node in nodeList: lines.append('\t'.join([node.data.get(head, '') for head in headings])) - with open(filePath, 'w', encoding=globalref.localTextEncoding) as f: + with pathObj.open('w', encoding=globalref.localTextEncoding) as f: f.writelines([(line + '\n') for line in lines]) return True - def exportXmlGeneric(self, filePath=''): - """Export generic XML, use ExportDialog options. + def exportOldTreeLine(self, pathObj=None): + """Export old TreeLine version (2.0.x), use ExportDialog options. Prompt user for path if not given in argument. Return True on successful export. Arguments: - filePath -- use if given, otherwise prompt user + pathObj -- use if given, otherwise prompt user """ - if not filePath: - filePath = self.getFileName(_('TreeLine - Export Generic XML'), - 'xml') - if not filePath: + if not pathObj: + pathObj = self.getFileName(_('TreeLine - Export TreeLine Subtree'), + 'trl') + if not pathObj: return False - QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) - if ExportDialog.exportWhat == ExportDialog.entireTree: - self.selectedNodes = [self.rootNode] - addBranches = ExportDialog.exportWhat != ExportDialog.selectNode - if len(self.selectedNodes) > 1: - rootElement = ElementTree.Element(treeformat.defaultTypeName) - for node in self.selectedNodes: - rootElement.append(node.exportGenericXml(addBranches)) - else: - rootElement = self.selectedNodes[0].exportGenericXml(addBranches) + QApplication.setOverrideCursor(Qt.WaitCursor) + if ExportDialog.exportWhat != ExportDialog.entireTree: + self.structure = treestructure.TreeStructure(topNodes=self. + selectedNodes, + addSpots=False) + addDescend = ExportDialog.exportWhat != ExportDialog.selectNode + addChildren = addDescend + if len(self.structure.childList) > 1: + if not addDescend: + addChildren = True + self.structure = treestructure.TreeStructure(topNodes=self. + structure.childList, + addSpots=False) + rootType = nodeformat.NodeFormat(treeformats.defaultTypeName, + self.structure.treeFormats, + addDefaultField=True) + self.structure.treeFormats.addTypeIfMissing(rootType) + root = treenode.TreeNode(self.structure. + treeFormats[treeformats.defaultTypeName]) + root.setTitle(treestructure.defaultRootTitle) + self.structure.addNodeDictRef(root) + root.childList = self.structure.childList + self.structure.childList = [root] + idDict = {} + for node in self.structure.childList[0].descendantGen(): + _setOldUniqueId(idDict, node) + idDict = {i[1]: i[0] for i in idDict.items()} # reverse (new id keys) + rootElement = _oldElementXml(self.structure.childList[0], + self.structure, idDict, + addChildren=addChildren, + addDescend=addDescend) + if __version__: + rootElement.set('tlversion', __version__) + rootElement.attrib.update(_convertOldPrintData(self.printData. + fileData())) elementTree = ElementTree.ElementTree(rootElement) - elementTree.write(filePath, 'utf-8', True) + elementTree.write(str(pathObj), 'utf-8', True) return True - def exportXmlSubtree(self, filePath=''): + def exportSubtree(self, pathObj=None): """Export TreeLine subtree, use ExportDialog options. Prompt user for path if not given in argument. Return True on successful export. Arguments: - filePath -- use if given, otherwise prompt user + pathObj -- use if given, otherwise prompt user + """ + if not pathObj: + pathObj = self.getFileName(_('TreeLine - Export TreeLine Subtree'), + 'trlnsave') + if not pathObj: + return False + QApplication.setOverrideCursor(Qt.WaitCursor) + self.structure = treestructure.TreeStructure(topNodes=self. + selectedNodes, + addSpots=False) + fileData = self.structure.fileData() + fileData['properties'].update(self.printData.fileData()) + if ExportDialog.exportWhat == ExportDialog.selectNode: + topNodeIds = set([node.uId for node in self.structure.childList]) + nodeData = [data for data in fileData['nodes'] if data['uid'] in + topNodeIds] + for data in nodeData: + data['children'] = [] + fileData['nodes'] = nodeData + with pathObj.open('w', encoding='utf-8', newline='\n') as f: + json.dump(fileData, f, indent=0, sort_keys=True) + return True + + def exportXmlGeneric(self, pathObj=None): + """Export generic XML, use ExportDialog options. + + Prompt user for path if not given in argument. + Return True on successful export. + Arguments: + pathObj -- use if given, otherwise prompt user """ - if not filePath: - filePath = self.getFileName(_('TreeLine - Export TreeLine ' - 'Subtree'), 'trl') - if not filePath: + if not pathObj: + pathObj = self.getFileName(_('TreeLine - Export Generic XML'), + 'xml') + if not pathObj: return False - QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) + QApplication.setOverrideCursor(Qt.WaitCursor) if ExportDialog.exportWhat == ExportDialog.entireTree: - self.selectedNodes = [self.rootNode] + self.selectedNodes = self.structure.childList addBranches = ExportDialog.exportWhat != ExportDialog.selectNode if len(self.selectedNodes) > 1: - modelRef = self.selectedNodes[0].modelRef - dummyFormat = modelRef.formats.addDummyRootType() - root = treenode.TreeNode(None, dummyFormat.name, modelRef) - name = os.path.basename(self.defaultFilePath) - if not name: - name = treemodel.defaultRootName - root.setTitle(name) + rootElement = ElementTree.Element(treeformats.defaultTypeName) for node in self.selectedNodes: - root.childList.append(copy.copy(node)) - root.childList[-1].parent = root + rootElement.append(_createGenericXml(node, addBranches)) else: - root = self.selectedNodes[0] - rootElement = root.elementXml(addChildren=addBranches) - rootElement.attrib.update(globalref.mainControl.activeControl. - printData.xmlAttr()) + rootElement = _createGenericXml(self.selectedNodes[0], addBranches) elementTree = ElementTree.ElementTree(rootElement) - elementTree.write(filePath, 'utf-8', True) - root.modelRef.formats.removeDummyRootType() + elementTree.write(str(pathObj), 'utf-8', True) return True - def exportOdfText(self, filePath=''): + def exportOdfText(self, pathObj=None): """Export an ODF text file, use ExportDialog options. Prompt user for path if not given in argument. Return True on successful export. Arguments: - filePath -- use if given, otherwise prompt user + pathObj -- use if given, otherwise prompt user """ - if not filePath: - filePath = self.getFileName(_('TreeLine - Export ODF Text'), 'odt') - if not filePath: + if not pathObj: + pathObj = self.getFileName(_('TreeLine - Export ODF Text'), 'odt') + if not pathObj: return False - QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) + QApplication.setOverrideCursor(Qt.WaitCursor) if ExportDialog.exportWhat == ExportDialog.entireTree: - self.selectedNodes = [self.rootNode] + self.selectedSpots = self.structure.rootSpots() addBranches = ExportDialog.exportWhat != ExportDialog.selectNode for prefix, uri in _odfNamespace.items(): ElementTree.register_namespace(prefix, uri) versionAttr = {'office:version': '1.0'} - fontInfo = QtGui.QFontInfo(globalref.mainControl.activeControl. + fontInfo = QFontInfo(globalref.mainControl.activeControl. activeWindow.editorSplitter.widget(0). font()) fontAttr = {'style:font-pitch': 'fixed' if fontInfo.fixedPitch() else 'variable', 'style:name': fontInfo.family(), 'svg:font-family': fontInfo.family()} - fontElem = addOdfElement('office:font-face-decls') - addOdfElement('style:font-face', fontElem, fontAttr) + fontElem = _addOdfElement('office:font-face-decls') + _addOdfElement('style:font-face', fontElem, fontAttr) fontSizeDelta = 2 - contentRoot = addOdfElement('office:document-content', - attr=versionAttr) + contentRoot = _addOdfElement('office:document-content', + attr=versionAttr) contentRoot.append(fontElem) - contentBodyElem = addOdfElement('office:body', contentRoot) - contentTextElem = addOdfElement('office:text', contentBodyElem) + contentBodyElem = _addOdfElement('office:body', contentRoot) + contentTextElem = _addOdfElement('office:text', contentBodyElem) maxLevel = 0 - for node in self.selectedNodes: - level = node.exportOdf(contentTextElem, addBranches) + for spot in self.selectedSpots: + level = _addOdfText(spot, contentTextElem, addBranches) maxLevel = max(level, maxLevel) - manifestRoot = addOdfElement('manifest:manifest') - addOdfElement('manifest:file-entry', manifestRoot, - {'manifest:media-type': - 'application/vnd.oasis.opendocument.text', - 'manifest:full-path': '/'}) - addOdfElement('manifest:file-entry', manifestRoot, - {'manifest:media-type': 'text/xml', - 'manifest:full-path': 'content.xml'}) - addOdfElement('manifest:file-entry', manifestRoot, - {'manifest:media-type': 'text/xml', - 'manifest:full-path': 'styles.xml'}) + manifestRoot = _addOdfElement('manifest:manifest') + _addOdfElement('manifest:file-entry', manifestRoot, + {'manifest:media-type': + 'application/vnd.oasis.opendocument.text', + 'manifest:full-path': '/'}) + _addOdfElement('manifest:file-entry', manifestRoot, + {'manifest:media-type': 'text/xml', + 'manifest:full-path': 'content.xml'}) + _addOdfElement('manifest:file-entry', manifestRoot, + {'manifest:media-type': 'text/xml', + 'manifest:full-path': 'styles.xml'}) - styleRoot = addOdfElement('office:document-styles', attr=versionAttr) + styleRoot = _addOdfElement('office:document-styles', attr=versionAttr) styleRoot.append(fontElem) - stylesElem = addOdfElement('office:styles', styleRoot) - defaultStyleElem = addOdfElement('style:default-style', stylesElem, - {'style:family': 'paragraph'}) - addOdfElement('style:paragraph-properties', defaultStyleElem, - {'style:writing-mode': 'page'}) - addOdfElement('style:text-properties', defaultStyleElem, - {'fo:font-size': '{0}pt'.format(fontInfo.pointSize()), - 'fo:hyphenate': 'false', - 'style:font-name': fontInfo.family()}) - addOdfElement('style:style', stylesElem, - {'style:name': 'Standard', 'style:class': 'text', - 'style:family': 'paragraph'}) - bodyStyleElem = addOdfElement('style:style', stylesElem, + stylesElem = _addOdfElement('office:styles', styleRoot) + defaultStyleElem = _addOdfElement('style:default-style', stylesElem, + {'style:family': 'paragraph'}) + _addOdfElement('style:paragraph-properties', defaultStyleElem, + {'style:writing-mode': 'page'}) + _addOdfElement('style:text-properties', defaultStyleElem, + {'fo:font-size': '{0}pt'.format(fontInfo.pointSize()), + 'fo:hyphenate': 'false', + 'style:font-name': fontInfo.family()}) + _addOdfElement('style:style', stylesElem, + {'style:name': 'Standard', 'style:class': 'text', + 'style:family': 'paragraph'}) + bodyStyleElem = _addOdfElement('style:style', stylesElem, {'style:name': 'Text_20_body', 'style:display-name': 'Text body', 'style:class': 'text', 'style:family': 'paragraph', 'style:parent-style-name': 'Standard'}) - addOdfElement('style:paragraph-properties', bodyStyleElem, + _addOdfElement('style:paragraph-properties', bodyStyleElem, {'fo:margin-bottom': '6.0pt'}) - headStyleElem = addOdfElement('style:style', stylesElem, + headStyleElem = _addOdfElement('style:style', stylesElem, {'style:name': 'Heading', 'style:class': 'text', 'style:family': 'paragraph', 'style:next-style-name': 'Text_20_body', 'style:parent-style-name': 'Standard'}) - addOdfElement('style:paragraph-properties', headStyleElem, - {'fo:keep-with-next': 'always', - 'fo:margin-bottom': '6.0pt', - 'fo:margin-top': '12.0pt'}) - addOdfElement('style:text-properties', headStyleElem, - {'fo:font-size': - '{0}pt'.format(fontInfo.pointSize() + fontSizeDelta), - 'style:font-name': fontInfo.family()}) - outlineStyleElem = addOdfElement('text:outline-style') + _addOdfElement('style:paragraph-properties', headStyleElem, + {'fo:keep-with-next': 'always', + 'fo:margin-bottom': '6.0pt', + 'fo:margin-top': '12.0pt'}) + _addOdfElement('style:text-properties', headStyleElem, + {'fo:font-size': + '{0}pt'.format(fontInfo.pointSize() + fontSizeDelta), + 'style:font-name': fontInfo.family()}) + outlineStyleElem = _addOdfElement('text:outline-style') for level in range(1, maxLevel + 1): size = fontInfo.pointSize() if level <= 2: size += 2 * fontSizeDelta elif level <= 4: size += fontSizeDelta - levelStyleElem = addOdfElement('style:style', stylesElem, - {'style:name': - 'Heading_20_{0}'.format(level), - 'style:display-name': - 'Heading {0}'.format(level), - 'style:class': 'text', - 'style:family': 'paragraph', - 'style:parent-style-name': - 'Heading', - 'style:default-outline-level': - '{0}'.format(level)}) - levelTextElem = addOdfElement('style:text-properties', - levelStyleElem, - {'fo:font-size': - '{0}pt'.format(size), - 'fo:font-weight': 'bold'}) + levelStyleElem = _addOdfElement('style:style', stylesElem, + {'style:name': + 'Heading_20_{0}'.format(level), + 'style:display-name': + 'Heading {0}'.format(level), + 'style:class': 'text', + 'style:family': 'paragraph', + 'style:parent-style-name': + 'Heading', + 'style:default-outline-level': + '{0}'.format(level)}) + levelTextElem = _addOdfElement('style:text-properties', + levelStyleElem, + {'fo:font-size': + '{0}pt'.format(size), + 'fo:font-weight': 'bold'}) if level % 2 == 0: levelTextElem.set('fo:font-style', 'italic') - addOdfElement('text:outline-level-style', outlineStyleElem, - {'text:level': '{0}'.format(level), - 'style:num-format': ''}) + _addOdfElement('text:outline-level-style', outlineStyleElem, + {'text:level': '{0}'.format(level), + 'style:num-format': ''}) stylesElem.append(outlineStyleElem) - autoStyleElem = addOdfElement('office:automatic-styles', styleRoot) - pageLayElem = addOdfElement('style:page-layout', autoStyleElem, - {'style:name': 'pm1'}) - addOdfElement('style:page-layout-properties', pageLayElem, - {'fo:margin-bottom': '0.75in', - 'fo:margin-left': '0.75in', - 'fo:margin-right': '0.75in', 'fo:margin-top': '0.75in', - 'fo:page-height': '11in', 'fo:page-width': '8.5in', - 'style:print-orientation': 'portrait'}) - masterStyleElem = addOdfElement('office:master-styles', styleRoot) - addOdfElement('style:master-page', masterStyleElem, - {'style:name': 'Standard', - 'style:page-layout-name': 'pm1'}) + autoStyleElem = _addOdfElement('office:automatic-styles', styleRoot) + pageLayElem = _addOdfElement('style:page-layout', autoStyleElem, + {'style:name': 'pm1'}) + _addOdfElement('style:page-layout-properties', pageLayElem, + {'fo:margin-bottom': '0.75in', + 'fo:margin-left': '0.75in', + 'fo:margin-right': '0.75in', 'fo:margin-top': '0.75in', + 'fo:page-height': '11in', 'fo:page-width': '8.5in', + 'style:print-orientation': 'portrait'}) + masterStyleElem = _addOdfElement('office:master-styles', styleRoot) + _addOdfElement('style:master-page', masterStyleElem, + {'style:name': 'Standard', + 'style:page-layout-name': 'pm1'}) - with zipfile.ZipFile(filePath, 'w', zipfile.ZIP_DEFLATED) as odfZip: + with zipfile.ZipFile(str(pathObj), 'w', + zipfile.ZIP_DEFLATED) as odfZip: _addElemToZip(odfZip, contentRoot, 'content.xml') _addElemToZip(odfZip, manifestRoot, 'META-INF/manifest.xml') _addElemToZip(odfZip, styleRoot, 'styles.xml') return True - def exportBookmarksHtml(self, filePath=''): + def exportBookmarksHtml(self, pathObj=None): """Export HTML format bookmarks, use ExportDialog options. Prompt user for path if not given in argument. Return True on successful export. Arguments: - filePath -- use if given, otherwise prompt user + pathObj -- use if given, otherwise prompt user """ - if not filePath: - filePath = self.getFileName(_('TreeLine - Export HTML Bookmarks'), - 'html') - if not filePath: + if not pathObj: + pathObj = self.getFileName(_('TreeLine - Export HTML Bookmarks'), + 'html') + if not pathObj: return False - QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) + QApplication.setOverrideCursor(Qt.WaitCursor) if ExportDialog.exportWhat == ExportDialog.entireTree: - self.selectedNodes = [self.rootNode] + self.selectedNodes = self.structure.childList addBranches = ExportDialog.exportWhat != ExportDialog.selectNode title = _bookmarkTitle if len(self.selectedNodes) == 1 and addBranches: @@ -649,27 +979,27 @@ 'charset=utf-8">', '{0}'.format(title), '

{0}

'.format(title)] for node in self.selectedNodes: - lines.extend(node.exportHtmlBookmarks(addBranches)) - with open(filePath, 'w', encoding='utf-8') as f: + lines.extend(_exportHtmlBookmarks(node, addBranches)) + with pathObj.open('w', encoding='utf-8') as f: f.writelines([(line + '\n') for line in lines]) return True - def exportBookmarksXbel(self, filePath=''): + def exportBookmarksXbel(self, pathObj=None): """Export XBEL format bookmarks, use ExportDialog options. Prompt user for path if not given in argument. Return True on successful export. Arguments: - filePath -- use if given, otherwise prompt user + pathObj -- use if given, otherwise prompt user """ - if not filePath: - filePath = self.getFileName(_('TreeLine - Export XBEL Bookmarks'), - 'xml') - if not filePath: + if not pathObj: + pathObj = self.getFileName(_('TreeLine - Export XBEL Bookmarks'), + 'xml') + if not pathObj: return False - QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) + QApplication.setOverrideCursor(Qt.WaitCursor) if ExportDialog.exportWhat == ExportDialog.entireTree: - self.selectedNodes = [self.rootNode] + self.selectedNodes = self.structure.childList addBranches = ExportDialog.exportWhat != ExportDialog.selectNode title = _bookmarkTitle if len(self.selectedNodes) == 1 and addBranches: @@ -680,29 +1010,479 @@ titleElem.text = title rootElem.append(titleElem) for node in self.selectedNodes: - rootElem.append(node.exportXbel(addBranches)) + rootElem.append(_exportXbel(node, addBranches)) elementTree = ElementTree.ElementTree(rootElem) - with open(filePath, 'wb') as f: + with pathObj.open('wb') as f: f.write(b'\n') elementTree.write(f, 'utf-8', False) return True +def _setHtmlDirectories(node, pathDict, parentPath, siblingNames, + addSuffix=True): + """Recursively create path obj for node and add to the path dict by uId. + + Arguments: + node -- the node to create a path object for + pathDict -- the dict of paths by uId for adding an entry + parentPath -- the path of the parent node + siblingNames -- set of already used sibling names + addSuffix -- add '.html' suffix to file names if True + """ + name = node.title() + maxLength = 32 + if len(name) > maxLength: + pos = name.rfind(' ', maxLength // 2, maxLength + 1) + if pos < 0: + pos = maxLength + name = name[:pos] + name = name.replace(' ', '_') + name = _idReplaceCharsRe.sub('', name) + if not name: + name = 'id' + elif not 'a' <= name.lower() <= 'z': + name = 'id_' + name + origName = name + i = 1 + while name in siblingNames: + name = origName + '_' + repr(i) + i += 1 + siblingNames.add(name) + pathObj = parentPath / name + filePathObj = pathObj.with_suffix('.html') if addSuffix else pathObj + pathDict[node.uId] = filePathObj + siblings = set() + for child in node.childList: + _setHtmlDirectories(child, pathDict, pathObj, siblings, addSuffix) + +def _writeHtmlPage(node, parent, grandparent, pathDict, level=0): + """Write web pages with navigation for this node and descendents. + + Arguments: + node -- the node to write the page for + parent -- the parent node (or None) + grandparent -- the grandparent node (or None) + pathDict -- the dict of paths by uId + level -- indicates the depth and how far up the css file is + """ + lines = ['', + '', '', + '', + ''.format('../' * level), + '{0}'.format(node.title()), + '', '', '', '
']) + outputLines = [line + '
' for line in node.output()] + if node.formatRef.siblingPrefix: + outputLines[0] = node.formatRef.siblingPrefix + outputLines[0] + if node.formatRef.siblingSuffix: + outputLines[-1] += node.formatRef.siblingSuffix + for i in range(len(outputLines)): + startPos = 0 + while True: + match = _genLinkRe.search(outputLines[i], startPos) + if not match: + break + addr = match.group(1) + if addr.startswith('#'): + pathObj = pathDict.get(addr[1:], None) + if pathObj: + relPath = os.path.relpath(str(pathObj), nodeDir) + outputLines[i] = (outputLines[i][:match.start(1)] + + relPath + outputLines[i][match.end(1):]) + elif urltools.isRelative(addr): + outputLines[i] = (outputLines[i][:match.start(1)] + + '../' * level + addr + + outputLines[i][match.end(1):]) + startPos = match.start(1) + startPos = 0 + while True: + match = _imgLinkRe.search(outputLines[i], startPos) + if not match: + break + addr = match.group(1) + if not addr.startswith('#') and urltools.isRelative(addr): + outputLines[i] = (outputLines[i][:match.start(1)] + + '../' * level + addr + + outputLines[i][match.end(1):]) + startPos = match.start(1) + lines.extend(outputLines) + lines.extend(['
', '', '']) + with pathDict[node.uId].open('w', encoding='utf-8') as f: + f.writelines([(line + '\n') for line in lines]) + if node.childList: + dirObj = pathDict[node.uId].with_suffix('') + if not dirObj.is_dir(): + dirObj.mkdir(0o755) + os.chdir(str(dirObj)) + for child in node.childList: + _writeHtmlPage(child, node, parent, pathDict, level + 1) + os.chdir('..') + +def _writeHtmlTable(node, parent, pathDict, level=1): + """Write web pages with tables for child data to nested directories. + + Arguments: + node -- the node to write the page for + parent -- the parent node (or None) + pathDict -- the dict of paths by uId + level -- the depth and how far up local links should point + """ + if not node.childList: + return + dirObj = pathDict[node.uId] + if not dirObj.is_dir(): + dirObj.mkdir(0o755) + os.chdir(str(dirObj)) + title = node.title() + lines = ['', + '', '', + '', '{0}'.format(title), + '', ''] + if ExportDialog.addHeader: + headerText = (globalref.mainControl.activeControl.printData. + formatHeaderFooter(True)) + if headerText: + lines.append(headerText) + lines.append('

{0}

'.format(title)) + if parent: + lines.append('

{0}: {1}' + '

'.format(_('Parent'), parent.title())) + lines.extend(['', '']) + lines.extend([''.format(name) for name in + node.childList[0].formatRef.fieldNames()]) + lines.append('') + for child in node.childList: + cellList = [field.outputText(child, False, True) for field in + child.formatRef.fields()] + for i in range(len(cellList)): + startPos = 0 + while True: + match = _genLinkRe.search(cellList[i], startPos) + if not match: + break + addr = match.group(1) + if addr.startswith('#'): + pathObj = pathDict.get(addr[1:], None) + if pathObj: + name = pathObj.stem + pathObj = pathObj / '..' / 'index.html' + relPath = os.path.relpath(str(pathObj), str(dirObj)) + relPath += '#' + name + cellList[i] = (cellList[i][:match.start(1)] + + relPath + cellList[i][match.end(1):]) + elif urltools.isRelative(addr): + cellList[i] = (cellList[i][:match.start(1)] + + '../' * level + addr + + cellList[i][match.end(1):]) + startPos = match.start(1) + startPos = 0 + while True: + match = _imgLinkRe.search(cellList[i], startPos) + if not match: + break + addr = match.group(1) + if not addr.startswith('#') and urltools.isRelative(addr): + cellList[i] = (cellList[i][:match.start(1)] + + '../' * level + addr + + cellList[i][match.end(1):]) + startPos = match.start(1) + if child.childList: + cellList[0] = ('{1}'. + format(pathDict[child.uId].stem, cellList[0])) + cellList[0] = '{1}'.format(pathDict[child.uId].stem, + cellList[0]) + lines.extend([''.format(cell) for cell in cellList]) + lines.append('') + lines.extend(['', '
{0}
{0}
']) + if ExportDialog.addHeader: + footerText = (globalref.mainControl.activeControl.printData. + formatHeaderFooter(False)) + if footerText: + lines.append(footerText) + lines.extend(['', '']) + with open('index.html', 'w', encoding='utf-8') as f: + f.writelines([(line + '\n') for line in lines]) + for child in node.childList: + _writeHtmlTable(child, node, pathDict, level + 1) + os.chdir('..') + +def _oldElementXml(node, structRef, idDict, skipTypeFormats=None, + extraFormats=True, addChildren=True, addDescend=True): + """Return an Element object with the XML for this node's branch. + + Arguments: + node -- the root node to save + structRef -- a ref to the tree structure + idDict -- a dict of new IDs to old IDs + skipTypeFormats -- a set of node format types not included in XML + extraFormats -- if True, includes unused format info + addChildren -- if True, include data for the first level of children + addDescend -- if True, add lower descendant nodes + """ + if skipTypeFormats == None: + skipTypeFormats = set() + nodeFormat = node.formatRef + addFormat = nodeFormat.name not in skipTypeFormats + element = ElementTree.Element(nodeFormat.name, {'item':'y'}) + # add line feeds to make output somewhat readable + element.tail = '\n' + element.text = '\n' + element.set('uniqueid', idDict[node.uId]) + if addFormat: + element.attrib.update(_convertOldNodeFormat(nodeFormat.storeFormat())) + skipTypeFormats.add(nodeFormat.name) + firstField = True + for field in nodeFormat.fields(): + text = node.data.get(field.name, '') + if text or addFormat: + fieldElement = ElementTree.SubElement(element, field.name) + fieldElement.tail = '\n' + if field.typeName in ('Date', 'DateTime'): + text = text.replace('-', '/') + if (field.typeName in ('Time', 'DateTime') and + text.endswith('.000000')): + text = text[:-7] + linkCount = 0 + startPos = 0 + while True: + match = _intLinkRe.search(text, startPos) + if not match: + break + uId = idDict.get(match.group(1), '') + if uId: + text = text[:match.start(1)] + uId + text[match.end(1):] + linkCount += 1 + startPos = match.start(1) + if linkCount: + fieldElement.attrib['linkcount'] = repr(linkCount) + fieldElement.text = text + if addFormat: + fieldElement.attrib.update(_convertOldFieldFormat(field. + formatData())) + if firstField: + fieldElement.attrib['idref'] = 'y' + firstField = False + if addChildren: + for child in node.childList: + element.append(_oldElementXml(child, structRef, idDict, + skipTypeFormats, False, + addChildren=addDescend)) + nodeFormats = [] + if extraFormats: # write format info for unused formats + nodeFormats = list(structRef.treeFormats.values()) + if structRef.treeFormats.fileInfoFormat.fieldFormatModified: + nodeFormats.append(structRef.treeFormats.fileInfoFormat) + for nodeFormat in nodeFormats: + if nodeFormat.name not in skipTypeFormats: + formatElement = ElementTree.SubElement(element, + nodeFormat.name, + {'item':'n'}) + formatElement.tail = '\n' + formatElement.attrib.update(_convertOldNodeFormat(nodeFormat. + storeFormat())) + firstField = True + for field in nodeFormat.fields(): + fieldElement = ElementTree.SubElement(formatElement, + field.name) + fieldElement.tail = '\n' + fieldElement.attrib.update(_convertOldFieldFormat(field. + formatData())) + if firstField: + fieldElement.attrib['idref'] = 'y' + firstField = False + return element + +def _setOldUniqueId(idDict, node): + """Set an old TreeLine unique ID for this node amd add to dict. + + Arguments: + idDict -- a dict of old IDs to new IDs. + node -- the node to give an old ID + """ + nodeFormat = node.formatRef + idField = next(iter(nodeFormat.fieldDict.values())) + uId = idField.outputText(node, True, nodeFormat.formatHtml) + uId = uId.strip().split('\n', 1)[0] + maxLength = 50 + if len(uId) > maxLength: + pos = uId.rfind(' ', maxLength // 2, maxLength + 1) + if pos < 0: + pos = maxLength + uId = uId[:pos] + uId = uId.replace(' ', '_').lower() + uId = _idReplaceCharsRe.sub('', uId) + if not uId: + uId = 'id_1' + elif not 'a' <= uId <= 'z': + uId = 'id_' + uId + if uId in idDict: + if uId == 'id_1': + uId = 'id' + i = 1 + while uId + '_' + repr(i) in idDict: + i += 1 + uId = uId + '_' + repr(i) + idDict[uId] = node.uId + +def _convertOldNodeFormat(attrib): + """Return old XML node format attributes from current data. + + Arguments: + attrib -- current node format data attributes + """ + if 'spacebetween' in attrib and not attrib['spacebetween']: + attrib['spacebetween'] = 'n' + for key in ('formathtml', 'bullets', 'tables'): + if key in attrib and attrib[key]: + attrib[key] = 'y' + attrib['line0'] = attrib.get('titleline', '') + del attrib['titleline'] + for i, line in enumerate(attrib['outputlines'], 1): + attrib['line' + repr(i)] = line + del attrib['outputlines'] + del attrib['formatname'] + del attrib['fields'] + return attrib + +def _convertOldFieldFormat(attrib): + """Return old XML field format attributes from current data. + + Arguments: + attrib -- current field format data attributes + """ + if 'fieldtype' in attrib: + attrib['type'] = attrib['fieldtype'] + del attrib['fieldtype'] + for key in ('lines', 'sortkeynum'): + if key in attrib: + attrib[key] = repr(attrib[key]) + if 'sortkeyfwd' in attrib and not attrib['sortkeyfwd']: + attrib['sortkeydir'] = 'r' + if 'evalhtml' in attrib: + attrib['evalhtml'] = 'y' if attrib['evalhtml'] else 'n' + if attrib['type'] in ('Date', 'Time', 'DateTime'): + fieldFormat = attrib.get('format', '') + if fieldFormat: + fieldFormat = fieldFormat.replace('%A', 'dddd') + fieldFormat = fieldFormat.replace('%a', 'ddd') + fieldFormat = fieldFormat.replace('%d', 'dd') + fieldFormat = fieldFormat.replace('%-d', 'd') + fieldFormat = fieldFormat.replace('%B', 'MMMM') + fieldFormat = fieldFormat.replace('%b', 'MMM') + fieldFormat = fieldFormat.replace('%m', 'MM') + fieldFormat = fieldFormat.replace('%-m', 'M') + fieldFormat = fieldFormat.replace('%Y', 'yyyy') + fieldFormat = fieldFormat.replace('%y', 'yy') + fieldFormat = fieldFormat.replace('%H', 'HH') + fieldFormat = fieldFormat.replace('%-H', 'H') + fieldFormat = fieldFormat.replace('%I', 'hh') + fieldFormat = fieldFormat.replace('%-I', 'h') + fieldFormat = fieldFormat.replace('%M', 'mm') + fieldFormat = fieldFormat.replace('%-M', 'm') + fieldFormat = fieldFormat.replace('%S', 'ss') + fieldFormat = fieldFormat.replace('%-S', 's') + fieldFormat = fieldFormat.replace('%f', 'zzz') + fieldFormat = fieldFormat.replace('%p', 'AP') + attrib['format'] = fieldFormat + del attrib['fieldname'] + return attrib + +def _convertOldPrintData(attrib): + """Return old XML print data attributes from current print data. + + Arguments: + attrib -- current print data attributes + """ + for key in ('printlines', 'printwidowcontrol', 'printportrait'): + if key in attrib and not attrib[key]: + attrib[key] = 'n' + for key in ('printindentfactor', 'printpaperwidth', 'printpaperheight', + 'printheadermargin', 'printfootermargin', 'printcolumnspace', + 'printnumcolumns'): + if key in attrib: + attrib[key] = repr(attrib[key]) + if 'printmargins' in attrib: + attrib['printmargins'] = ' '.join([repr(margin) for margin in + attrib['printmargins']]) + return attrib + + +def _createGenericXml(node, addChildren=True): + """Return an ElementTree element with generic XML from this branch. + + Called recursively for children if addChildren is True. + Arguments: + node -- the node to export + addChildren -- add branch if True + """ + nodeFormat = node.formatRef + element = ElementTree.Element(nodeFormat.name) + element.tail = '\n' + for fieldName in nodeFormat.fieldNames(): + text = node.data.get(fieldName, '') + if text and fieldName != imports.genericXmlTextFieldName: + element.set(fieldName, text) + if imports.genericXmlTextFieldName in nodeFormat.fieldDict: + text = node.data.get(imports.genericXmlTextFieldName, '') + if text: + element.text = text + if addChildren and node.childList: + if not text: + element.text = '\n' + for child in node.childList: + element.append(_createGenericXml(child)) + return element + + def _addElemToZip(destZip, rootElem, fileName): - """Adds ElelemnetTree root elements to the given zip file. + """Adds ElementTree root elements to the given zip file. Arguments: destZip -- the destination zip file rootElem -- the root element tree item to add - fileName -- the fiel name or path in the zip file + fileName -- the file name or path in the zip file """ elemTree = ElementTree.ElementTree(rootElem) with io.BytesIO() as output: elemTree.write(output, 'utf-8', True) destZip.writestr(fileName, output.getvalue()) - -def addOdfElement(name, parent=None, attr=None): +def _addOdfElement(name, parent=None, attr=None): """Shortcut function to add elements to the ElementTree. Converts names and attr keys from short version (with ':') to the full URI. @@ -728,8 +1508,124 @@ parent.append(elem) return elem +def _addOdfText(spot, parentElem, addChildren=True, level=1, maxLevel=1): + """Add heading and text elements to the parent element tree element. + + Called recursively for children if addChildren is True. + Returns the maximum indent level used for this branch. + Arguments: + spot -- the spot to export + parentElem -- the parent element tree element to add to + addChildren -- add branch if True + level -- the current tree indent level + maxLevel -- the previous max indent level + """ + headElem = _addOdfElement('text:h', parentElem, + {'text:outline-level': '{0}'.format(level), + 'text:style-name': + 'Heading_20_{0}'.format(level)}) + headElem.text = spot.nodeRef.title(spot) + output = spot.nodeRef.output(True, False, spot) + if output and output[0] == spot.nodeRef.title(spot): + del output[0] # remove first line if same as title + for line in output: + textElem = _addOdfElement('text:p', parentElem, + {'text:outline-level': '{0}'.format(level), + 'text:style-name': 'Text_20_body'}) + textElem.text = line + if addChildren and spot.nodeRef.childList: + for child in spot.childSpots(): + childlevel = _addOdfText(child, parentElem, True, level + 1, + maxLevel) + maxLevel = max(childlevel, maxLevel) + else: + maxLevel = max(level, maxLevel) + return maxLevel + +def _exportHtmlBookmarks(node, addChildren=True): + """Return a text list ith descendant bookmarks in Mozilla format. + + Called recursively for children if addChildren is True. + Arguments: + node -- the node to export + addChildren -- add branch if True + """ + title = node.title() + if not node.childList: + nodeFormat = node.formatRef + field = _findLinkField(nodeFormat) + if field: + linkMatch = fieldformat.linkRegExp.search(node.data. + get(field.name, '')) + if linkMatch: + link = linkMatch.group(1) + return ['
{1}'.format(link, title)] + elif (len(nodeFormat.fieldDict) == 1 and not + node.data.get(nodeFormat.fieldNames()[0], '')): + return ['
'] + result = ['

{0}

'.format(title)] + if addChildren: + result.append('

') + for child in node.childList: + result.extend(_exportHtmlBookmarks(child)) + result.append('

') + return result + +def _exportXbel(node, addChildren=True): + """Return an ElementTree element with XBEL bookmarks from this branch. + + Called recursively for children if addChildren is True. + Arguments: + node -- the node to export + addChildren -- add branch if True + """ + titleElem = ElementTree.Element('title') + titleElem.text = node.title() + if not node.childList: + nodeFormat = node.formatRef + field = _findLinkField(nodeFormat) + if field: + linkMatch = fieldformat.linkRegExp.search(node.data. + get(field.name, '')) + if linkMatch: + link = linkMatch.group(1) + element = ElementTree.Element('bookmark', {'href': link}) + element.append(titleElem) + element.tail = '\n' + return element + elif (len(nodeFormat.fieldDict) == 1 and not + node.data.get(nodeFormat.fieldNames()[0], '')): + element = ElementTree.Element('separator') + element.tail = '\n' + return element + element = ElementTree.Element('folder') + element.append(titleElem) + element.tail = '\n' + if addChildren: + for child in node.childList: + element.append(_exportXbel(child)) + return element + +def _findLinkField(nodeFormat): + """Return the field most likely to contain a bookmark URL. + + Return None if there are no matches. + Arguments: + nodeFormat -- the format to find a field in + """ + availFields = [field for field in nodeFormat.fieldDict.values() if + field.typeName == 'ExternalLink'] + if not availFields: + return None + bestFields = [field for field in availFields if + field.name.lower() == + imports.bookmarkLinkFieldName.lower()] + if bestFields: + return bestFields[0] + return availFields[0] + -class ExportDialog(QtGui.QWizard): +class ExportDialog(QWizard): """Dialog/wizard for setting file export type and options. """ typePage, subtypePage, optionPage = range(3) @@ -740,15 +1636,18 @@ addHeader = False numColumns = 1 navPaneLevels = 2 - exportTypes = ['html', 'text', 'xml', 'odf', 'bookmarks'] + exportTypes = ['html', 'text', 'treeline', 'xml', 'odf', 'bookmarks'] currentType = 'html' exportTypeDescript = {'html': _('&HTML'), 'text': _('&Text'), - 'xml': _('&XML'), 'odf': _('&ODF Outline'), + 'treeline': _('Tree&Line'), + 'xml': _('&XML (generic)'), 'odf': _('&ODF Outline'), 'bookmarks': _('Book&marks')} exportSubtypes = {'html': ['htmlSingle', 'htmlNavSingle','htmlPages', - 'htmlTables'], - 'text': ['textTitles', 'textPlain', 'textTables'], - 'xml': ['xmlGeneric', 'xmlSubtree'], + 'htmlTables', 'htmlLiveLink', 'htmlLiveSingle'], + 'text': ['textTitles', 'textPlain', 'textTableMultiCsv', + 'textTableCsv', 'textTableTab'], + 'treeline': ['oldTreeLine', 'treeLineSubtree'], + 'xml': ['xmlGeneric'], 'odf': ['odfText'], 'bookmarks': ['bookmarksHtml', 'bookmarksXbel']} currentSubtype = 'htmlSingle' @@ -758,21 +1657,31 @@ 'htmlPages': _('Multiple HTML &pages with ' 'navigation pane'), 'htmlTables': _('Multiple HTML &data tables'), + 'htmlLiveLink': _('Live tree view, linked to ' + 'TreeLine file (for web server)'), + 'htmlLiveSingle': _('Live tree view, single file ' + '(embedded data)'), 'textTitles': _('&Tabbed title text'), 'textPlain': _('&Unformatted output of all text'), - 'textTables': _('Tab &delimited table of children'), - 'xmlGeneric': _('Generic &XML output'), - 'xmlSubtree': _('&Subtree in TreeLine format'), + 'textTableMultiCsv': _('&Comma delimited (CSV) table ' + 'of descendants (level numbers)'), + 'textTableCsv': _('Comma &delimited (CSV) table ' + 'of children (single level)'), + 'textTableTab': _('Tab &delimited table of children ' + '(&single level)'), + 'oldTreeLine': _('&Old TreeLine (2.0.x)'), + 'treeLineSubtree': _('&TreeLine Subtree'), 'bookmarksHtml': _('&HTML format bookmarks'), 'bookmarksXbel': _('&XBEL format bookmarks')} - disableEntireTree = {'textTables'} - disableSelBranches = set() - disableSelNodes = {'htmlNavSingle', 'htmlPages', 'htmlTables'} + disableEntireTree = {'textTableCsv', 'textTableTab', 'treeLineSubtree'} + disableSelBranches = {'htmlLiveLink'} + disableSelNodes = {'htmlNavSingle', 'htmlPages', 'htmlTables', + 'htmlLiveLink', 'textTableMultiCsv'} enableRootNode = {'htmlSingle', 'htmlNavSingle', 'textTitles', - 'textPlain', 'ODF'} - forceRootNodeOff = {'textTables'} + 'textPlain', 'textTableMultiCsv', 'ODF'} + forceRootNodeOff = {'textTableCsv', 'textTableTab'} enableOpenOnly = {'htmlSingle', 'htmlNavSingle', 'textTitles', - 'textPlain', 'ODF'} + 'textPlain', 'textTableMultiCsv', 'ODF'} enableHeader = {'htmlSingle', 'htmlNavSingle', 'htmlTables'} enableColumns = {'htmlSingle'} enableNavLevels = {'htmlNavSingle'} @@ -784,18 +1693,18 @@ selectionAvail -- false if no nodes or branches are selected parent -- the parent window """ - super().__init__(parent, QtCore.Qt.Dialog) - self.setWindowFlags(QtCore.Qt.Dialog | QtCore.Qt.WindowTitleHint | - QtCore.Qt.WindowSystemMenuHint) + super().__init__(parent, Qt.Dialog) + self.setWindowFlags(Qt.Dialog | Qt.WindowTitleHint | + Qt.WindowCloseButtonHint) self.setWindowTitle(_('File Export')) - self.setWizardStyle(QtGui.QWizard.ClassicStyle) + self.setWizardStyle(QWizard.ClassicStyle) self.setPage(ExportDialog.typePage, ExportDialogTypePage()) self.setPage(ExportDialog.subtypePage, ExportDialogSubtypePage()) self.setPage(ExportDialog.optionPage, ExportDialogOptionPage(selectionAvail)) -class ExportDialogTypePage(QtGui.QWizardPage): +class ExportDialogTypePage(QWizardPage): """A wizard page for selecting the main export type. """ def __init__(self, parent=None): @@ -806,13 +1715,13 @@ """ super().__init__(parent) - topLayout = QtGui.QVBoxLayout(self) + topLayout = QVBoxLayout(self) self.setLayout(topLayout) self.setTitle(_('Choose export format type')) - typeButtons = QtGui.QButtonGroup(self) + typeButtons = QButtonGroup(self) for id, exportType in enumerate(ExportDialog.exportTypes): - button = QtGui.QRadioButton(ExportDialog. + button = QRadioButton(ExportDialog. exportTypeDescript[exportType]) typeButtons.addButton(button, id) topLayout.addWidget(button) @@ -839,7 +1748,7 @@ return ExportDialog.optionPage -class ExportDialogSubtypePage(QtGui.QWizardPage): +class ExportDialogSubtypePage(QWizardPage): """A wizard page for selecting the export subtype. """ def __init__(self, parent=None): @@ -850,10 +1759,10 @@ """ super().__init__(parent) - topLayout = QtGui.QVBoxLayout(self) + topLayout = QVBoxLayout(self) self.setLayout(topLayout) self.setTitle(_('Choose export format subtype')) - self.subtypeButtons = QtGui.QButtonGroup(self) + self.subtypeButtons = QButtonGroup(self) self.subtypeButtons.buttonClicked[int].connect(self.setCurrentSubtype) def initializePage(self): @@ -868,7 +1777,7 @@ for id, subtype in enumerate(ExportDialog. exportSubtypes[ExportDialog.currentType]): - button = QtGui.QRadioButton(ExportDialog.subtypeDescript[subtype]) + button = QRadioButton(ExportDialog.subtypeDescript[subtype]) self.subtypeButtons.addButton(button, id) topLayout.addWidget(button) if subtype == ExportDialog.currentSubtype: @@ -884,7 +1793,7 @@ ExportDialog.currentSubtype = availSubtypes[buttonId] -class ExportDialogOptionPage(QtGui.QWizardPage): +class ExportDialogOptionPage(QWizardPage): """A wizard page for selecting other export options. """ def __init__(self, selectionAvail=True, parent=None): @@ -897,65 +1806,65 @@ super().__init__(parent) self.selectionAvail = selectionAvail - topLayout = QtGui.QVBoxLayout(self) + topLayout = QVBoxLayout(self) self.setLayout(topLayout) self.setTitle(_('Choose export options')) - whatGroupBox = QtGui.QGroupBox(_('What to Export')) + whatGroupBox = QGroupBox(_('What to Export')) topLayout.addWidget(whatGroupBox) - whatLayout = QtGui.QVBoxLayout(whatGroupBox) - self.whatButtons = QtGui.QButtonGroup(self) - treeButton = QtGui.QRadioButton(_('&Entire tree')) + whatLayout = QVBoxLayout(whatGroupBox) + self.whatButtons = QButtonGroup(self) + treeButton = QRadioButton(_('&Entire tree')) self.whatButtons.addButton(treeButton, ExportDialog.entireTree) whatLayout.addWidget(treeButton) - branchButton = QtGui.QRadioButton(_('Selected &branches')) + branchButton = QRadioButton(_('Selected &branches')) self.whatButtons.addButton(branchButton, ExportDialog.selectBranch) whatLayout.addWidget(branchButton) - nodeButton = QtGui.QRadioButton(_('Selected &nodes')) + nodeButton = QRadioButton(_('Selected &nodes')) self.whatButtons.addButton(nodeButton, ExportDialog.selectNode) whatLayout.addWidget(nodeButton) self.whatButtons.button(ExportDialog.exportWhat).setChecked(True) self.whatButtons.buttonClicked[int].connect(self.setExportWhat) - optionBox = QtGui.QGroupBox(_('Other Options')) + optionBox = QGroupBox(_('Other Options')) topLayout.addWidget(optionBox) - optionLayout = QtGui.QVBoxLayout(optionBox) - self.rootButton = QtGui.QCheckBox(_('&Include root node')) + optionLayout = QVBoxLayout(optionBox) + self.rootButton = QCheckBox(_('&Include root nodes')) optionLayout.addWidget(self.rootButton) self.rootButton.setChecked(ExportDialog.includeRoot) self.rootButton.toggled.connect(self.setIncludeRoot) - self.openOnlyButton = QtGui.QCheckBox(_('&Only open node children')) + self.openOnlyButton = QCheckBox(_('&Only open node children')) optionLayout.addWidget(self.openOnlyButton) self.openOnlyButton.setChecked(ExportDialog.openOnly) self.openOnlyButton.toggled.connect(self.setOpenOnly) - self.headerButton = QtGui.QCheckBox(_('Include &print header && ' + self.headerButton = QCheckBox(_('Include &print header && ' 'footer')) optionLayout.addWidget(self.headerButton) self.headerButton.setChecked(ExportDialog.addHeader) self.headerButton.toggled.connect(self.setAddHeader) - columnLayout = QtGui.QHBoxLayout() + columnLayout = QHBoxLayout() optionLayout.addLayout(columnLayout) - self.numColSpin = QtGui.QSpinBox() + self.numColSpin = QSpinBox() columnLayout.addWidget(self.numColSpin) self.numColSpin.setRange(1, 9) self.numColSpin.setMaximumWidth(40) self.numColSpin.setValue(ExportDialog.numColumns) - self.colLabel = QtGui.QLabel(_('&Columns')) + self.colLabel = QLabel(_('&Columns')) columnLayout.addWidget(self.colLabel) self.colLabel.setBuddy(self.numColSpin) self.numColSpin.valueChanged.connect(self.setNumColumns) - navLevelsLayout = QtGui.QHBoxLayout() + navLevelsLayout = QHBoxLayout() optionLayout.addLayout(navLevelsLayout) - self.navLevelsSpin = QtGui.QSpinBox() + self.navLevelsSpin = QSpinBox() navLevelsLayout.addWidget(self.navLevelsSpin) self.navLevelsSpin.setRange(1, 9) self.navLevelsSpin.setMaximumWidth(40) self.navLevelsSpin.setValue(ExportDialog.navPaneLevels) - self.navLevelsLabel = QtGui.QLabel(_('Navigation pane &levels')) + self.navLevelsLabel = QLabel(_('Navigation pane &levels')) navLevelsLayout.addWidget(self.navLevelsLabel) self.navLevelsLabel.setBuddy(self.navLevelsSpin) self.navLevelsSpin.valueChanged.connect(self.setNavLevels) @@ -972,7 +1881,18 @@ self.selectionAvail) num = 0 while not self.whatButtons.checkedButton().isEnabled(): - self.whatButtons.button(num).setChecked(True) + try: + self.whatButtons.button(num).setChecked(True) + except AttributeError: + QMessageBox.warning(self, 'TreeLine', + _('Must select nodes prior to export')) + parent = self.parent() + while parent: + try: + parent.reject() + return + except AttributeError: + parent = parent.parent() num += 1 if (subtype in ExportDialog.enableRootNode and diff -Nru treeline-2.0.2/source/fieldformat.py treeline-3.0.1/source/fieldformat.py --- treeline-2.0.2/source/fieldformat.py 2015-03-27 01:17:13.000000000 +0000 +++ treeline-3.0.1/source/fieldformat.py 2018-09-29 16:40:05.000000000 +0000 @@ -4,7 +4,7 @@ # fieldformat.py, provides a class to handle field format types # # TreeLine, an information storage program -# Copyright (C) 2015, Douglas W. Bell +# Copyright (C) 2018, 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 @@ -13,76 +13,98 @@ #****************************************************************************** import re -import os.path -import xml.sax.saxutils -from PyQt4 import QtCore, QtGui -import globalref +import sys +import enum +import datetime +import xml.sax.saxutils as saxutils import gennumber import genboolean import numbering -import dataeditors import matheval import urltools +import globalref fieldTypes = [N_('Text'), N_('HtmlText'), N_('OneLineText'), N_('SpacedText'), - N_('Number'), N_('Math'), N_('Numbering'), N_('Boolean'), - N_('Date'), N_('Time'), N_('Choice'), N_('AutoChoice'), - N_('Combination'), N_('AutoCombination'), N_('ExternalLink'), - N_('InternalLink'), N_('Picture'), N_('RegularExpression')] + N_('Number'), N_('Math'), N_('Numbering'), + N_('Date'), N_('Time'), N_('DateTime'), N_('Boolean'), + N_('Choice'), N_('AutoChoice'), N_('Combination'), + N_('AutoCombination'), N_('ExternalLink'), N_('InternalLink'), + N_('Picture'), N_('RegularExpression')] +translatedFieldTypes = [_(name) for name in fieldTypes] _errorStr = '#####' _dateStampString = _('Now') _timeStampString = _('Now') -numericResult, dateResult, timeResult, booleanResult, textResult = range(5) -mathResultStr = {numericResult: 'numeric', dateResult: 'date', - timeResult: 'time', booleanResult: 'boolean', - textResult: 'text'} -mathResultVar = {mathResultStr[key]: key for key in mathResultStr} -_mathResultBlank = {numericResult: 0, dateResult: 0, timeResult: 0, - booleanResult: False, textResult: ''} +MathResult = enum.Enum('MathResult', 'number date time boolean text') +_mathResultBlank = {MathResult.number: 0, MathResult.date: 0, + MathResult.time: 0, MathResult.boolean: False, + MathResult.text: ''} +_multipleSpaceRegEx = re.compile(r' {2,}') linkRegExp = re.compile(r']*href="([^"]+)"[^>]*>(.*?)', re.I | re.S) -linkSeparateNameRegExp = re.compile(r'(.*?)\[(.*)\]') -imageRegExp = re.compile(r']*src="([^"]+)"[^>]*>', re.I | re.S) +linkSeparateNameRegExp = re.compile(r'(.*) \[(.*)\]\s*$') +_imageRegExp = re.compile(r']*src="([^"]+)"[^>]*>', re.I | re.S) class TextField: """Class to handle a rich-text field format type. - + Stores options and format strings for a text field type. Provides methods to return formatted data. """ typeName = 'Text' defaultFormat = '' - useRichText = True + showRichTextInCell = True + evalHtmlDefault = False + fixEvalHtmlSetting = True defaultNumLines = 1 - editorClass = dataeditors.RichTextEditor + editorClassName = 'RichTextEditor' + sortTypeStr = '80_text' + supportsInitDefault = True formatHelpMenuList = [] - def __init__(self, name, attrs=None): + def __init__(self, name, formatData=None): """Initialize a field format type. Arguments: name -- the field name string - attrs -- the attributes that define this field's format + formatData -- the dict that defines this field's format """ self.name = name - if not attrs: - attrs = {} - self.setFormat(attrs.get('format', type(self).defaultFormat)) - self.prefix = attrs.get('prefix', '') - self.suffix = attrs.get('suffix', '') - self.initDefault = attrs.get('init', '') - self.numLines = int(attrs.get('lines', type(self).defaultNumLines)) - self.sortKeyNum = int(attrs.get('sortkeynum', '0')) - self.sortKeyForward = not attrs.get('sortkeydir', '').startswith('r') + if not formatData: + formatData = {} + self.prefix = formatData.get('prefix', '') + self.suffix = formatData.get('suffix', '') + self.initDefault = formatData.get('init', '') + self.numLines = formatData.get('lines', type(self).defaultNumLines) + self.sortKeyNum = formatData.get('sortkeynum', 0) + self.sortKeyForward = formatData.get('sortkeyfwd', True) + self.evalHtml = self.evalHtmlDefault + if not self.fixEvalHtmlSetting: + self.evalHtml = formatData.get('evalhtml', self.evalHtmlDefault) self.useFileInfo = False self.showInDialog = True - # following lines used to convert from old TreeLine 1.x versions only: - self.oldHasHtml = attrs.get('html', '').startswith('y') - self.oldRef = attrs.get('ref', '').startswith('y') - self.oldLinkAltField = attrs.get('linkalt', '') - self.oldTypeName = '' - attrType = attrs.get('type', '') - if attrType and attrType != self.typeName: - self.oldTypeName = attrType + self.setFormat(formatData.get('format', type(self).defaultFormat)) + + def formatData(self): + """Return a dictionary of this field's format settings. + """ + formatData = {'fieldname': self.name, 'fieldtype': self.typeName} + if self.format: + formatData['format'] = self.format + if self.prefix: + formatData['prefix'] = self.prefix + if self.suffix: + formatData['suffix'] = self.suffix + if self.initDefault: + formatData['init'] = self.initDefault + if self.numLines != self.defaultNumLines: + formatData['lines'] = self.numLines + if self.sortKeyNum > 0: + formatData['sortkeynum'] = self.sortKeyNum + if not self.sortKeyForward: + formatData['sortkeyfwd'] = False + if (not self.fixEvalHtmlSetting and + self.evalHtml != self.evalHtmlDefault): + formatData['evalhtml'] = self.evalHtml + return formatData def setFormat(self, format): """Set the format string and initialize as required. @@ -93,16 +115,18 @@ """ self.format = format - def outputText(self, node, titleMode, formatHtml): + def outputText(self, node, titleMode, 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 formatHtml -- if False, escapes HTML from prefix & suffix + spotRef -- optional, used for ancestor field refs """ - if self.useFileInfo: - node = node.modelRef.fileInfoNode + if self.useFileInfo and node.spotRefs: + # get file info node if not already the file info node + node = node.treeStructureRef().fileInfoNode storedText = node.data.get(self.name, '') if storedText: return self.formatOutput(storedText, titleMode, formatHtml) @@ -124,8 +148,8 @@ prefix = removeMarkup(prefix) suffix = removeMarkup(suffix) elif not formatHtml: - prefix = xml.sax.saxutils.escape(prefix) - suffix = xml.sax.saxutils.escape(suffix) + prefix = saxutils.escape(prefix) + suffix = saxutils.escape(suffix) return '{0}{1}{2}'.format(prefix, storedText, suffix) def editorText(self, node): @@ -159,6 +183,15 @@ """ return editorText + def storedTextFromTitle(self, titleText): + """Return new text to be stored based on title text edits. + + Overloads may raise a ValueError if the data does not match the format. + Arguments: + titleText -- the new title text + """ + return self.storedText(saxutils.escape(titleText)) + def getInitDefault(self): """Return the initial stored value for newly created nodes. """ @@ -175,36 +208,20 @@ def getEditorInitDefault(self): """Return initial value in editor format. - - The function for default text field just returns the initial value. """ - return self.formatEditorText(self.initDefault) + value = '' + if self.supportsInitDefault: + try: + value = self.formatEditorText(self.initDefault) + except ValueError: + pass + return value def initDefaultChoices(self): """Return a list of choices for setting the init default. """ return [] - def xmlAttr(self): - """Return a dictionary of this field's attributes. - """ - attrs = {'type': self.typeName} - if self.format: - attrs['format'] = self.format - if self.prefix: - attrs['prefix'] = self.prefix - if self.suffix: - attrs['suffix'] = self.suffix - if self.initDefault: - attrs['init'] = self.initDefault - if self.numLines != self.defaultNumLines: - attrs['lines'] = repr(self.numLines) - if self.sortKeyNum > 0: - attrs['sortkeynum'] = repr(self.sortKeyNum) - if not self.sortKeyForward: - attrs['sortkeydir'] = 'r' - return attrs - def mathValue(self, node, zeroBlanks=True): """Return a value to be used in math field equations. @@ -225,17 +242,7 @@ node -- the tree item storing the data """ storedText = node.data.get(self.name, '') - storedText = removeMarkup(storedText) - return storedText.lower() - - def sortKey(self, node): - """Return a tuple with field type and comparison value for sorting. - - Allows different types to be sorted. - Arguments: - node -- the tree item storing the data - """ - return ('80_text', self.compareValue(node)) + return self.adjustedCompareValue(storedText) def adjustedCompareValue(self, value): """Return value adjusted like the compareValue for use in conditionals. @@ -247,6 +254,15 @@ value = removeMarkup(value) return value.lower() + def sortKey(self, node): + """Return a tuple with field type and comparison value for sorting. + + Allows different types to be sorted. + Arguments: + node -- the tree item storing the data + """ + return (self.sortTypeStr, self.compareValue(node)) + def changeType(self, newType): """Change this field's type to newType with a default format. @@ -255,6 +271,8 @@ """ self.__class__ = globals()[newType + 'Field'] self.setFormat(self.defaultFormat) + if self.fixEvalHtmlSetting: + self.evalHtml = self.evalHtmlDefault def sepName(self): """Return the name enclosed with {* *} separators @@ -271,22 +289,32 @@ class HtmlTextField(TextField): """Class to handle an HTML text field format type - + Stores options and format strings for an HTML text field type. Does not use the rich text editor. Provides methods to return formatted data. """ typeName = 'HtmlText' - useRichText = False - editorClass = dataeditors.HtmlTextEditor - def __init__(self, name, attrs=None): + showRichTextInCell = False + evalHtmlDefault = True + editorClassName = 'HtmlTextEditor' + def __init__(self, name, formatData=None): """Initialize a field format type. Arguments: name -- the field name string - attrs -- the attributes that define this field's format + formatData -- the dict that defines this field's format + """ + super().__init__(name, formatData) + + def storedTextFromTitle(self, titleText): + """Return new text to be stored based on title text edits. + + Overloads may raise a ValueError if the data does not match the format. + Arguments: + titleText -- the new title text """ - super().__init__(name, attrs) + return self.storedText(titleText) class OneLineTextField(TextField): @@ -296,15 +324,15 @@ Provides methods to return formatted data. """ typeName = 'OneLineText' - editorClass = dataeditors.OneLineTextEditor - def __init__(self, name, attrs=None): + editorClassName = 'OneLineTextEditor' + def __init__(self, name, formatData=None): """Initialize a field format type. Arguments: name -- the field name string - attrs -- the attributes that define this field's format + formatData -- the dict that defines this field's format """ - super().__init__(name, attrs) + super().__init__(name, formatData) def formatOutput(self, storedText, titleMode, formatHtml): """Return formatted output text from stored text for this field. @@ -327,24 +355,25 @@ return storedText.split('
', 1)[0] -class SpacedTextField(HtmlTextField): +class SpacedTextField(TextField): """Class to handle a preformatted text field format type. - + Stores options and format strings for a spaced text field type. Uses

 tags to preserve spacing.
     Does not use the rich text editor.
     Provides methods to return formatted data.
     """
     typeName = 'SpacedText'
-    editorClass = dataeditors.PlainTextEditor
-    def __init__(self, name, attrs=None):
+    showRichTextInCell = False
+    editorClassName = 'PlainTextEditor'
+    def __init__(self, name, formatData=None):
         """Initialize a field format type.
 
         Arguments:
             name -- the field name string
-            attrs -- the attributes that define this field's format
+            formatData -- the dict that defines this field's format
         """
-        super().__init__(name, attrs)
+        super().__init__(name, formatData)
 
     def formatOutput(self, storedText, titleMode, formatHtml):
         """Return formatted output text from stored text for this field.
@@ -358,6 +387,30 @@
             storedText = '
{0}
'.format(storedText) return super().formatOutput(storedText, titleMode, formatHtml) + def formatEditorText(self, storedText): + """Return text formatted for use in the data editor. + + Arguments: + storedText -- the source text to format + """ + return saxutils.unescape(storedText) + + def storedText(self, editorText): + """Return new text to be stored based on text from the data editor. + + Arguments: + editorText -- the new text entered into the editor + """ + return saxutils.escape(editorText) + + def storedTextFromTitle(self, titleText): + """Return new text to be stored based on title text edits. + + Arguments: + titleText -- the new title text + """ + return self.storedText(titleText) + class NumberField(HtmlTextField): """Class to handle a general number field format type. @@ -367,7 +420,9 @@ """ typeName = 'Number' defaultFormat = '#.##' - editorClass = dataeditors.LineEditor + evalHtmlDefault = False + editorClassName = 'LineEditor' + sortTypeStr = '20_num' formatHelpMenuList = [(_('Optional Digit\t#'), '#'), (_('Required Digit\t0'), '0'), (_('Digit or Space (external)\t'), ' '), @@ -375,8 +430,8 @@ (_('Decimal Point\t.'), '.'), (_('Decimal Comma\t,'), ','), ('', ''), - (_('Comma Separator\t\,'), '\,'), - (_('Dot Separator\t\.'), '\.'), + (_('Comma Separator\t\\,'), '\\,'), + (_('Dot Separator\t\\.'), '\\.'), (_('Space Separator (internal)\t'), ' '), ('', ''), (_('Optional Sign\t-'), '-'), @@ -385,14 +440,14 @@ (_('Exponent (capital)\tE'), 'E'), (_('Exponent (small)\te'), 'e')] - def __init__(self, name, attrs=None): + def __init__(self, name, formatData=None): """Initialize a field format type. Arguments: name -- the field name string - attrs -- the attributes that define this field's format + formatData -- the dict that defines this field's format """ - super().__init__(name, attrs) + super().__init__(name, formatData) def formatOutput(self, storedText, titleMode, formatHtml): """Return formatted output text from stored text for this field. @@ -444,28 +499,6 @@ return gennumber.GenNumber(storedText).num return 0 if zeroBlanks else None - def compareValue(self, node): - """Return a value for comparison to other nodes and for sorting. - - Returns lowercase text for text fields or numbers for non-text fields. - Arguments: - node -- the tree item storing the data - """ - storedText = node.data.get(self.name, '') - try: - return gennumber.GenNumber(storedText).num - except ValueError: - return 0 - - def sortKey(self, node): - """Return a tuple with field type and comparison values for sorting. - - Allows different types to be sorted. - Arguments: - node -- the tree item storing the data - """ - return ('20_num', self.compareValue(node)) - def adjustedCompareValue(self, value): """Return value adjusted like the compareValue for use in conditionals. @@ -479,25 +512,27 @@ return 0 -class MathField(NumberField): +class MathField(HtmlTextField): """Class to handle a math calculation field type. Stores options and format strings for a math field type. Provides methods to return formatted data. """ typeName = 'Math' - editorClass = dataeditors.ReadOnlyEditor - def __init__(self, name, attrs=None): + defaultFormat = '#.##' + evalHtmlDefault = False + editorClassName = 'ReadOnlyEditor' + def __init__(self, name, formatData=None): """Initialize a field format type. Arguments: name -- the field name string - attrs -- the attributes that define this field's format + formatData -- the attributes that define this field's format """ - super().__init__(name, attrs) + super().__init__(name, formatData) self.equation = None - self.resultType = mathResultVar[attrs.get('resulttype', 'numeric')] - equationText = attrs.get('eqn', '').strip() + self.resultType = MathResult[formatData.get('resulttype', 'number')] + equationText = formatData.get('eqn', '').strip() if equationText: self.equation = matheval.MathEquation(equationText) try: @@ -505,6 +540,18 @@ except ValueError: self.equation = None + def formatData(self): + """Return a dictionary of this field's attributes. + + Add the math equation to the standard XML output. + """ + formatData = super().formatData() + if self.equation: + formatData['eqn'] = self.equation.equationText() + if self.resultType != MathResult.number: + formatData['resulttype'] = self.resultType.name + return formatData + def setFormat(self, format): """Set the format string and initialize as required. @@ -513,7 +560,7 @@ """ if not hasattr(self, 'equation'): self.equation = None - self.resultType = numericResult + self.resultType = MathResult.number super().setFormat(format) def formatOutput(self, storedText, titleMode, formatHtml): @@ -525,26 +572,22 @@ formatHtml -- if False, escapes HTML from prefix & suffix """ text = storedText - if self.resultType == numericResult: - return super().formatOutput(text, titleMode, formatHtml) - if self.resultType == dateResult: - date = QtCore.QDate.fromString(text, QtCore.Qt.ISODate) - if date.isValid(): - text = date.toString(self.format) - else: - text = _errorStr - elif self.resultType == timeResult: - time = QtCore.QTime.fromString(text) - if time.isValid(): - text = time.toString(self.format) - else: - text = _errorStr - elif self.resultType == booleanResult: - try: + try: + if self.resultType == MathResult.number: + text = gennumber.GenNumber(text).numStr(self.format) + elif self.resultType == MathResult.date: + date = datetime.datetime.strptime(text, + DateField.isoFormat).date() + text = date.strftime(adjOutDateFormat(self.format)) + elif self.resultType == MathResult.time: + time = datetime.datetime.strptime(text, + TimeField.isoFormat).time() + text = time.strftime(adjOutDateFormat(self.format)) + elif self.resultType == MathResult.boolean: text = genboolean.GenBoolean(text).boolStr(self.format) - except ValueError: - text = _errorStr - return HtmlTextField.formatOutput(self, text, titleMode, formatHtml) + except ValueError: + text = _errorStr + return super().formatOutput(text, titleMode, formatHtml) def formatEditorText(self, storedText): """Return text formatted for use in the data editor. @@ -555,23 +598,25 @@ """ if not storedText: return '' - if self.resultType == numericResult: - return super().formatEditorText(storedText) - if self.resultType == dateResult: - date = QtCore.QDate.fromString(storedText, QtCore.Qt.ISODate) - if date.isValid(): - editorFormat = globalref.genOptions.getValue('EditDateFormat') - return date.toString(editorFormat) - elif self.resultType == timeResult: - time = QtCore.QTime.fromString(storedText) - if time.isValid(): - editorFormat = globalref.genOptions.getValue('EditTimeFormat') - return time.toString(editorFormat) - elif self.resultType == booleanResult: + if self.resultType == MathResult.number: + return gennumber.GenNumber(storedText).numStr(self.format) + if self.resultType == MathResult.date: + date = datetime.datetime.strptime(storedText, + DateField.isoFormat).date() + editorFormat = adjOutDateFormat(globalref. + genOptions['EditDateFormat']) + return date.strftime(editorFormat) + if self.resultType == MathResult.time: + time = datetime.datetime.strptime(storedText, + TimeField.isoFormat).time() + editorFormat = adjOutDateFormat(globalref. + genOptions['EditTimeFormat']) + return time.strftime(editorFormat) + if self.resultType == MathResult.boolean: return genboolean.GenBoolean(storedText).boolStr(self.format) - else: - return storedText - raise ValueError + if storedText == _errorStr: + raise ValueError + return storedText def equationText(self): """Return the current equation text. @@ -595,31 +640,22 @@ return _errorStr if num == None: return '' - if self.resultType in (numericResult, booleanResult, textResult): - return str(num) - elif self.resultType == dateResult: - date = DateField.refDate.addDays(num) - if not date.isValid(): - return _errorStr - return date.toString(QtCore.Qt.ISODate) - else: - time = TimeField.refTime.addSecs(num) - if not time.isValid(): - return _errorStr - return time.toString() + if self.resultType == MathResult.date: + date = DateField.refDate + datetime.timedelta(days=num) + return date.strftime(DateField.isoFormat) + if self.resultType == MathResult.time: + dateTime = datetime.datetime.combine(DateField.refDate, + TimeField.refTime) + dateTime = dateTime + datetime.timedelta(seconds=num) + time = dateTime.time() + return time.strftime(TimeField.isoFormat) + return str(num) return '' - def xmlAttr(self): - """Return a dictionary of this field's attributes. - - Add the math equation to the standard XML output. + def resultClass(self): + """Return the result type's field class. """ - attrs = super().xmlAttr() - if self.equation: - attrs['eqn'] = self.equation.equationText() - if self.resultType != numericResult: - attrs['resulttype'] = mathResultStr[self.resultType] - return attrs + return globals()[self.resultType.name.capitalize() + 'Field'] def changeResultType(self, resultType): """Change the result type and reset the output format. @@ -629,29 +665,71 @@ """ if resultType != self.resultType: self.resultType = resultType - if resultType == numericResult: - self.setFormat(self.defaultFormat) - elif resultType == dateResult: - self.setFormat(DateField.defaultFormat) - elif resultType == timeResult: - self.setFormat(TimeField.defaultFormat) - elif resultType == booleanResult: - self.setFormat(BooleanField.defaultFormat) - else: - self.setFormat('') + self.setFormat(self.resultClass().defaultFormat) + + def mathValue(self, node, zeroBlanks=True): + """Return a numeric value to be used in math field equations. + + Return None if blank and not zeroBlanks, + raise a ValueError if it isn't valid. + Arguments: + node -- the tree item storing the data + zeroBlanks -- replace blank field values with zeros if True + """ + storedText = node.data.get(self.name, '') + if storedText: + if self.resultType == MathResult.number: + return gennumber.GenNumber(storedText).num + if self.resultType == MathResult.date: + date = datetime.datetime.strptime(storedText, + DateField.isoFormat).date() + return (date - DateField.refDate).days + if self.resultType == MathResult.time: + time = datetime.datetime.strptime(storedText, + TimeField.isoFormat).time() + return (time - TimeField.refTime).seconds + if self.resultType == MathResult.boolean: + return genboolean.GenBoolean(storedText).value + return removeMarkup(storedText) + return _mathResultBlank[self.resultType] if zeroBlanks else None + + def adjustedCompareValue(self, value): + """Return value adjusted like the compareValue for use in conditionals. + + Number version converts to a numeric value. + Arguments: + value -- the comparison value to adjust + """ + try: + if self.resultType == MathResult.number: + return gennumber.GenNumber(value).num + if self.resultType == MathResult.date: + date = datetime.datetime.strptime(value, + DateField.isoFormat).date() + return date.strftime(DateField.isoFormat) + if self.resultType == MathResult.time: + time = datetime.datetime.strptime(value, + TimeField.isoFormat).time() + return time.strftime(TimeField.isoFormat) + if self.resultType == MathResult.boolean: + return genboolean.GenBoolean(value).value + return value.lower() + except ValueError: + return 0 + + def sortKey(self, node): + """Return a tuple with field type and comparison value for sorting. + + Allows different types to be sorted. + Arguments: + node -- the tree item storing the data + """ + return (self.resultClass().sortTypeStr, self.compareValue(node)) def getFormatHelpMenuList(self): """Return the list of descriptions and keys for the format help menu. """ - if self.resultType == numericResult: - return self.formatHelpMenuList - if self.resultType == dateResult: - return DateField.formatHelpMenuList - if self.resultType == timeResult: - return TimeField.formatHelpMenuList - if self.resultType == booleanResult: - return BooleanField.formatHelpMenuList - return [] + return self.resultClass().formatHelpMenuList class NumberingField(HtmlTextField): @@ -662,7 +740,9 @@ """ typeName = 'Numbering' defaultFormat = '1..' - editorClass = dataeditors.LineEditor + evalHtmlDefault = False + editorClassName = 'LineEditor' + sortTypeStr = '10_numbering' formatHelpMenuList = [(_('Number\t1'), '1'), (_('Capital Letter\tA'), 'A'), (_('Small Letter\ta'), 'a'), @@ -679,15 +759,15 @@ 'I../A../1../a)/i)'), (_('Section Example\t1.1.1.1'), '1.1.1.1')] - def __init__(self, name, attrs=None): + def __init__(self, name, formatData=None): """Initialize a field format type. Arguments: name -- the field name string - attrs -- the attributes that define this field's format + formatData -- the attributes that define this field's format """ self.numFormat = None - super().__init__(name, attrs) + super().__init__(name, formatData) def setFormat(self, format): """Set the format string and initialize as required. @@ -734,30 +814,6 @@ checkData = [int(num) for num in editorText.split('.')] return editorText - def compareValue(self, node): - """Return a value for comparison to other nodes and for sorting. - - Returns lowercase text for text fields or numbers for non-text fields. - Arguments: - node -- the tree item storing the data - """ - storedText = node.data.get(self.name, '') - if storedText: - try: - return [int(num) for num in editorText.split('.')] - except ValueError: - pass - return [0] - - def sortKey(self, node): - """Return a tuple with field type and comparison values for sorting. - - Allows different types to be sorted. - Arguments: - node -- the tree item storing the data - """ - return ('10_numbering', self.compareValue(node)) - def adjustedCompareValue(self, value): """Return value adjusted like the compareValue for use in conditionals. @@ -773,40 +829,39 @@ return [0] -class ChoiceField(HtmlTextField): - """Class to handle a field with pre-defined, individual text choices. +class DateField(HtmlTextField): + """Class to handle a general date field format type. - Stores options and format strings for a choice field type. + Stores options and format strings for a date field type. Provides methods to return formatted data. """ - typeName = 'Choice' - editSep = '/' - defaultFormat = '1/2/3/4' - editorClass = dataeditors.ComboEditor - numChoiceColumns = 1 - autoAddChoices = False - formatHelpMenuList = [(_('Separator\t/'), '/'), ('', ''), - (_('"/" Character\t//'), '//'), ('', ''), - (_('Example\t1/2/3/4'), '1/2/3/4')] - def __init__(self, name, attrs=None): + typeName = 'Date' + defaultFormat = '%B %-d, %Y' + isoFormat = '%Y-%m-%d' + evalHtmlDefault = False + editorClassName = 'DateEditor' + refDate = datetime.date(1970, 1, 1) + sortTypeStr = '40_date' + formatHelpMenuList = [(_('Day (1 or 2 digits)\t%-d'), '%-d'), + (_('Day (2 digits)\t%d'), '%d'), ('', ''), + (_('Weekday Abbreviation\t%a'), '%a'), + (_('Weekday Name\t%A'), '%A'), ('', ''), + (_('Month (1 or 2 digits)\t%-m'), '%-m'), + (_('Month (2 digits)\t%m'), '%m'), + (_('Month Abbreviation\t%b'), '%b'), + (_('Month Name\t%B'), '%B'), ('', ''), + (_('Year (2 digits)\t%y'), '%y'), + (_('Year (4 digits)\t%Y'), '%Y'), ('', ''), + (_('Week Number (0 to 53)\t%-U'), '%-U'), + (_('Day of year (1 to 366)\t%-j'), '%-j')] + def __init__(self, name, formatData=None): """Initialize a field format type. Arguments: name -- the field name string - attrs -- the attributes that define this field's format - """ - super().__init__(name, attrs) - - def setFormat(self, format): - """Set the format string and initialize as required. - - Arguments: - format -- the new format string + formatData -- the dict that defines this field's format """ - super().setFormat(format) - self.choiceList = self.splitText(self.format) - self.choices = set([xml.sax.saxutils.escape(choice) for choice in - self.choiceList]) + super().__init__(name, formatData) def formatOutput(self, storedText, titleMode, formatHtml): """Return formatted output text from stored text for this field. @@ -816,9 +871,13 @@ titleMode -- if True, removes all HTML markup for tree title use formatHtml -- if False, escapes HTML from prefix & suffix """ - if storedText not in self.choices: - storedText = _errorStr - return super().formatOutput(storedText, titleMode, formatHtml) + try: + date = datetime.datetime.strptime(storedText, + DateField.isoFormat).date() + text = date.strftime(adjOutDateFormat(self.format)) + except ValueError: + text = _errorStr + return super().formatOutput(text, titleMode, formatHtml) def formatEditorText(self, storedText): """Return text formatted for use in the data editor. @@ -827,43 +886,602 @@ Arguments: storedText -- the source text to format """ - if storedText and storedText not in self.choices: - raise ValueError - return xml.sax.saxutils.unescape(storedText) + if not storedText: + return '' + date = datetime.datetime.strptime(storedText, + DateField.isoFormat).date() + editorFormat = adjOutDateFormat(globalref.genOptions['EditDateFormat']) + return date.strftime(editorFormat) def storedText(self, editorText): """Return new text to be stored based on text from the data editor. + Two digit years are interpretted as 1950-2049. Raises a ValueError if the data does not match the format. Arguments: editorText -- the new text entered into the editor """ - editorText = xml.sax.saxutils.escape(editorText) - if not editorText or editorText in self.choices: - return editorText - raise ValueError + editorText = _multipleSpaceRegEx.sub(' ', editorText.strip()) + if not editorText: + return '' + editorFormat = adjInDateFormat(globalref.genOptions['EditDateFormat']) + try: + date = datetime.datetime.strptime(editorText, editorFormat).date() + except ValueError: # allow use of a 4-digit year to fix invalid dates + fullYearFormat = editorFormat.replace('%y', '%Y') + if fullYearFormat != editorFormat: + date = datetime.datetime.strptime(editorText, + fullYearFormat).date() + else: + raise + return date.strftime(DateField.isoFormat) - def comboChoices(self): - """Return a list of choices for the combo box. + def getInitDefault(self): + """Return the initial stored value for newly created nodes. """ - return self.choiceList + if self.initDefault == _dateStampString: + date = datetime.date.today() + return date.strftime(DateField.isoFormat) + return super().getInitDefault() + + def setInitDefault(self, editorText): + """Set the default initial value from editor text. + + The function for default text field just returns the stored text. + Arguments: + editorText -- the new text entered into the editor + """ + if editorText == _dateStampString: + self.initDefault = _dateStampString + else: + super().setInitDefault(editorText) + + def getEditorInitDefault(self): + """Return initial value in editor format. + """ + if self.initDefault == _dateStampString: + return _dateStampString + return super().getEditorInitDefault() def initDefaultChoices(self): """Return a list of choices for setting the init default. """ - return self.choiceList + return [_dateStampString] - def splitText(self, textStr): - """Split textStr using editSep, return a list of strings. + def mathValue(self, node, zeroBlanks=True): + """Return a numeric value to be used in math field equations. - Double editSep's are not split (become single). - Removes duplicates and empty strings. + Return None if blank and not zeroBlanks, + raise a ValueError if it isn't a valid date. Arguments: - textStr -- the text to split + node -- the tree item storing the data + zeroBlanks -- replace blank field values with zeros if True """ - result = [] - textStr = textStr.replace(self.editSep * 2, '\0') - for text in textStr.split(self.editSep): + storedText = node.data.get(self.name, '') + if storedText: + date = datetime.datetime.strptime(storedText, + DateField.isoFormat).date() + return (date - DateField.refDate).days + return 0 if zeroBlanks else None + + def compareValue(self, node): + """Return a value for comparison to other nodes and for sorting. + + Returns lowercase text for text fields or numbers for non-text fields. + Date field uses ISO date format (YYY-MM-DD). + Arguments: + node -- the tree item storing the data + """ + return node.data.get(self.name, '') + + def adjustedCompareValue(self, value): + """Return value adjusted like the compareValue for use in conditionals. + + Date version converts to an ISO date format (YYYY-MM-DD). + Arguments: + value -- the comparison value to adjust + """ + value = _multipleSpaceRegEx.sub(' ', value.strip()) + if not value: + return '' + if value == _dateStampString: + date = datetime.date.today() + return date.strftime(DateField.isoFormat) + try: + return self.storedText(value) + except ValueError: + return value + + +class TimeField(HtmlTextField): + """Class to handle a general time field format type + + Stores options and format strings for a time field type. + Provides methods to return formatted data. + """ + typeName = 'Time' + defaultFormat = '%-I:%M:%S %p' + isoFormat = '%H:%M:%S.%f' + evalHtmlDefault = False + editorClassName = 'TimeEditor' + numChoiceColumns = 2 + autoAddChoices = False + refTime = datetime.time() + sortTypeStr = '50_time' + formatHelpMenuList = [(_('Hour (0-23, 1 or 2 digits)\t%-H'), '%-H'), + (_('Hour (00-23, 2 digits)\t%H'), '%H'), + (_('Hour (1-12, 1 or 2 digits)\t%-I'), '%-I'), + (_('Hour (01-12, 2 digits)\t%I'), '%I'), ('', ''), + (_('Minute (1 or 2 digits)\t%-M'), '%-M'), + (_('Minute (2 digits)\t%M'), '%M'), ('', ''), + (_('Second (1 or 2 digits)\t%-S'), '%-S'), + (_('Second (2 digits)\t%S'), '%S'), ('', ''), + (_('Microseconds (6 digits)\t%f'), '%f'), ('', ''), + (_('AM/PM\t%p'), '%p')] + def __init__(self, name, formatData=None): + """Initialize a field format type. + + Arguments: + name -- the field name string + formatData -- the attributes that define this field's format + """ + super().__init__(name, formatData) + + def formatOutput(self, storedText, titleMode, 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 + formatHtml -- if False, escapes HTML from prefix & suffix + """ + try: + time = datetime.datetime.strptime(storedText, + TimeField.isoFormat).time() + outFormat = adjOutDateFormat(self.format) + outFormat = adjTimeAmPm(outFormat, time) + text = time.strftime(outFormat) + except ValueError: + text = _errorStr + return super().formatOutput(text, titleMode, formatHtml) + + def formatEditorText(self, storedText): + """Return text formatted for use in the data editor. + + Raises a ValueError if the data does not match the format. + Arguments: + storedText -- the source text to format + """ + if not storedText: + return '' + time = datetime.datetime.strptime(storedText, + TimeField.isoFormat).time() + editorFormat = adjOutDateFormat(globalref.genOptions['EditTimeFormat']) + editorFormat = adjTimeAmPm(editorFormat, time) + return time.strftime(editorFormat) + + def storedText(self, editorText): + """Return new text to be stored based on text from the data editor. + + Raises a ValueError if the data does not match the format. + Arguments: + editorText -- the new text entered into the editor + """ + editorText = _multipleSpaceRegEx.sub(' ', editorText.strip()) + if not editorText: + return '' + editorFormat = adjInDateFormat(globalref.genOptions['EditTimeFormat']) + time = None + try: + time = datetime.datetime.strptime(editorText, editorFormat).time() + except ValueError: + noSecFormat = editorFormat.replace(':%S', '') + noSecFormat = _multipleSpaceRegEx.sub(' ', noSecFormat.strip()) + try: + time = datetime.datetime.strptime(editorText, + noSecFormat).time() + except ValueError: + for altFormat in (editorFormat, noSecFormat): + noAmFormat = altFormat.replace('%p', '') + noAmFormat = _multipleSpaceRegEx.sub(' ', + noAmFormat.strip()) + try: + time = datetime.datetime.strptime(editorText, + noAmFormat).time() + break + except ValueError: + pass + if not time: + raise ValueError + return time.strftime(TimeField.isoFormat) + + def annotatedComboChoices(self, editorText): + """Return a list of (choice, annotation) tuples for the combo box. + + Arguments: + editorText -- the text entered into the editor + """ + editorFormat = adjOutDateFormat(globalref.genOptions['EditTimeFormat']) + choices = [(datetime.datetime.now().time().strftime(editorFormat), + '({0})'.format(_timeStampString))] + for hour in (6, 9, 12, 15, 18, 21, 0): + choices.append((datetime.time(hour).strftime(editorFormat), '')) + return choices + + def getInitDefault(self): + """Return the initial stored value for newly created nodes. + """ + if self.initDefault == _timeStampString: + time = datetime.datetime.now().time() + return time.strftime(TimeField.isoFormat) + return super().getInitDefault() + + def setInitDefault(self, editorText): + """Set the default initial value from editor text. + + The function for default text field just returns the stored text. + Arguments: + editorText -- the new text entered into the editor + """ + if editorText == _timeStampString: + self.initDefault = _timeStampString + else: + super().setInitDefault(editorText) + + def getEditorInitDefault(self): + """Return initial value in editor format. + """ + if self.initDefault == _timeStampString: + return _timeStampString + return super().getEditorInitDefault() + + def initDefaultChoices(self): + """Return a list of choices for setting the init default. + """ + return [_timeStampString] + + def mathValue(self, node, zeroBlanks=True): + """Return a numeric value to be used in math field equations. + + Return None if blank and not zeroBlanks, + raise a ValueError if it isn't a valid time. + Arguments: + node -- the tree item storing the data + zeroBlanks -- replace blank field values with zeros if True + """ + storedText = node.data.get(self.name, '') + if storedText: + time = datetime.datetime.strptime(storedText, + TimeField.isoFormat).time() + dateTime = datetime.datetime.combine(DateField.refDate, time) + refDateTime = datetime.datetime.combine(DateField.refDate, + TimeField.refTime) + return (dateTime - refDateTime).seconds + return 0 if zeroBlanks else None + + def compareValue(self, node): + """Return a value for comparison to other nodes and for sorting. + + Returns lowercase text for text fields or numbers for non-text fields. + Time field uses HH:MM:SS format. + Arguments: + node -- the tree item storing the data + """ + return node.data.get(self.name, '') + + def adjustedCompareValue(self, value): + """Return value adjusted like the compareValue for use in conditionals. + + Time version converts to HH:MM:SS format. + Arguments: + value -- the comparison value to adjust + """ + value = _multipleSpaceRegEx.sub(' ', value.strip()) + if not value: + return '' + if value == _timeStampString: + time = datetime.datetime.now().time() + return time.strftime(TimeField.isoFormat) + try: + return self.storedText(value) + except ValueError: + return value + + +class DateTimeField(HtmlTextField): + """Class to handle a general date and time field format type. + + Stores options and format strings for a date and time field type. + Provides methods to return formatted data. + """ + typeName = 'DateTime' + defaultFormat = '%B %-d, %Y %-I:%M:%S %p' + isoFormat = '%Y-%m-%d %H:%M:%S.%f' + evalHtmlDefault = False + editorClassName = 'DateTimeEditor' + refDateTime = datetime.datetime(1970, 1, 1) + sortTypeStr ='45_datetime' + formatHelpMenuList = [(_('Day (1 or 2 digits)\t%-d'), '%-d'), + (_('Day (2 digits)\t%d'), '%d'), ('', ''), + (_('Weekday Abbreviation\t%a'), '%a'), + (_('Weekday Name\t%A'), '%A'), ('', ''), + (_('Month (1 or 2 digits)\t%-m'), '%-m'), + (_('Month (2 digits)\t%m'), '%m'), + (_('Month Abbreviation\t%b'), '%b'), + (_('Month Name\t%B'), '%B'), ('', ''), + (_('Year (2 digits)\t%y'), '%y'), + (_('Year (4 digits)\t%Y'), '%Y'), ('', ''), + (_('Week Number (0 to 53)\t%-U'), '%-U'), + (_('Day of year (1 to 366)\t%-j'), '%-j'), + (_('Hour (0-23, 1 or 2 digits)\t%-H'), '%-H'), + (_('Hour (00-23, 2 digits)\t%H'), '%H'), + (_('Hour (1-12, 1 or 2 digits)\t%-I'), '%-I'), + (_('Hour (01-12, 2 digits)\t%I'), '%I'), ('', ''), + (_('Minute (1 or 2 digits)\t%-M'), '%-M'), + (_('Minute (2 digits)\t%M'), '%M'), ('', ''), + (_('Second (1 or 2 digits)\t%-S'), '%-S'), + (_('Second (2 digits)\t%S'), '%S'), ('', ''), + (_('Microseconds (6 digits)\t%f'), '%f'), ('', ''), + (_('AM/PM\t%p'), '%p')] + def __init__(self, name, formatData=None): + """Initialize a field format type. + + Arguments: + name -- the field name string + formatData -- the dict that defines this field's format + """ + super().__init__(name, formatData) + + def formatOutput(self, storedText, titleMode, 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 + formatHtml -- if False, escapes HTML from prefix & suffix + """ + try: + dateTime = datetime.datetime.strptime(storedText, + DateTimeField.isoFormat) + outFormat = adjOutDateFormat(self.format) + outFormat = adjTimeAmPm(outFormat, dateTime) + text = dateTime.strftime(outFormat) + except ValueError: + text = _errorStr + return super().formatOutput(text, titleMode, formatHtml) + + def formatEditorText(self, storedText): + """Return text formatted for use in the data editor. + + Raises a ValueError if the data does not match the format. + Arguments: + storedText -- the source text to format + """ + if not storedText: + return '' + dateTime = datetime.datetime.strptime(storedText, + DateTimeField.isoFormat) + editorFormat = '{0} {1}'.format(globalref.genOptions['EditDateFormat'], + globalref.genOptions['EditTimeFormat']) + editorFormat = adjOutDateFormat(editorFormat) + editorFormat = adjTimeAmPm(editorFormat, dateTime) + return dateTime.strftime(editorFormat) + + def storedText(self, editorText): + """Return new text to be stored based on text from the data editor. + + Two digit years are interpretted as 1950-2049. + Raises a ValueError if the data does not match the format. + Arguments: + editorText -- the new text entered into the editor + """ + editorText = _multipleSpaceRegEx.sub(' ', editorText.strip()) + if not editorText: + return '' + editorFormat = '{0} {1}'.format(globalref.genOptions['EditDateFormat'], + globalref.genOptions['EditTimeFormat']) + editorFormat = adjInDateFormat(editorFormat) + dateTime = None + try: + dateTime = datetime.datetime.strptime(editorText, editorFormat) + except ValueError: + noSecFormat = editorFormat.replace(':%S', '') + noSecFormat = _multipleSpaceRegEx.sub(' ', noSecFormat.strip()) + altFormats = [editorFormat, noSecFormat] + for altFormat in altFormats[:]: + noAmFormat = altFormat.replace('%p', '') + noAmFormat = _multipleSpaceRegEx.sub(' ', noAmFormat.strip()) + altFormats.append(noAmFormat) + for altFormat in altFormats[:]: + fullYearFormat = altFormat.replace('%y', '%Y') + altFormats.append(fullYearFormat) + for editorFormat in altFormats[1:]: + try: + dateTime = datetime.datetime.strptime(editorText, + editorFormat) + break + except ValueError: + pass + if not dateTime: + raise ValueError + return dateTime.strftime(DateTimeField.isoFormat) + + def getInitDefault(self): + """Return the initial stored value for newly created nodes. + """ + if self.initDefault == _timeStampString: + dateTime = datetime.datetime.now() + return dateTime.strftime(DateTimeField.isoFormat) + return super().getInitDefault() + + def setInitDefault(self, editorText): + """Set the default initial value from editor text. + + The function for default text field just returns the stored text. + Arguments: + editorText -- the new text entered into the editor + """ + if editorText == _timeStampString: + self.initDefault = _timeStampString + else: + super().setInitDefault(editorText) + + def getEditorInitDefault(self): + """Return initial value in editor format. + """ + if self.initDefault == _timeStampString: + return _timeStampString + return super().getEditorInitDefault() + + def initDefaultChoices(self): + """Return a list of choices for setting the init default. + """ + return [_timeStampString] + + def mathValue(self, node, zeroBlanks=True): + """Return a numeric value to be used in math field equations. + + Return None if blank and not zeroBlanks, + raise a ValueError if it isn't a valid time. + Arguments: + node -- the tree item storing the data + zeroBlanks -- replace blank field values with zeros if True + """ + storedText = node.data.get(self.name, '') + if storedText: + dateTime = datetime.datetime.strptime(storedText, + DateTimeField.isoFormat) + return (dateTime - DateTimeField.refDateTime).seconds + return 0 if zeroBlanks else None + + def compareValue(self, node): + """Return a value for comparison to other nodes and for sorting. + + Returns lowercase text for text fields or numbers for non-text fields. + DateTime field uses YYYY-MM-DD HH:MM:SS format. + Arguments: + node -- the tree item storing the data + """ + return node.data.get(self.name, '') + + def adjustedCompareValue(self, value): + """Return value adjusted like the compareValue for use in conditionals. + + Time version converts to HH:MM:SS format. + Arguments: + value -- the comparison value to adjust + """ + value = _multipleSpaceRegEx.sub(' ', value.strip()) + if not value: + return '' + if value == _timeStampString: + dateTime = datetime.datetime.now() + return dateTime.strftime(DateTimeField.isoFormat) + try: + return self.storedText(value) + except ValueError: + return value + + +class ChoiceField(HtmlTextField): + """Class to handle a field with pre-defined, individual text choices. + + Stores options and format strings for a choice field type. + Provides methods to return formatted data. + """ + typeName = 'Choice' + editSep = '/' + defaultFormat = '1/2/3/4' + evalHtmlDefault = False + fixEvalHtmlSetting = False + editorClassName = 'ComboEditor' + numChoiceColumns = 1 + autoAddChoices = False + formatHelpMenuList = [(_('Separator\t/'), '/'), ('', ''), + (_('"/" Character\t//'), '//'), ('', ''), + (_('Example\t1/2/3/4'), '1/2/3/4')] + def __init__(self, name, formatData=None): + """Initialize a field format type. + + Arguments: + name -- the field name string + formatData -- the dict that defines this field's format + """ + super().__init__(name, formatData) + + def setFormat(self, format): + """Set the format string and initialize as required. + + Arguments: + format -- the new format string + """ + super().setFormat(format) + self.choiceList = self.splitText(self.format) + if self.evalHtml: + self.choices = set(self.choiceList) + else: + self.choices = set([saxutils.escape(choice) for choice in + self.choiceList]) + + def formatOutput(self, storedText, titleMode, 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 + formatHtml -- if False, escapes HTML from prefix & suffix + """ + if storedText not in self.choices: + storedText = _errorStr + return super().formatOutput(storedText, titleMode, formatHtml) + + def formatEditorText(self, storedText): + """Return text formatted for use in the data editor. + + Raises a ValueError if the data does not match the format. + Arguments: + storedText -- the source text to format + """ + if storedText and storedText not in self.choices: + raise ValueError + if self.evalHtml: + return storedText + return saxutils.unescape(storedText) + + def storedText(self, editorText): + """Return new text to be stored based on text from the data editor. + + Raises a ValueError if the data does not match the format. + Arguments: + editorText -- the new text entered into the editor + """ + if not self.evalHtml: + editorText = saxutils.escape(editorText) + if not editorText or editorText in self.choices: + return editorText + raise ValueError + + def comboChoices(self): + """Return a list of choices for the combo box. + """ + return self.choiceList + + def initDefaultChoices(self): + """Return a list of choices for setting the init default. + """ + return self.choiceList + + def splitText(self, textStr): + """Split textStr using editSep, return a list of strings. + + Double editSep's are not split (become single). + Removes duplicates and empty strings. + Arguments: + textStr -- the text to split + """ + result = [] + textStr = textStr.replace(self.editSep * 2, '\0') + for text in textStr.split(self.editSep): text = text.strip().replace('\0', self.editSep) if text and text not in result: result.append(text) @@ -877,17 +1495,19 @@ Provides methods to return formatted data. """ typeName = 'AutoChoice' - editorClass = dataeditors.ComboEditor + evalHtmlDefault = False + fixEvalHtmlSetting = False + editorClassName = 'ComboEditor' numChoiceColumns = 1 autoAddChoices = True - def __init__(self, name, attrs=None): + def __init__(self, name, formatData=None): """Initialize a field format type. Arguments: name -- the field name string - attrs -- the attributes that define this field's format + formatData -- the attributes that define this field's format """ - super().__init__(name, attrs) + super().__init__(name, formatData) self.choices = set() def formatEditorText(self, storedText): @@ -896,7 +1516,9 @@ Arguments: storedText -- the source text to format """ - return xml.sax.saxutils.unescape(storedText) + if self.evalHtml: + return storedText + return saxutils.unescape(storedText) def storedText(self, editorText): """Return new text to be stored based on text from the data editor. @@ -904,12 +1526,18 @@ Arguments: editorText -- the new text entered into the editor """ - return xml.sax.saxutils.escape(editorText) + if self.evalHtml: + return editorText + return saxutils.escape(editorText) def comboChoices(self): """Return a list of choices for the combo box. """ - choices = [xml.sax.saxutils.unescape(text) for text in self.choices] + if self.evalHtml: + choices = self.choices + else: + choices = [saxutils.unescape(text) for text in + self.choices] return sorted(choices, key=str.lower) def addChoice(self, text): @@ -934,16 +1562,16 @@ Provides methods to return formatted data. """ typeName = 'Combination' - editorClass = dataeditors.CombinationEditor + editorClassName = 'CombinationEditor' numChoiceColumns = 2 - def __init__(self, name, attrs=None): + def __init__(self, name, formatData=None): """Initialize a field format type. Arguments: name -- the field name string - attrs -- the attributes that define this field's format + formatData -- the dict that defines this field's format """ - super().__init__(name, attrs) + super().__init__(name, formatData) def setFormat(self, format): """Set the format string and initialize as required. @@ -952,12 +1580,13 @@ format -- the new format string """ TextField.setFormat(self, format) - format = xml.sax.saxutils.escape(format) + if not self.evalHtml: + format = saxutils.escape(format) self.choiceList = self.splitText(format) self.choices = set(self.choiceList) self.outputSep = '' - def outputText(self, node, titleMode, formatHtml): + def outputText(self, node, titleMode, formatHtml, spotRef=None): """Return formatted output text for this field in this node. Sets output separator prior to calling base class methods. @@ -965,9 +1594,10 @@ node -- the tree item storing the data titleMode -- if True, removes all HTML markup for tree title use formatHtml -- if False, escapes HTML from prefix & suffix + spotRef -- optional, used for ancestor field refs """ - self.outputSep = node.nodeFormat().outputSeparator - return super().outputText(node, titleMode, formatHtml) + self.outputSep = node.formatRef.outputSeparator + return super().outputText(node, titleMode, formatHtml, spotRef) def formatOutput(self, storedText, titleMode, formatHtml): """Return formatted output text from stored text for this field. @@ -993,7 +1623,9 @@ """ selections = set(self.splitText(storedText)) if selections.issubset(self.choices): - return xml.sax.saxutils.unescape(storedText) + if self.evalHtml: + return storedText + return saxutils.unescape(storedText) raise ValueError def storedText(self, editorText): @@ -1003,7 +1635,8 @@ Arguments: editorText -- the new text entered into the editor """ - editorText = xml.sax.saxutils.escape(editorText) + if not self.evalHtml: + editorText = saxutils.escape(editorText) selections, valid = self.sortedSelections(editorText) if not valid: raise ValueError @@ -1012,7 +1645,9 @@ def comboChoices(self): """Return a list of choices for the combo box. """ - return [xml.sax.saxutils.unescape(text) for text in self.choiceList] + if self.evalHtml: + return self.choiceList + return [saxutils.unescape(text) for text in self.choiceList] def comboActiveChoices(self, editorText): """Return a sorted list of choices currently in editorText. @@ -1020,9 +1655,10 @@ Arguments: editorText -- the text entered into the editor """ - selections, valid = self.sortedSelections(xml.sax.saxutils. - escape(editorText)) - return [xml.sax.saxutils.unescape(text) for text in selections] + selections, valid = self.sortedSelections(saxutils.escape(editorText)) + if self.evalHtml: + return selections + return [saxutils.unescape(text) for text in selections] def initDefaultChoices(self): """Return a list of choices for setting the init default. @@ -1062,18 +1698,18 @@ autoAddChoices = True defaultFormat = '' formatHelpMenuList = [] - def __init__(self, name, attrs=None): + def __init__(self, name, formatData=None): """Initialize a field format type. Arguments: name -- the field name string - attrs -- the attributes that define this field's format + formatData -- the attributes that define this field's format """ - super().__init__(name, attrs) + super().__init__(name, formatData) self.choices = set() self.outputSep = '' - def outputText(self, node, titleMode, formatHtml): + def outputText(self, node, titleMode, formatHtml, spotRef=None): """Return formatted output text for this field in this node. Sets output separator prior to calling base class methods. @@ -1081,9 +1717,10 @@ node -- the tree item storing the data titleMode -- if True, removes all HTML markup for tree title use formatHtml -- if False, escapes HTML from prefix & suffix + spotRef -- optional, used for ancestor field refs """ - self.outputSep = node.nodeFormat().outputSeparator - return super().outputText(node, titleMode, formatHtml) + self.outputSep = node.formatRef.outputSeparator + return super().outputText(node, titleMode, formatHtml, spotRef) def formatOutput(self, storedText, titleMode, formatHtml): """Return formatted output text from stored text for this field. @@ -1102,7 +1739,9 @@ Arguments: storedText -- the source text to format """ - return xml.sax.saxutils.unescape(storedText) + if self.evalHtml: + return storedText + return saxutils.unescape(storedText) def storedText(self, editorText): """Return new text to be stored based on text from the data editor. @@ -1112,389 +1751,82 @@ editorText -- the new text entered into the editor """ self.outputSep = '' - editorText = xml.sax.saxutils.escape(editorText) - selections = sorted(self.splitText(editorText), key=str.lower) - return self.joinText(selections) - - def comboChoices(self): - """Return a list of choices for the combo box. - """ - return [xml.sax.saxutils.unescape(text) for text in - sorted(self.choices, key=str.lower)] - - def comboActiveChoices(self, editorText): - """Return a sorted list of choices currently in editorText. - - Arguments: - editorText -- the text entered into the editor - """ - selections, valid = self.sortedSelections(xml.sax.saxutils. - escape(editorText)) - return [xml.sax.saxutils.unescape(text) for text in selections] - - def sortedSelections(self, inText): - """Split inText using editSep and sort like format string. - - Return a tuple of resulting selection list and bool validity. - This version always returns valid. - Arguments: - inText -- the text to split and sequence - """ - selections = sorted(self.splitText(inText), key=str.lower) - return (selections, True) - - def addChoice(self, text): - """Add a new choice. - - Arguments: - text -- the stored text combinations to be added - """ - for choice in self.splitText(text): - self.choices.add(choice) - - def clearChoices(self): - """Remove all current choices. - """ - self.choices = set() - - -class BooleanField(ChoiceField): - """Class to handle a general boolean field format type. - - Stores options and format strings for a boolean field type. - Provides methods to return formatted data. - """ - typeName = 'Boolean' - defaultFormat = _('yes/no') - formatHelpMenuList = [(_('true/false'), 'true/false'), - (_('T/F'), 'T/F'), ('', ''), - (_('yes/no'), 'yes/no'), - (_('Y/N'), 'Y/N'), ('', ''), - ('1/0', '1/0')] - def __init__(self, name, attrs=None): - """Initialize a field format type. - - Arguments: - name -- the field name string - attrs -- the attributes that define this field's format - """ - super().__init__(name, attrs) - - def formatOutput(self, storedText, titleMode, 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 - formatHtml -- if False, escapes HTML from prefix & suffix - """ - try: - text = genboolean.GenBoolean(storedText).boolStr(self.format) - except ValueError: - text = _errorStr - return super().formatOutput(text, titleMode, formatHtml) - - def formatEditorText(self, storedText): - """Return text formatted for use in the data editor. - - Raises a ValueError if the data does not match the format. - Arguments: - storedText -- the source text to format - """ - if not storedText: - return '' - return genboolean.GenBoolean(storedText).boolStr(self.format) - - def storedText(self, editorText): - """Return new text to be stored based on text from the data editor. - - Raises a ValueError if the data does not match the format. - Arguments: - editorText -- the new text entered into the editor - """ - if not editorText: - return '' - try: - return repr(genboolean.GenBoolean().setFromStr(editorText, - self.format)) - except ValueError: - return repr(genboolean.GenBoolean(editorText)) - - def mathValue(self, node, zeroBlanks=True): - """Return a value to be used in math field equations. - - Return None if blank and not zeroBlanks, - raise a ValueError if it isn't a valid boolean. - Arguments: - node -- the tree item storing the data - zeroBlanks -- replace blank field values with zeros if True - """ - storedText = node.data.get(self.name, '') - if storedText: - return genboolean.GenBoolean(storedText).value - return False if zeroBlanks else None - - def compareValue(self, node): - """Return a value for comparison to other nodes and for sorting. - - Returns lowercase text for text fields or numbers for non-text fields. - Bool fields return True or False values. - Arguments: - node -- the tree item storing the data - """ - storedText = node.data.get(self.name, '') - try: - return genboolean.GenBoolean(storedText).value - except ValueError: - return False - - def sortKey(self, node): - """Return a tuple with field type and comparison values for sorting. - - Allows different types to be sorted. - Arguments: - node -- the tree item storing the data - """ - return ('30_bool', self.compareValue(node)) - - def adjustedCompareValue(self, value): - """Return value adjusted like the compareValue for use in conditionals. - - Bool version converts to a bool value. - Arguments: - value -- the comparison value to adjust - """ - try: - return genboolean.GenBoolean().setFromStr(value, self.format).value - except ValueError: - try: - return genboolean.GenBoolean(value).value - except ValueError: - return False - - -class DateField(HtmlTextField): - """Class to handle a general date field format type. - - Stores options and format strings for a date field type. - Provides methods to return formatted data. - """ - typeName = 'Date' - defaultFormat = 'MMMM d, yyyy' - editorClass = dataeditors.DateEditor - refDate = QtCore.QDate(1970, 1, 1) - formatHelpMenuList = [(_('Day (1 or 2 digits)\td'), 'd'), - (_('Day (2 digits)\tdd'), 'dd'), ('', ''), - (_('Weekday Abbreviation\tddd'), 'ddd'), - (_('Weekday Name\tdddd'), 'dddd'), ('', ''), - (_('Month (1 or 2 digits)\tM'), 'M'), - (_('Month (2 digits)\tMM'), 'MM'), - (_('Month Abbreviation\tMMM'), 'MMM'), - (_('Month Name\tMMMM'), 'MMMM'), ('', ''), - (_('Year (2 digits)\tyy'), 'yy'), - (_('Year (4 digits)\tyyyy'), 'yyyy'), ('', '')] - - def __init__(self, name, attrs=None): - """Initialize a field format type. - - Arguments: - name -- the field name string - attrs -- the attributes that define this field's format - """ - super().__init__(name, attrs) - - def formatOutput(self, storedText, titleMode, 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 - formatHtml -- if False, escapes HTML from prefix & suffix - """ - date = QtCore.QDate.fromString(storedText, QtCore.Qt.ISODate) - if date.isValid(): - text = date.toString(self.format) - else: - text = _errorStr - return super().formatOutput(text, titleMode, formatHtml) - - def formatEditorText(self, storedText): - """Return text formatted for use in the data editor. - - Raises a ValueError if the data does not match the format. - Arguments: - storedText -- the source text to format - """ - if not storedText: - return '' - date = QtCore.QDate.fromString(storedText, QtCore.Qt.ISODate) - if date.isValid(): - editorFormat = globalref.genOptions.getValue('EditDateFormat') - return date.toString(editorFormat) - raise ValueError - - def storedText(self, editorText): - """Return new text to be stored based on text from the data editor. - - Two digit years are interpretted as 1950-2049. - Raises a ValueError if the data does not match the format. - Arguments: - editorText -- the new text entered into the editor - """ - if not editorText: - return '' - editorFormat = globalref.genOptions.getValue('EditDateFormat') - date = QtCore.QDate.fromString(editorText, editorFormat) - if not date.isValid(): - # allow use of single digit month and day - editorFormat = re.sub(r'(?{1}'.format(address.strip(), name.strip()) - def compareValue(self, node): - """Return a value for comparison to other nodes and for sorting. + def adjustedCompareValue(self, value): + """Return value adjusted like the compareValue for use in conditionals. - Returns lowercase text for text fields or numbers for non-text fields. - Link fields use stored link format + Link fields use link address. Arguments: - node -- the tree item storing the data + value -- the comparison value to adjust """ - storedText = node.data.get(self.name, '') - if not storedText: + if not value: return '' - linkMatch = linkRegExp.search(storedText) - if not linkMatch: - return storedText - address, name = linkMatch.groups() + try: + address, name = self.addressAndName(value) + except ValueError: + return value.lower() return address.lstrip('#').lower() - def sortKey(self, node): - """Return a tuple with field type and comparison values for sorting. - - Allows different types to be sorted. - Arguments: - node -- the tree item storing the data - """ - return ('50_link', self.compareValue(node)) - class InternalLinkField(ExternalLinkField): """Class to handle a field containing internal links to nodes. @@ -1764,16 +2017,17 @@ Stores data as HTML local link tag, shows in editors as "id [name]". """ typeName = 'InternalLink' - editorClass = dataeditors.IntLinkEditor + editorClassName = 'IntLinkEditor' + supportsInitDefault = False - def __init__(self, name, attrs=None): + def __init__(self, name, formatData=None): """Initialize a field format type. Arguments: name -- the field name string - attrs -- the attributes that define this field's format + formatData -- the attributes that define this field's format """ - super().__init__(name, attrs) + super().__init__(name, formatData) def editorText(self, node): """Return text formatted for use in the data editor. @@ -1785,9 +2039,9 @@ node -- the tree item storing the data """ storedText = node.data.get(self.name, '') - return self.formatEditorText(storedText, node.modelRef) + return self.formatEditorText(storedText, node.treeStructureRef()) - def formatEditorText(self, storedText, modelRef=None): + def formatEditorText(self, storedText, treeStructRef): """Return text formatted for use in the data editor. Raises a ValueError if the data does not match the format. @@ -1795,39 +2049,44 @@ the editor text as the second argument to the exception. Arguments: storedText -- the source text to format - modelRef -- a model ref to check for valid links if given + treeStructRef -- ref to the tree structure to get the linked title """ if not storedText: return '' - linkMatch = linkRegExp.search(storedText) - if not linkMatch: - raise ValueError - address, name = linkMatch.groups() + address, name = self.addressAndName(storedText) address = address.lstrip('#') + targetNode = treeStructRef.nodeDict.get(address, None) + linkTitle = targetNode.title() if targetNode else _errorStr name = name.strip() - if not name: - name = address - result = '{0} [{1}]'.format(address, name) - if modelRef and address not in modelRef.nodeIdDict: + if not name and targetNode: + name = linkTitle + result = 'LinkTo: {0} [{1}]'.format(linkTitle, name) + if linkTitle == _errorStr: raise ValueError('invalid address', result) return result def storedText(self, editorText): """Return new text to be stored based on text from the data editor. + Uses the "address [name]" format as input, not the final editor form. Raises a ValueError if the data does not match the format. Arguments: - editorText -- the new text entered into the editor + editorText -- the new editor text in "address [name]" format """ if not editorText: return '' nameMatch = linkSeparateNameRegExp.match(editorText) - if nameMatch: - address, name = nameMatch.groups() - else: - address = editorText - name = address - return '{1}'.format(address.strip(), name.strip()) + if not nameMatch: + raise ValueError + address, name = nameMatch.groups() + if not address: + raise ValueError('invalid address', '') + if not name: + name = _errorStr + result = '{1}'.format(address.strip(), name.strip()) + if name == _errorStr: + raise ValueError('invalid name', result) + return result class PictureField(HtmlTextField): @@ -1837,16 +2096,18 @@ Stores data as HTML tags, shows in editors as "protocol:address [name]". """ typeName = 'Picture' - editorClass = dataeditors.PictureLinkEditor + evalHtmlDefault = False + editorClassName = 'PictureLinkEditor' + sortTypeStr ='60_link' - def __init__(self, name, attrs=None): + def __init__(self, name, formatData=None): """Initialize a field format type. Arguments: name -- the field name string - attrs -- the attributes that define this field's format + formatData -- the attributes that define this field's format """ - super().__init__(name, attrs) + super().__init__(name, formatData) def formatOutput(self, storedText, titleMode, formatHtml): """Return formatted output text from stored text for this field. @@ -1857,7 +2118,7 @@ formatHtml -- if False, escapes HTML from prefix & suffix """ if titleMode: - linkMatch = imageRegExp.search(storedText) + linkMatch = _imageRegExp.search(storedText) if linkMatch: address = linkMatch.group(1) storedText = address.strip() @@ -1872,7 +2133,7 @@ """ if not storedText: return '' - linkMatch = imageRegExp.search(storedText) + linkMatch = _imageRegExp.search(storedText) if not linkMatch: raise ValueError return linkMatch.group(1) @@ -1895,31 +2156,20 @@ name = urltools.shortName(address) return ''.format(editorText) - def compareValue(self, node): - """Return a value for comparison to other nodes and for sorting. + def adjustedCompareValue(self, value): + """Return value adjusted like the compareValue for use in conditionals. - Returns lowercase text for text fields or numbers for non-text fields. - Link fields use stored link format + Link fields use link address. Arguments: - node -- the tree item storing the data + value -- the comparison value to adjust """ - storedText = node.data.get(self.name, '') - if not storedText: + if not value: return '' - linkMatch = imageRegExp.search(storedText) + linkMatch = _imageRegExp.search(value) if not linkMatch: - return storedText + return value.lower() return linkMatch.group(1).lower() - def sortKey(self, node): - """Return a tuple with field type and comparison values for sorting. - - Allows different types to be sorted. - Arguments: - node -- the tree item storing the data - """ - return ('50_link', self.compareValue(node)) - class RegularExpressionField(HtmlTextField): """Class to handle a field format type controlled by a regular expression. @@ -1929,7 +2179,9 @@ """ typeName = 'RegularExpression' defaultFormat = '.*' - editorClass = dataeditors.LineEditor + evalHtmlDefault = False + fixEvalHtmlSetting = False + editorClassName = 'LineEditor' formatHelpMenuList = [(_('Any Character\t.'), '.'), (_('End of Text\t$'), '$'), ('', ''), @@ -1945,14 +2197,14 @@ (_('Or\t|'), '|'), (_('Escape a Special Character\t\\'), '\\')] - def __init__(self, name, attrs=None): + def __init__(self, name, formatData=None): """Initialize a field format type. Arguments: name -- the field name string - attrs -- the attributes that define this field's format + formatData -- the dict that defines this field's format """ - super().__init__(name, attrs) + super().__init__(name, formatData) def setFormat(self, format): """Set the format string and initialize as required. @@ -1975,7 +2227,7 @@ titleMode -- if True, removes all HTML markup for tree title use formatHtml -- if False, escapes HTML from prefix & suffix """ - match = re.match(self.format, xml.sax.saxutils.unescape(storedText)) + match = re.fullmatch(self.format, saxutils.unescape(storedText)) if not storedText or match: text = storedText else: @@ -1989,10 +2241,11 @@ Arguments: storedText -- the source text to format """ - editorText = xml.sax.saxutils.unescape(storedText) - match = re.match(self.format, editorText) - if not editorText or match: - return editorText + if not self.evalHtml: + storedText = saxutils.unescape(storedText) + match = re.fullmatch(self.format, storedText) + if not storedText or match: + return storedText raise ValueError def storedText(self, editorText): @@ -2002,9 +2255,11 @@ Arguments: editorText -- the new text entered into the editor """ - match = re.match(self.format, editorText) + match = re.fullmatch(self.format, editorText) if not editorText or match: - return xml.sax.saxutils.escape(editorText) + if self.evalHtml: + return editorText + return saxutils.escape(editorText) raise ValueError @@ -2022,7 +2277,7 @@ super().__init__(name, {}) self.ancestorLevel = ancestorLevel - def outputText(self, node, titleMode, formatHtml): + def outputText(self, node, titleMode, formatHtml, spotRef=None): """Return formatted output text for this field in this node. Finds the appropriate ancestor node to get the field text. @@ -2030,16 +2285,20 @@ node -- the tree node to start from titleMode -- if True, removes all HTML markup for tree title use formatHtml -- if False, escapes HTML from prefix & suffix + spotRef -- optional, used for ancestor field refs """ + if not spotRef: + spotRef = node.spotByNumber(0) for num in range(self.ancestorLevel): - node = node.parent - if not node: + spotRef = spotRef.parentSpot + if not spotRef: return '' try: - field = node.nodeFormat().fieldDict[self.name] - except KeyError: + field = spotRef.nodeRef.formatRef.fieldDict[self.name] + except (AttributeError, KeyError): return '' - return field.outputText(node, titleMode, formatHtml) + return field.outputText(spotRef.nodeRef, titleMode, formatHtml, + spotRef) def sepName(self): """Return the name enclosed with {* *} separators @@ -2059,7 +2318,7 @@ """ super().__init__(name, {}) - def outputText(self, node, titleMode, formatHtml): + def outputText(self, node, titleMode, formatHtml, spotRef=None): """Return formatted output text for this field in this node. Finds the appropriate ancestor node to get the field text. @@ -2067,15 +2326,19 @@ node -- the tree node to start from titleMode -- if True, removes all HTML markup for tree title use formatHtml -- if False, escapes HTML from prefix & suffix + spotRef -- optional, used for ancestor field refs """ - while node.parent: - node = node.parent + if not spotRef: + spotRef = node.spotByNumber(0) + while spotRef.parentSpot: + spotRef = spotRef.parentSpot try: - field = node.nodeFormat().fieldDict[self.name] - except KeyError: + field = spotRef.nodeRef.formatRef.fieldDict[self.name] + except (AttributeError, KeyError): pass else: - return field.outputText(node, titleMode, formatHtml) + return field.outputText(spotRef.nodeRef, titleMode, formatHtml, + spotRef) return '' def sepName(self): @@ -2096,7 +2359,7 @@ """ super().__init__(name, {}) - def outputText(self, node, titleMode, formatHtml): + def outputText(self, node, titleMode, formatHtml, spotRef=None): """Return formatted output text for this field in this node. Returns a joined list of matching child field data. @@ -2104,16 +2367,18 @@ node -- the tree node to start from titleMode -- if True, removes all HTML markup for tree title use formatHtml -- if False, escapes HTML from prefix & suffix + spotRef -- optional, used for ancestor field refs """ result = [] for child in node.childList: try: - field = child.nodeFormat().fieldDict[self.name] + field = child.formatRef.fieldDict[self.name] except KeyError: pass else: - result.append(field.outputText(child, titleMode, formatHtml)) - outputSep = node.nodeFormat().outputSeparator + result.append(field.outputText(child, titleMode, formatHtml, + spotRef)) + outputSep = node.formatRef.outputSeparator return outputSep.join(result) def sepName(self): @@ -2136,7 +2401,7 @@ super().__init__(name, {}) self.descendantLevel = descendantLevel - def outputText(self, node, titleMode, formatHtml): + def outputText(self, node, titleMode, formatHtml, spotRef=None): """Return formatted output text for this field in this node. Returns a count of descendants at the approriate level. @@ -2144,11 +2409,12 @@ node -- the tree node to start from titleMode -- if True, removes all HTML markup for tree title use formatHtml -- if False, escapes HTML from prefix & suffix + spotRef -- optional, used for ancestor field refs """ newNodes = [node] for i in range(self.descendantLevel): prevNodes = newNodes - newNodes= [] + newNodes = [] for child in prevNodes: newNodes.extend(child.childList) return repr(len(newNodes)) @@ -2159,41 +2425,50 @@ return '{{*#{0}*}}'.format(self.name) -class UniqueIdField(TextField): - """Placeholder format for output of the unique ID for a node. +#### Utility Functions #### + +_stripTagRe = re.compile('<.*?>') + +def removeMarkup(text): + """Return text with all HTML Markup removed and entities unescaped. """ - typeName = 'UniqueId' - def __init__(self, name): - """Initialize a field format placeholder type. + text = _stripTagRe.sub('', text) + return saxutils.unescape(text) - Arguments: - name -- the field name string - """ - super().__init__(name, {}) +def adjOutDateFormat(dateFormat): + """Replace Linux lead zero removal with Windows version in date formats. - def outputText(self, node, titleMode, formatHtml): - """Return formatted output text for this field in this node. + Arguments: + dateFormat -- the format to modify + """ + if sys.platform.startswith('win'): + dateFormat = dateFormat.replace('%-', '%#') + return dateFormat - Returns the node's unique ID. - Arguments: - node -- the tree node to start from - titleMode -- if True, removes all HTML markup for tree title use - formatHtml -- if False, escapes HTML from prefix & suffix - """ - return node.uniqueId +def adjInDateFormat(dateFormat): + """Remove lead zero formatting in date formats for reading dates. - def sepName(self): - """Return the name enclosed with {* *} separators - """ - return '{{*!{0}*}}'.format(self.name) + Arguments: + dateFormat -- the format to modify + """ + return dateFormat.replace('%-', '%') +def adjTimeAmPm(timeFormat, time): + """Add AM/PM to timeFormat if in format and locale skips it. -#### Utility Functions #### + Arguments: + timeFormat -- the format to modify + time -- the datetime object to check for AM/PM + """ + if '%p' in timeFormat and time.strftime('%I (%p)').endswith('()'): + amPm = 'AM' if time.hour < 12 else 'PM' + timeFormat = re.sub(r'(?') +def translatedTypeName(typeName): + """Return a translated type name. -def removeMarkup(text): - """Return text with all HTML Markup removed and entities unescaped. + Arguments: + typeName -- the English type name """ - text = _stripTagRe.sub('', text) - return xml.sax.saxutils.unescape(text) + return translatedFieldTypes[fieldTypes.index(typeName)] diff -Nru treeline-2.0.2/source/gennumber.py treeline-3.0.1/source/gennumber.py --- treeline-2.0.2/source/gennumber.py 2012-02-25 22:17:50.000000000 +0000 +++ treeline-3.0.1/source/gennumber.py 2018-04-08 19:19:50.000000000 +0000 @@ -3,7 +3,7 @@ #****************************************************************************** # gennumber.py, provides a class for number formating # -# Copyright (C) 2011, Douglas W. Bell +# Copyright (C) 2018, 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 @@ -87,7 +87,8 @@ num = self.num / 10**exp totPlcs = len(re.findall(r'[#0]', formMain)) num = round(num, totPlcs - 1 if totPlcs > 0 else 0) - wholePlcs = len(re.findall(r'[#0]', _doubleSplit('.', formMain)[0])) + radix = _getRadix(strFormat) + wholePlcs = len(re.findall(r'[#0]', _doubleSplit(radix, formMain)[0])) expChg = wholePlcs - int(math.floor(math.log10(abs(num)))) - 1 num = num * 10**expChg exp -= expChg diff -Nru treeline-2.0.2/source/globalref.py treeline-3.0.1/source/globalref.py --- treeline-2.0.2/source/globalref.py 2014-12-20 19:15:44.000000000 +0000 +++ treeline-3.0.1/source/globalref.py 2018-04-08 19:19:50.000000000 +0000 @@ -4,7 +4,7 @@ # globalref.py, provides a module for access to a few global variables # # TreeLine, an information storage program -# Copyright (C) 2014, Douglas W. Bell +# Copyright (C) 2017, 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 @@ -26,14 +26,20 @@ localTextEncoding = '' lang = '' -fileFilters = {'trl': '{} (*.trl *.xml)'.format(_('TreeLine Files')), - 'trlgz': '{} (*.trl *.trl.gz)'. - format(_('TreeLine Files - Compressed')), - 'trlenc': '{} (*.trl)'.format(_('TreeLine Files - Encrypted')), +fileFilters = {'trlnopen': '{} (*.trln *.trln.gz *.trl)'. + format(_('All TreeLine Files')), + 'trlnv3': '{} (*.trln *.trln.gz)'.format(_('TreeLine Files')), + 'trlnsave': '{} (*.trln)'.format(_('TreeLine Files')), + 'trlngz': '{} (*.trln *.trln.gz)'. + format(_('TreeLine Files - Compressed')), + 'trlnenc': '{} (*.trln)'. + format(_('TreeLine Files - Encrypted')), + 'trl': '{} (*.trl *.xml)'.format(_('Old TreeLine Files')), 'all': '{} (*)'.format(_('All Files')), 'html': '{} (*.html *.htm)'.format(_('HTML Files')), 'txt': '{} (*.txt)'.format(_('Text Files')), 'xml': '{} (*.xml)'.format(_('XML Files')), + 'csv': '{} (*.csv)'.format(_('CSV (Comma Delimited) Files')), 'odt': '{} (*.odt)'.format(_('ODF Text Files')), 'hjt': '{} (*.hjt)'.format(_('Treepad Files')), 'pdf': '{} (*.pdf)'.format(_('PDF Files'))} diff -Nru treeline-2.0.2/source/helpview.py treeline-3.0.1/source/helpview.py --- treeline-2.0.2/source/helpview.py 2015-03-06 03:24:15.000000000 +0000 +++ treeline-3.0.1/source/helpview.py 2018-04-08 19:19:50.000000000 +0000 @@ -3,7 +3,7 @@ #**************************************************************************** # helpview.py, provides a window for viewing an html help file # -# Copyright (C) 2015, Douglas W. Bell +# Copyright (C) 2017, 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 @@ -11,36 +11,39 @@ # but WITTHOUT ANY WARRANTY. See the included LICENSE file for details. #***************************************************************************** -import os.path -import sys -from PyQt4 import QtCore, QtGui +from PyQt5.QtCore import QUrl, Qt +from PyQt5.QtGui import QTextDocument +from PyQt5.QtWidgets import (QAction, QLabel, QLineEdit, QMainWindow, QMenu, + QStatusBar, QTextBrowser) import dataeditors -class HelpView(QtGui.QMainWindow): +class HelpView(QMainWindow): """Main window for viewing an html help file. """ - def __init__(self, path, caption, icons, parent=None): + def __init__(self, pathObj, caption, icons, parent=None): """Helpview initialize with text. - """ - QtGui.QMainWindow.__init__(self, parent) - self.setAttribute(QtCore.Qt.WA_QuitOnClose, False) - self.setWindowFlags(QtCore.Qt.Window) - self.setStatusBar(QtGui.QStatusBar()) + + Arguments: + pathObj -- a path object for the help file + caption -- the window caption + icons -- dict of view icons + """ + QMainWindow.__init__(self, parent) + self.setAttribute(Qt.WA_QuitOnClose, False) + self.setWindowFlags(Qt.Window) + self.setStatusBar(QStatusBar()) self.textView = HelpViewer(self) self.setCentralWidget(self.textView) - path = os.path.abspath(path) - if sys.platform.startswith('win'): - path = path.replace('\\', '/') - self.textView.setSearchPaths([os.path.dirname(path)]) - self.textView.setSource(QtCore.QUrl('file:///{0}'.format(path))) + self.textView.setSearchPaths([str(pathObj.parent)]) + self.textView.setSource(QUrl(pathObj.as_uri())) self.resize(520, 440) self.setWindowTitle(caption) tools = self.addToolBar(_('Tools')) - self.menu = QtGui.QMenu(self.textView) + self.menu = QMenu(self.textView) self.textView.highlighted[str].connect(self.showLink) - backAct = QtGui.QAction(_('&Back'), self) + backAct = QAction(_('&Back'), self) backAct.setIcon(icons['helpback']) tools.addAction(backAct) self.menu.addAction(backAct) @@ -48,7 +51,7 @@ backAct.setEnabled(False) self.textView.backwardAvailable.connect(backAct.setEnabled) - forwardAct = QtGui.QAction(_('&Forward'), self) + forwardAct = QAction(_('&Forward'), self) forwardAct.setIcon(icons['helpforward']) tools.addAction(forwardAct) self.menu.addAction(forwardAct) @@ -56,7 +59,7 @@ forwardAct.setEnabled(False) self.textView.forwardAvailable.connect(forwardAct.setEnabled) - homeAct = QtGui.QAction(_('&Home'), self) + homeAct = QAction(_('&Home'), self) homeAct.setIcon(icons['helphome']) tools.addAction(homeAct) self.menu.addAction(homeAct) @@ -64,21 +67,21 @@ tools.addSeparator() tools.addSeparator() - findLabel = QtGui.QLabel(_(' Find: '), self) + findLabel = QLabel(_(' Find: '), self) tools.addWidget(findLabel) - self.findEdit = QtGui.QLineEdit(self) + self.findEdit = QLineEdit(self) tools.addWidget(self.findEdit) self.findEdit.textEdited.connect(self.findTextChanged) self.findEdit.returnPressed.connect(self.findNext) - self.findPreviousAct = QtGui.QAction(_('Find &Previous'), self) + self.findPreviousAct = QAction(_('Find &Previous'), self) self.findPreviousAct.setIcon(icons['helpprevious']) tools.addAction(self.findPreviousAct) self.menu.addAction(self.findPreviousAct) self.findPreviousAct.triggered.connect(self.findPrevious) self.findPreviousAct.setEnabled(False) - self.findNextAct = QtGui.QAction(_('Find &Next'), self) + self.findNextAct = QAction(_('Find &Next'), self) self.findNextAct.setIcon(icons['helpnext']) tools.addAction(self.findNextAct) self.menu.addAction(self.findNextAct) @@ -87,11 +90,17 @@ def showLink(self, text): """Send link text to the statusbar. + + Arguments: + text -- link text to show """ self.statusBar().showMessage(text) def findTextChanged(self, text): """Update find controls based on text in text edit. + + Arguments: + text -- the search text """ self.findPreviousAct.setEnabled(len(text) > 0) self.findNextAct.setEnabled(len(text) > 0) @@ -100,7 +109,7 @@ """Command to find the previous string. """ if self.textView.find(self.findEdit.text(), - QtGui.QTextDocument.FindBackward): + QTextDocument.FindBackward): self.statusBar().clearMessage() else: self.statusBar().showMessage(_('Text string not found')) @@ -114,22 +123,33 @@ self.statusBar().showMessage(_('Text string not found')) -class HelpViewer(QtGui.QTextBrowser): +class HelpViewer(QTextBrowser): """Shows an html help file. """ def __init__(self, parent=None): - QtGui.QTextBrowser.__init__(self, parent) + """Initialize the viewer. + + Arguments: + parent -- the parent widget, if given + """ + QTextBrowser.__init__(self, parent) def setSource(self, url): """Called when user clicks on a URL. + + Arguments: + url -- the clicked on QUrl """ name = url.toString() if name.startswith('http'): dataeditors.openExtUrl(name) else: - QtGui.QTextBrowser.setSource(self, QtCore.QUrl(name)) + QTextBrowser.setSource(self, QUrl(name)) def contextMenuEvent(self, event): """Init popup menu on right click"". + + Arguments: + event -- the menu event """ self.parentWidget().menu.exec_(event.globalPos()) diff -Nru treeline-2.0.2/source/icondict.py treeline-3.0.1/source/icondict.py --- treeline-2.0.2/source/icondict.py 2013-01-24 11:35:38.000000000 +0000 +++ treeline-3.0.1/source/icondict.py 2018-04-08 19:19:50.000000000 +0000 @@ -3,7 +3,7 @@ #****************************************************************************** # icondict.py, provides a class to load and store icons # -# Copyright (C) 2011, Douglas W. Bell +# Copyright (C) 2017, 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 @@ -11,8 +11,7 @@ # but WITTHOUT ANY WARRANTY. See the included LICENSE file for details. #****************************************************************************** -import os.path -from PyQt4 import QtCore, QtGui +from PyQt5.QtGui import QIcon, QPixmap _iconExtension = ('.png', '.bmp', '.ico', '.gif') defaultName = 'default' @@ -21,39 +20,39 @@ class IconDict(dict): """Loads and stores icons by name. """ - def __init__(self, potentialPaths, subPaths=None): + def __init__(self, pathObjList, subPaths=None): """Set icon paths and initialize variables. The first potential path that has icons is used. Arguments: - potentialPaths -- a list of path names to check for icons + pathObjList -- a list of path objects to check for icons subPaths -- a list of optional subpaths under the base paths """ super().__init__() - self.pathList = [] + self.pathObjList = [] self.subPaths = [''] - self.addIconPath(potentialPaths, subPaths) + self.addIconPath(pathObjList, subPaths) self.allLoaded = False self[noneName] = None - def addIconPath(self, potentialPaths, subPaths=None): + def addIconPath(self, pathObjList, subPaths=None): """Add an icon path and set the subPaths if given. Arguments: - potentialPaths -- a list of path names to check for icons + pathObjList -- a list of path objects to check for icons subPaths -- a list of optional subpaths under the base paths """ if subPaths: self.subPaths = subPaths - for path in potentialPaths: + for mainPath in pathObjList: for subPath in self.subPaths: + dirPath = mainPath / subPath try: - for name in os.listdir(os.path.join(path, subPath)): - pixmap = QtGui.QPixmap(os.path.join(path, subPath, - name)) + for fullPath in dirPath.iterdir(): + pixmap = QPixmap(str(fullPath)) if not pixmap.isNull(): - if path not in self.pathList: - self.pathList.append(path) + if mainPath not in self.pathObjList: + self.pathObjList.append(mainPath) break except OSError: pass @@ -82,13 +81,12 @@ Arguments: name -- the name of the icon to load """ - icon = QtGui.QIcon() - for path in self.pathList: + icon = QIcon() + for path in self.pathObjList: for ext in _iconExtension: fileName = name + ext for subPath in self.subPaths: - pixmap = QtGui.QPixmap(os.path.join(path, subPath, - fileName)) + pixmap = QPixmap(str(path.joinpath(subPath, fileName))) if not pixmap.isNull(): icon.addPixmap(pixmap) if not icon.isNull(): @@ -110,19 +108,15 @@ """ self.clear() self[noneName] = None - for path in self.pathList: + for mainPath in self.pathObjList: for subPath in self.subPaths: + dirPath = mainPath / subPath try: - for name in os.listdir(os.path.join(path, subPath)): - pixmap = QtGui.QPixmap(os.path.join(path, subPath, - name)) + for fullPath in dirPath.iterdir(): + pixmap = QPixmap(str(fullPath)) if not pixmap.isNull(): - name = os.path.splitext(name)[0] - try: - icon = self[name] - except KeyError: - icon = QtGui.QIcon() - self[name] = icon + name = fullPath.stem + icon = self.setdefault(name, QIcon()) icon.addPixmap(pixmap) except OSError: pass diff -Nru treeline-2.0.2/source/imports.py treeline-3.0.1/source/imports.py --- treeline-2.0.2/source/imports.py 2015-09-23 01:39:06.000000000 +0000 +++ treeline-3.0.1/source/imports.py 2018-09-14 01:59:15.000000000 +0000 @@ -4,7 +4,7 @@ # imports.py, provides classes for a file import dialog and import functions # # TreeLine, an information storage program -# Copyright (C) 2015, Douglas W. Bell +# Copyright (C) 2018, 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 @@ -12,42 +12,67 @@ # but WITTHOUT ANY WARRANTY. See the included LICENSE file for details. #****************************************************************************** -import os.path +import pathlib import re import collections import zipfile +import csv import html.parser +import xml.sax.saxutils from xml.etree import ElementTree -from PyQt4 import QtCore, QtGui +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import QApplication, QDialog, QFileDialog, QMessageBox import miscdialogs import treenode +import treestructure import treemodel import nodeformat import treeformats +import urltools import globalref methods = collections.OrderedDict() -methods.update([(_('&Tab indented text, one node per line'), +methods.update([(_('Text'), None), + (_('&Tab indented text, one node per line'), 'importTabbedText'), + (_('Co&mma delimited (CSV) text table with level column && ' + 'header row'), 'importTableCsvLevels'), + (_('Comma delimited (CSV) text table &with header row'), + 'importTableCsv'), (_('Tab delimited text table with header &row'), - 'importTableText'), - (_('Plain text, one node per &line (CR delimited)'), + 'importTableTabbed'), + (_('Plain text, one &node per line (CR delimited)'), 'importTextLines'), (_('Plain text ¶graphs (blank line delimited)'), 'importTextPara'), + (_('Bookmarks'), None), + (_('&HTML bookmarks (Mozilla Format)'), 'importMozilla'), + (_('&XML bookmarks (XBEL format)'), 'importXbel'), + (_('Other'), None), + (_('Old Tree&Line File (1.x or 2.x)'), 'importOldTreeLine'), (_('Treepad &file (text nodes only)'), 'importTreePad'), (_('&Generic XML (non-TreeLine file)'), 'importXml'), - (_('Open &Document (ODF) outline'), 'importOdfText'), - (_('&HTML bookmarks (Mozilla Format)'), 'importMozilla'), - (_('&XML bookmarks (XBEL format)'), 'importXbel')]) -fileFilters = {'importTabbedText': 'txt', 'importTableText': 'txt', - 'importTextLines': 'txt', 'importTextPara': 'txt', + (_('Open &Document (ODF) outline'), 'importOdfText')]) +fileFilters = {'importTabbedText': 'txt', + 'importTableCsvLevels': 'csv', + 'importTableCsv': 'csv', + 'importTableTabbed': 'txt', + 'importTextLines': 'txt', + 'importTextPara': 'txt', + 'importMozilla': 'html', + 'importXbel': 'xml', + 'importOldTreeLine': 'trl', 'importTreePad': 'hjt', 'importXml': 'xml', - 'importOdfText': 'odt', - 'importMozilla': 'html', - 'importXbel': 'xml'} + 'importOdfText': 'odt'} + +oldDateTimeConv = {'d': '%-d', 'dd': '%d', 'ddd': '%a', 'dddd': '%A', + 'M': '%-m', 'MM': '%m', 'MMM': '%b', 'MMMM': '%B', + 'yy': '%y', 'yyyy': '%Y', + 'H': '%-H', 'HH': '%H', 'h': '%-I', 'hh': '%I', + 'm': '%-M', 'mm': '%M', 's': '%-S', 'ss': '%S', + 'zzz': '%f', 'AP': '%p', 'ap': '%p'} bookmarkFolderTypeName = _('FOLDER') bookmarkLinkTypeName = _('BOOKMARK') bookmarkSeparatorTypeName = _('SEPARATOR') @@ -60,104 +85,194 @@ class ImportControl: """Control file imports of alt file types. """ - def __init__(self, filePath=''): + def __init__(self, pathObj=None): """Initialize the import control object. Arguments: - filePath -- the file to import from if give, otherwise prompt user + pathObj -- the path object to import if given, o/w prompt user """ - self.filePath = filePath + self.pathObj = pathObj self.errorMessage = '' + # below members for old TreeLine file imports + self.treeLineImportVersion = [] + self.treeLineRootAttrib = {} + self.treeLineOldFieldAttr = {} def interactiveImport(self, addWarning=False): """Prompt the user for import type & proceed with import. - Return the model if import is successful, otherwise None + Return the structure if import is successful, otherwise None Arguments: addWarning - if True, add non-valid file warning to dialog """ dialog = miscdialogs.RadioChoiceDialog(_('Import File'), _('Choose Import Method'), methods.items(), - QtGui.QApplication. + QApplication. activeWindow()) if addWarning: - fileName = os.path.basename(self.filePath) + fileName = self.pathObj.name dialog.addLabelBox(_('Invalid File'), _('"{0}" is not a valid TreeLine file.\n\n' 'Use an import filter?').format(fileName)) - if dialog.exec_() != QtGui.QDialog.Accepted: + if dialog.exec_() != QDialog.Accepted: return None method = dialog.selectedButton() - if not self.filePath: + if not self.pathObj: filters = ';;'.join((globalref.fileFilters[fileFilters[method]], globalref.fileFilters['all'])) - defaultFilePath = globalref.mainControl.defaultFilePath(True) - self.filePath = QtGui.QFileDialog.getOpenFileName(QtGui. - QApplication.activeWindow(), + defaultFilePath = str(globalref.mainControl.defaultPathObj(True)) + filePath, selFltr = QFileDialog.getOpenFileName(QApplication. + activeWindow(), _('TreeLine - Import File'), defaultFilePath, filters) - if not self.filePath: + if not filePath: return None + self.pathObj = pathlib.Path(filePath) self.errorMessage = '' try: - QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) - model = getattr(self, method)() - QtGui.QApplication.restoreOverrideCursor() + QApplication.setOverrideCursor(Qt.WaitCursor) + structure = getattr(self, method)() + QApplication.restoreOverrideCursor() except IOError: - QtGui.QApplication.restoreOverrideCursor() - QtGui.QMessageBox.warning(QtGui.QApplication.activeWindow(), + QApplication.restoreOverrideCursor() + QMessageBox.warning(QApplication.activeWindow(), 'TreeLine', _('Error - could not read file {0}'). - format(self.filePath)) + format(self.pathObj)) return None - if not model: - message = _('Error - improper format in {0}').format(self.filePath) + if not structure: + message = _('Error - improper format in {0}').format(self.pathObj) if self.errorMessage: message = '{0}\n{1}'.format(message, self.errorMessage) self.errorMessage = '' - QtGui.QMessageBox.warning(QtGui.QApplication.activeWindow(), - 'TreeLine', message) - return model + QMessageBox.warning(QApplication.activeWindow(), 'TreeLine', + message) + return structure def importTabbedText(self): """Import a file with tabbed title structure. - Return the model if import is successful, otherwise None + Return the structure if import is successful, otherwise None """ - textLevelList = [] - with open(self.filePath, 'r', - encoding=globalref.localTextEncoding) as f: + structure = treestructure.TreeStructure(addDefaults=True, + addSpots=False) + formatRef = structure.childList[0].formatRef + structure.removeNodeDictRef(structure.childList[0]) + structure.childList = [] + nodeList = [] + with self.pathObj.open(encoding=globalref.localTextEncoding) as f: for line in f: text = line.strip() if text: level = line.count('\t', 0, len(line) - len(line.lstrip())) - textLevelList.append((text, level)) - if textLevelList: - if len([item for item in textLevelList if item[1] == 0]) > 1: - textLevelList = [(text, level + 1) for text, level in - textLevelList] - textLevelList.insert(0, (treemodel.defaultRootName, 0)) - model = treemodel.TreeModel(True) - text, level = textLevelList.pop(0) - if level == 0: - model.root.setTitle(text) - if model.root.loadChildLevels(textLevelList): - return model + node = treenode.TreeNode(formatRef) + node.setTitle(text) + structure.addNodeDictRef(node) + nodeList.append((node, level)) + if nodeList and structure.loadChildNodeLevels(nodeList): + structure.generateSpots(None) + return structure return None - def importTableText(self): + def importTableCsvLevels(self): + """Import a CSV-delimited table file with level column, header row. + + Return the structure if import is successful, otherwise None. + """ + structure = treestructure.TreeStructure(addSpots=False) + tableFormat = nodeformat.NodeFormat(_('TABLE'), structure.treeFormats) + structure.treeFormats.addTypeIfMissing(tableFormat) + nodeList = [] + with self.pathObj.open(newline='', + encoding=globalref.localTextEncoding) as f: + reader = csv.reader(f) + try: + headings = [self.correctFieldName(name) for name in + next(reader)][1:] + tableFormat.addFieldList(headings, True, True) + for entries in reader: + if entries: + node = treenode.TreeNode(tableFormat) + structure.addNodeDictRef(node) + try: + level = int(entries.pop(0)) + except ValueError: + self.errorMessage = (_('Invalid level number on ' + 'line {0}'). + format(reader.line_num)) + return None # abort + nodeList.append((node, level)) + try: + for heading in headings: + node.data[heading] = entries.pop(0) + except IndexError: + pass # fewer entries than headings is OK + if entries: + self.errorMessage = (_('Too many entries on ' + 'Line {0}'). + format(reader.line_num)) + return None # abort if too few headings + except csv.Error: + self.errorMessage = (_('Bad CSV format on Line {0}'). + format(reader.line_num)) + return None # abort + if nodeList: + if structure.loadChildNodeLevels(nodeList): + structure.generateSpots(None) + return structure + self.errorMessage = (_('Invalid level structure')) + return None + + def importTableCsv(self): + """Import a file with a CSV-delimited table with header row. + + Return the structure if import is successful, otherwise None. + """ + structure = treestructure.TreeStructure(addDefaults=True, + addSpots=False) + tableFormat = nodeformat.NodeFormat(_('TABLE'), structure.treeFormats) + structure.treeFormats.addTypeIfMissing(tableFormat) + with self.pathObj.open(newline='', + encoding=globalref.localTextEncoding) as f: + reader = csv.reader(f) + try: + headings = [self.correctFieldName(name) for name in + next(reader)] + tableFormat.addFieldList(headings, True, True) + for entries in reader: + if entries: + node = treenode.TreeNode(tableFormat) + structure.childList[0].childList.append(node) + structure.addNodeDictRef(node) + try: + for heading in headings: + node.data[heading] = entries.pop(0) + except IndexError: + pass # fewer entries than headings is OK + if entries: + self.errorMessage = (_('Too many entries on ' + 'Line {0}'). + format(reader.line_num)) + return None # abort if too few headings + except csv.Error: + self.errorMessage = (_('Bad CSV format on Line {0}'). + format(reader.line_num)) + return None # abort + structure.generateSpots(None) + return structure + + def importTableTabbed(self): """Import a file with a tab-delimited table with header row. - Return the model if import is successful, otherwise None. + Return the structure if import is successful, otherwise None. """ - model = treemodel.TreeModel(True) - typeName = _('TABLE') - tableFormat = nodeformat.NodeFormat(typeName, model.formats) - model.formats.addTypeIfMissing(tableFormat) - with open(self.filePath, 'r', - encoding=globalref.localTextEncoding) as f: - headings = [self.correctFieldName(name) for name in + structure = treestructure.TreeStructure(addDefaults=True, + addSpots=False) + tableFormat = nodeformat.NodeFormat(_('TABLE'), structure.treeFormats) + structure.treeFormats.addTypeIfMissing(tableFormat) + with self.pathObj.open(encoding=globalref.localTextEncoding) as f: + headings = [self.correctFieldName(name) for name in f.readline().split('\t')] tableFormat.addFieldList(headings, True, True) lineNum = 1 @@ -165,8 +280,9 @@ lineNum += 1 if line.strip(): entries = line.split('\t') - node = treenode.TreeNode(model.root, typeName, model) - model.root.childList.append(node) + node = treenode.TreeNode(tableFormat) + structure.childList[0].childList.append(node) + structure.addNodeDictRef(node) try: for heading in headings: node.data[heading] = entries.pop(0) @@ -176,8 +292,8 @@ self.errorMessage = (_('Too many entries on Line {0}'). format(lineNum)) return None # abort if too few headings - node.setUniqueId(True) - return model + structure.generateSpots(None) + return structure @staticmethod def correctFieldName(name): @@ -196,53 +312,334 @@ def importTextLines(self): """Import a text file, creating one node per line. - Return the model if import is successful, otherwise None. + Return the structure if import is successful, otherwise None. """ - model = treemodel.TreeModel(True) - with open(self.filePath, 'r', - encoding=globalref.localTextEncoding) as f: + structure = treestructure.TreeStructure(addDefaults=True, + addSpots=False) + nodeFormat = structure.childList[0].formatRef + structure.removeNodeDictRef(structure.childList[0]) + structure.childList = [] + with self.pathObj.open(encoding=globalref.localTextEncoding) as f: for line in f: line = line.strip() if line: - node = treenode.TreeNode(model.root, model.root.formatName, - model) - model.root.childList.append(node) + node = treenode.TreeNode(nodeFormat) + structure.childList.append(node) + structure.addNodeDictRef(node) node.data[nodeformat.defaultFieldName] = line - node.setUniqueId(True) - return model + structure.generateSpots(None) + return structure def importTextPara(self): """Import a text file, creating one node per paragraph. Blank line delimited. - Return the model if import is successful, otherwise None. + Return the structure if import is successful, otherwise None. """ - model = treemodel.TreeModel(True) - with open(self.filePath, 'r', - encoding=globalref.localTextEncoding) as f: + structure = treestructure.TreeStructure(addDefaults=True, + addSpots=False) + nodeFormat = structure.childList[0].formatRef + structure.removeNodeDictRef(structure.childList[0]) + structure.childList = [] + with self.pathObj.open(encoding=globalref.localTextEncoding) as f: text = f.read() paraList = text.split('\n\n') for para in paraList: para = para.strip() if para: - node = treenode.TreeNode(model.root, model.root.formatName, - model) - model.root.childList.append(node) + node = treenode.TreeNode(nodeFormat) + structure.childList.append(node) + structure.addNodeDictRef(node) node.data[nodeformat.defaultFieldName] = para - node.setUniqueId(True) - return model + structure.generateSpots(None) + return structure + + def importOldTreeLine(self): + """Import an old TreeLine File (1.x or 2.x). + + Return the structure if import is successful, otherwise None. + """ + tree = ElementTree.ElementTree() + try: + tree.parse(str(self.pathObj)) + except ElementTree.ParseError: + tree = None + if not tree or not tree.getroot().get('item') == 'y': + fileObj = self.pathObj.open('rb') + # decompress before decrypt to support TreeLine 1.4 and earlier + fileObj, compressed = globalref.mainControl.decompressFile(fileObj) + fileObj, encrypted = globalref.mainControl.decryptFile(fileObj) + if not fileObj: + return None + if encrypted and not compressed: + fileObj, compressed = (globalref.mainControl. + decompressFile(fileObj)) + if compressed or encrypted: + tree = ElementTree.ElementTree() + try: + tree.parse(fileObj) + except ElementTree.ParseError: + tree = None + fileObj.close() + if not tree or not tree.getroot().get('item') == 'y': + return None + version = tree.getroot().get('tlversion', '').split('.') + try: + self.treeLineImportVersion = [int(i) for i in version] + except ValueError: + pass + self.treeLineRootAttrib = self.convertPrintData(tree.getroot().attrib) + structure = treestructure.TreeStructure() + idRefDict = {} + linkList = [] + self.loadOldTreeLineNode(tree.getroot(), structure, idRefDict, + linkList, None) + self.convertOldNodes(structure) + linkRe = re.compile(r']*href="#(.*?)"[^>]*>.*?', re.I | re.S) + for node, fieldName in linkList: + text = node.data[fieldName] + startPos = 0 + while True: + match = linkRe.search(text, startPos) + if not match: + break + newId = idRefDict.get(match.group(1), '') + if newId: + text = text[:match.start(1)] + newId + text[match.end(1):] + startPos = match.start(1) + node.data[fieldName] = text + structure.generateSpots(None) + if nodeformat.FileInfoFormat.typeName in structure.treeFormats: + fileFormat = structure.treeFormats[nodeformat.FileInfoFormat. + typeName] + structure.treeFormats.fileInfoFormat.duplicateFileInfo(fileFormat) + del structure.treeFormats[nodeformat.FileInfoFormat.typeName] + structure.treeFormats.updateDerivedRefs() + for nodeFormat in structure.treeFormats.values(): + nodeFormat.updateLineParsing() + return structure + + def loadOldTreeLineNode(self, element, structure, idRefDict, linkList, + parent=None): + """Recursively load an old TreeLine ElementTree node and its children. + + Arguments: + element -- an ElementTree node + structure -- a ref to the new tree structure + idRefDict -- a dict to relate old to new unique node IDs + linkList -- internal link list ref with (node, fieldname) tuples + parent -- the parent TreeNode (None for the root node only) + """ + try: + typeFormat = structure.treeFormats[element.tag] + except KeyError: + formatData = self.convertOldNodeFormat(element.attrib) + typeFormat = nodeformat.NodeFormat(element.tag, + structure.treeFormats, + formatData) + structure.treeFormats[element.tag] = typeFormat + self.treeLineOldFieldAttr[typeFormat.name] = {} + if element.get('item') == 'y': + node = treenode.TreeNode(typeFormat) + oldId = element.attrib.get('uniqueid', '') + if oldId: + idRefDict[oldId] = node.uId + if parent: + parent.childList.append(node) + else: + structure.childList.append(node) + structure.nodeDict[node.uId] = node + cloneAttr = element.attrib.get('clones', '') + if cloneAttr: + for cloneId in cloneAttr.split(','): + if cloneId in idRefDict: + cloneNode = structure.nodeDict[idRefDict[cloneId]] + node.data = cloneNode.data.copy() + break + else: # bare format (no nodes) + node = None + for child in element: + if child.get('item') and node: + self.loadOldTreeLineNode(child, structure, idRefDict, + linkList, node) + else: + if node and child.text: + node.data[child.tag] = child.text + if child.get('linkcount'): + linkList.append((node, child.tag)) + if child.tag not in typeFormat.fieldDict: + fieldData = self.convertOldFieldFormat(child.attrib) + oldFormatDict = self.treeLineOldFieldAttr[typeFormat.name] + oldFormatDict[child.tag] = fieldData + typeFormat.addField(child.tag, fieldData) + + def convertPrintData(self, attrib): + """Return JSON print data from old root attributes. + + Arguments: + attrib -- old root print data attributes + """ + for key in ('printlines', 'printwidowcontrol', 'printportrait'): + if key in attrib: + attrib[key] = not attrib[key].startswith('n') + for key in ('printindentfactor', 'printpaperwidth', 'printpaperheight', + 'printheadermargin', 'printfootermargin', + 'printcolumnspace'): + if key in attrib: + attrib[key] = float(attrib[key]) + if 'printmargins' in attrib: + attrib['printmargins'] = [float(margin) for margin in + attrib['printmargins'].split()] + if 'printnumcolumns' in attrib: + attrib['printnumcolumns'] = int(attrib['printnumcolumns']) + return attrib + + def convertOldNodeFormat(self, attrib): + """Return JSON format data from old node format attributes. + + Arguments: + attrib -- old node format attrib dict + """ + for key in ('spacebetween', 'formathtml', 'bullets', 'tables'): + if key in attrib: + attrib[key] = attrib[key].startswith('y') + attrib['titleline'] = attrib.get('line0', '') + lineKeyRe = re.compile(r'line\d+$') + lineNums = sorted([int(key[4:]) for key in attrib.keys() + if lineKeyRe.match(key)]) + if lineNums and lineNums[0] == 0: + del lineNums[0] + attrib['outputlines'] = [attrib['line{0}'.format(keyNum)] for + keyNum in lineNums] + if self.treeLineImportVersion < [1, 9]: # for very old TL versions + attrib['spacebetween'] = not (self.treeLineRootAttrib. + get('nospace', '').startswith('y')) + attrib['formathtml'] = not (self.treeLineRootAttrib. + get('nohtml', '').startswith('y')) + return attrib + + def convertOldFieldFormat(self, attrib): + """Return JSON format data from old field format attributes. + + Arguments: + attrib -- old field node format attrib dict + """ + fieldType = attrib.get('type', '') + if fieldType: + attrib['fieldtype'] = fieldType + fieldFormat = attrib.get('format', '') + if self.treeLineImportVersion < [1, 9]: # for very old TL versions + if fieldType in ('URL', 'Path', 'ExecuteLink', 'Email'): + attrib['oldfieldtype'] = fieldType + fieldType = 'ExternalLink' + attrib['fieldtype'] = fieldType + if fieldType == 'Date': + fieldFormat = fieldFormat.replace('w', 'd') + fieldFormat = fieldFormat.replace('m', 'M') + if fieldType == 'Time': + fieldFormat = fieldFormat.replace('M', 'm') + fieldFormat = fieldFormat.replace('s', 'z') + fieldFormat = fieldFormat.replace('S', 's') + fieldFormat = fieldFormat.replace('AA', 'AP') + fieldFormat = fieldFormat.replace('aa', 'ap') + if 'lines' in attrib: + attrib['lines'] = int(attrib['lines']) + if 'sortkeynum' in attrib: + attrib['sortkeynum'] = int(attrib['sortkeynum']) + if 'sortkeydir' in attrib: + attrib['sortkeyfwd'] = not attrib['sortkeydir'].startswith('r') + if 'evalhtml' in attrib: + attrib['evalhtml'] = attrib['evalhtml'].startswith('y') + if fieldType in ('Date', 'Time', 'DateTime'): + origFormat = fieldFormat + fieldFormat = '' + while origFormat: + replLen = 4 + while replLen > 0: + if origFormat[:replLen] in oldDateTimeConv: + fieldFormat += oldDateTimeConv[origFormat[:replLen]] + origFormat = origFormat[replLen:] + break + replLen -= 1 + if replLen == 0: + fieldFormat += origFormat[0] + origFormat = origFormat[1:] + if fieldFormat: + attrib['format'] = fieldFormat + return attrib + + def convertOldNodes(self, structure): + """Convert node data to new date and time formats. + + Arguments: + structure -- the ref structure containing the data + """ + for node in structure.nodeDict.values(): + for field in node.formatRef.fields(): + text = node.data.get(field.name, '') + if text: + if field.typeName in ('Date', 'DateTime'): + text = text.replace('/', '-') + if field.typeName in ('Time', 'DateTime'): + text = text + '.000000' + if self.treeLineImportVersion < [1, 9]: # very old TL ver + oldFormatDict = self.treeLineOldFieldAttr[node. + formatRef.name] + oldFieldAttr = oldFormatDict[field.name] + if (field.typeName == 'Text' and not + oldFieldAttr.get('html', '').startswith('y')): + text = text.strip() + text = xml.sax.saxutils.escape(text) + text = text.replace('\n', '
\n') + elif (field.typeName == 'ExternalLink' and + oldFieldAttr.get('oldfieldtype', '')): + oldType = oldFieldAttr['oldfieldtype'] + linkAltField = oldFieldAttr.get('linkalt', '') + dispName = node.data.get(linkAltField, '') + if not dispName: + dispName = text + if oldType == 'URL': + if not urltools.extractScheme(text): + text = urltools.replaceScheme('http', text) + elif oldType == 'Path': + text = urltools.replaceScheme('file', text) + elif oldType == 'ExecuteLink': + if urltools.isRelative(text): + fullPath = urltools.which(text) + if fullPath: + text = fullPath + text = urltools.replaceScheme('file', text) + elif oldType == 'Email': + text = urltools.replaceScheme('mailto', text) + text = '{1}'.format(text, + dispName) + elif field.typeName == 'InternalLink': + linkAltField = oldFieldAttr.get('linkalt', '') + dispName = node.data.get(linkAltField, '') + if not dispName: + dispName = text + uniqueId = text.strip().split('\n', 1)[0] + uniqueId = uniqueId.replace(' ', '_').lower() + uniqueId = re.sub(r'[^a-zA-Z0-9_-]+', '', uniqueId) + text = '{1}'.format(uniqueId, + dispName) + elif field.typeName == 'Picture': + text = ''.format(text) + node.data[field.name] = text def importTreePad(self): """Import a Treepad file, text nodes only. Return the model if import is successful, otherwise None. """ - model = treemodel.TreeModel(True) - tpFormat = model.formats[treeformats.defaultTypeName] + structure = treestructure.TreeStructure(addDefaults=True, + addSpots=False) + structure.removeNodeDictRef(structure.childList[0]) + structure.childList = [] + tpFormat = structure.treeFormats[treeformats.defaultTypeName] tpFormat.addFieldList([textFieldName], False, True) tpFormat.fieldDict[textFieldName].changeType('SpacedText') - with open(self.filePath, 'r', - encoding=globalref.localTextEncoding) as f: + with self.pathObj.open(encoding=globalref.localTextEncoding) as f: textList = f.read().split(' 5P9i0s8y19Z') nodeList = [] for text in textList: @@ -256,12 +653,12 @@ lines = lines[2:] except (ValueError, IndexError): return None - node = treenode.TreeNode(None, tpFormat.name, model) + node = treenode.TreeNode(tpFormat) node.data[nodeformat.defaultFieldName] = title node.data[textFieldName] = '\n'.join(lines) node.level = level - node.setUniqueId(True) nodeList.append(node) + structure.addNodeDictRef(node) parentList = [] for node in nodeList: if node.level != 0: @@ -269,54 +666,53 @@ node.parent = parentList[-1] parentList[-1].childList.append(node) parentList.append(node) - model.root = nodeList[0] - return model + structure.childList = [nodeList[0]] + structure.generateSpots(None) + return structure def importXml(self): """Import a non-treeline generic XML file. - Return the model if import is successful, otherwise None. + Return the structure if import is successful, otherwise None. """ - model = treemodel.TreeModel() + structure = treestructure.TreeStructure() tree = ElementTree.ElementTree() try: - tree.parse(self.filePath) - self.loadXmlNode(tree.getroot(), model, None) + tree.parse(str(self.pathObj)) + self.loadXmlNode(tree.getroot(), structure, None) except ElementTree.ParseError: return None - for elemFormat in model.formats.values(): # fix formats if required - if not elemFormat.getLines()[0]: + for elemFormat in structure.treeFormats.values(): + if not elemFormat.getTitleLine(): # fix formats if required elemFormat.changeTitleLine(elemFormat.name) for fieldName in elemFormat.fieldNames(): elemFormat.addOutputLine('{0}="{{*{1}*}}"'. format(fieldName, fieldName)) if not elemFormat.fieldDict: elemFormat.addField(genericXmlTextFieldName) - if model.root: - for node in model.root.descendantGen(): - node.updateUniqueId() - return model + if structure.childList: + structure.generateSpots(None) + return structure return None - def loadXmlNode(self, element, model, parent=None): + def loadXmlNode(self, element, structure, parent=None): """Recursively load a generic XML ElementTree node and its children. Arguments: element -- an XML ElementTree node - model -- a ref to the TreeLine model + structure -- a ref to the TreeLine structure parent -- the parent TreeNode (None for the root node only) """ - elemFormat = model.formats.get(element.tag, None) + elemFormat = structure.treeFormats.get(element.tag, None) if not elemFormat: - elemFormat = nodeformat.NodeFormat(element.tag, model.formats) - model.formats[element.tag] = elemFormat - node = treenode.TreeNode(parent, elemFormat.name, model) - if parent: - parent.childList.append(node) - elif model.root: - raise ElementTree.ParseError # invalid with two roots - else: - model.root = node + elemFormat = nodeformat.NodeFormat(element.tag, + structure.treeFormats) + structure.treeFormats[element.tag] = elemFormat + node = treenode.TreeNode(elemFormat) + structure.addNodeDictRef(node) + if not parent: + parent = structure + parent.childList.append(node) if element.text and element.text.strip(): if genericXmlTextFieldName not in elemFormat.fieldDict: elemFormat.addFieldList([genericXmlTextFieldName], True, True) @@ -325,22 +721,25 @@ elemFormat.addFieldIfNew(key) node.data[key] = value for child in element: - self.loadXmlNode(child, model, node) + self.loadXmlNode(child, structure, node) def importOdfText(self): """Import an ODF format text file outline. - Return the model if import is successful, otherwise None. + Return the structure if import is successful, otherwise None. """ - model = treemodel.TreeModel(True) - odfFormat = model.formats[treeformats.defaultTypeName] + structure = treestructure.TreeStructure(addDefaults=True, + addSpots=False) + structure.removeNodeDictRef(structure.childList[0]) + structure.childList = [] + odfFormat = structure.treeFormats[treeformats.defaultTypeName] odfFormat.addField(textFieldName) odfFormat.changeOutputLines(['{{*{0}*}}'. format(nodeformat.defaultFieldName), '{{*{0}*}}'.format(textFieldName)]) odfFormat.formatHtml = True try: - with zipfile.ZipFile(self.filePath, 'r') as f: + with zipfile.ZipFile(str(self.pathObj), 'r') as f: text = f.read('content.xml') except (zipfile.BadZipFile, KeyError): return None @@ -352,8 +751,8 @@ headerTag = '{0}h'.format(nameSpace) paraTag = '{0}p'.format(nameSpace) numRegExp = re.compile(r'.*?(\d+)$') - currentNode = model.root - currentLevel = 0 + parents = [structure] + prevLevel = 0 for elem in rootElement.iter(): if elem.tag == headerTag: style = elem.get('{0}style-name'.format(nameSpace), '') @@ -361,25 +760,24 @@ level = int(numRegExp.match(style).group(1)) except AttributeError: return None - if level < 1 or level > currentLevel + 1: + if level < 1 or level > prevLevel + 1: return None - while(currentLevel >= level): - currentNode = currentNode.parent - currentLevel -= 1 - node = treenode.TreeNode(currentNode, odfFormat.name, model) - currentNode.childList.append(node) + parents = parents[:level] + node = treenode.TreeNode(odfFormat) + structure.addNodeDictRef(node) + parents[-1].childList.append(node) node.data[nodeformat.defaultFieldName] = ''.join(elem. itertext()) - node.setUniqueId(True) - currentNode = node - currentLevel = level + parents.append(node) + prevLevel = level elif elem.tag == paraTag: text = ''.join(elem.itertext()) - origText = currentNode.data.get(textFieldName, '') + origText = node.data.get(textFieldName, '') if origText: text = '{0}
{1}'.format(origText, text) node.data[textFieldName] = text - return model + structure.generateSpots(None) + return structure def createBookmarkFormat(self): """Return a set of node formats for bookmark imports. @@ -391,12 +789,13 @@ treeFormats[folderFormat.name] = folderFormat linkFormat = nodeformat.NodeFormat(bookmarkLinkTypeName, treeFormats, addDefaultField=True) - linkFormat.addField(bookmarkLinkFieldName, {'type': 'ExternalLink'}) + linkFormat.addField(bookmarkLinkFieldName, + {'fieldtype': 'ExternalLink'}) linkFormat.addOutputLine('{{*{0}*}}'.format(bookmarkLinkFieldName)) linkFormat.iconName = 'bookmark' treeFormats[linkFormat.name] = linkFormat sepFormat = nodeformat.NodeFormat(bookmarkSeparatorTypeName, - treeFormats, {'formathtml': 'y'}, + treeFormats, {'formathtml': True}, True) sepFormat.changeTitleLine('------------------') sepFormat.changeOutputLines(['
']) @@ -406,38 +805,40 @@ def importMozilla(self): """Import an HTML mozilla-format bookmark file. - Return the model if import is successful, otherwise None. + Return the structure if import is successful, otherwise None. """ - model = treemodel.TreeModel() - model.formats = self.createBookmarkFormat() - with open(self.filePath, 'r', encoding='utf-8') as f: + structure = treestructure.TreeStructure() + structure.treeFormats = self.createBookmarkFormat() + with self.pathObj.open(encoding='utf-8') as f: text = f.read() try: - handler = HtmlBookmarkHandler(model) + handler = HtmlBookmarkHandler(structure) handler.feed(text) handler.close() - except html.parser.HTMLParseError: + except ValueError: return None - return model + structure.generateSpots(None) + return structure def importXbel(self): """Import an XBEL format bookmark file. - Return the model if import is successful, otherwise None. + Return the structure if import is successful, otherwise None. """ - model = treemodel.TreeModel() - model.formats = self.createBookmarkFormat() + structure = treestructure.TreeStructure() + structure.treeFormats = self.createBookmarkFormat() tree = ElementTree.ElementTree() try: - tree.parse(self.filePath) + tree.parse(str(self.pathObj)) except ElementTree.ParseError: return None - self.loadXbelNode(tree.getroot(), model, None) - if model.root: - return model + self.loadXbelNode(tree.getroot(), structure, None) + if structure.childList: + structure.generateSpots(None) + return structure return None - def loadXbelNode(self, element, model, parent=None): + def loadXbelNode(self, element, structure, parent=None): """Recursively load an XBEL ElementTree node and its children. Arguments: @@ -446,28 +847,33 @@ parent -- the parent TreeNode (None for the root node only) """ if element.tag in ('xbel', 'folder'): - node = treenode.TreeNode(parent, bookmarkFolderTypeName, model) + node = treenode.TreeNode(structure. + treeFormats[bookmarkFolderTypeName]) + structure.addNodeDictRef(node) if parent: parent.childList.append(node) else: - model.root = node + structure.childList.append(node) for child in element: - self.loadXbelNode(child, model, node) + self.loadXbelNode(child, structure, node) elif element.tag == 'bookmark': - node = treenode.TreeNode(parent, bookmarkLinkTypeName, model) + node = treenode.TreeNode(structure. + treeFormats[bookmarkLinkTypeName]) + structure.addNodeDictRef(node) parent.childList.append(node) link = element.get('href').strip() if link: node.data[bookmarkLinkFieldName] = ('{1}'. format(link, link)) for child in element: - self.loadXbelNode(child, model, node) + self.loadXbelNode(child, structure, node) elif element.tag == 'title': parent.setTitle(element.text) elif element.tag == 'separator': - node = treenode.TreeNode(parent, bookmarkSeparatorTypeName, model) + node = treenode.TreeNode(structure. + treeFormats[bookmarkSeparatorTypeName]) + structure.addNodeDictRef(node) parent.childList.append(node) - node.setUniqueId(True) else: # unsupported tags pass @@ -475,24 +881,26 @@ class HtmlBookmarkHandler(html.parser.HTMLParser): """Handler to parse HTML mozilla bookmark format. """ - def __init__(self, model): + def __init__(self, structure): """Initialize the HTML parser object. Arguments: - model -- a reference to the tree model + structure -- a reference to the tree structure """ super().__init__() - self.model = model - self.model.root = treenode.TreeNode(None, bookmarkFolderTypeName, - self.model) - self.model.root.data[nodeformat.defaultFieldName] = _('Bookmarks') - self.currentNode = self.model.root - self.currentParent = None + self.structure = structure + rootNode = treenode.TreeNode(self.structure. + treeFormats[bookmarkFolderTypeName]) + rootNode.data[nodeformat.defaultFieldName] = _('Bookmarks') + self.structure.addNodeDictRef(rootNode) + self.structure.childList = [rootNode] + self.currentNode = rootNode + self.parents = [] self.text = '' def handle_starttag(self, tag, attrs): """Called by the reader at each open tag. - + Arguments: tag -- the tag label attrs -- any tag attributes @@ -500,56 +908,55 @@ if tag == 'dt' or tag == 'h1': # start any entry self.text = '' elif tag == 'dl': # start indent - self.currentParent = self.currentNode + self.parents.append(self.currentNode) self.currentNode = None elif tag == 'h3': # start folder - if not self.currentParent: - raise html.parser.HTMLParseError - self.currentNode = treenode.TreeNode(self.currentParent, - bookmarkFolderTypeName, - self.model) - self.currentParent.childList.append(self.currentNode) + if not self.parents: + raise ValueError + self.currentNode = treenode.TreeNode(self.structure. + treeFormats[bookmarkFolderTypeName]) + self.structure.addNodeDictRef(self.currentNode) + self.parents[-1].childList.append(self.currentNode) elif tag == 'a': # start link - if not self.currentParent: - raise html.parser.HTMLParseError - self.currentNode = treenode.TreeNode(self.currentParent, - bookmarkLinkTypeName, - self.model) - self.currentParent.childList.append(self.currentNode) + if not self.parents: + raise ValueError + self.currentNode = treenode.TreeNode(self.structure. + treeFormats[bookmarkLinkTypeName]) + self.structure.addNodeDictRef(self.currentNode) + self.parents[-1].childList.append(self.currentNode) for name, value in attrs: if name == 'href': link = '{0}'.format(value) self.currentNode.data[bookmarkLinkFieldName] = link elif tag == 'hr': # separator - if not self.currentParent: - raise html.parser.HTMLParseError - node = treenode.TreeNode(self.currentParent, - bookmarkSeparatorTypeName, self.model) - node.setUniqueId(True) - self.currentParent.childList.append(node) + if not self.parents: + raise ValueError + node = treenode.TreeNode(self.structure. + treeFormats[bookmarkSeparatorTypeName]) + self.structure.addNodeDictRef(node) + self.parents[-1].childList.append(node) self.currentNode = None def handle_endtag(self, tag): """Called by the reader at each end tag. - + Arguments: tag -- the tag label """ if tag == 'dl': # end indented section - self.currentParent = self.currentParent.parent + self.parents = self.parents[:-1] self.currentNode = None elif tag == 'h3' or tag == 'a': # end folder or link if not self.currentNode: - raise html.parser.HTMLParseError + raise ValueError self.currentNode.data[nodeformat.defaultFieldName] = self.text - self.currentNode.updateUniqueId() elif tag == 'h1': # end main title - self.model.root.data[nodeformat.defaultFieldName] = self.text - self.currentNode.updateUniqueId() + self.structure.childList[0].data[nodeformat. + defaultFieldName] = self.text def handle_data(self, data): """Called by the reader to process text. - + Arguments: data -- the new text """ @@ -557,7 +964,7 @@ def handle_entityref(self, name): """Convert escaped entity ref to char. - + Arguments: name -- the name of the escaped entity """ diff -Nru treeline-2.0.2/source/linkref.py treeline-3.0.1/source/linkref.py --- treeline-2.0.2/source/linkref.py 2012-10-29 19:25:35.000000000 +0000 +++ treeline-3.0.1/source/linkref.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,136 +0,0 @@ -#!/usr/bin/env python3 - -#****************************************************************************** -# linkref.py, provides a class to store and update internal link references -# -# TreeLine, an information storage program -# Copyright (C) 2011, 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 -# version. This program is distributed in the hope that it will be useful, -# but WITTHOUT ANY WARRANTY. See the included LICENSE file for details. -#****************************************************************************** - -import re - -intLinkRegExp = re.compile(r']*href="#([^"]+)"[^>]*>.*?', - re.I | re.S) - - -class LinkRefItem: - """Class to store node, field and target info for a single internal link. - """ - def __init__(self, nodeRef, fieldName, targetId): - """Initialize the link info object. - - Arguments: - nodeRef -- the node with this link in a field - fieldName -- the field with theis link - targetId -- the unique ID of the target node - """ - self.nodeRef = nodeRef - self.fieldName = fieldName - self.targetId = targetId - - -class LinkRefCollection: - """Class to store and retrieve the link info for a tree model. - """ - def __init__(self): - """Initialize the collection. - - The targetIdDict is used to find sets of link ref items by target ID, - nodeRefDict finds sets of them by node and a nested field name dict. - """ - self.targetIdDict = {} - self.nodeRefDict = {} - - def addLink(self, nodeRef, fieldName, targetId): - """Add a new link ref object. - - Arguments: - nodeRef -- the node with this link in a field - fieldName -- the field with this link - targetId -- the unique ID of the target node - """ - link = LinkRefItem(nodeRef, fieldName, targetId) - linksByTarget = self.targetIdDict.setdefault(targetId, set()) - linksByTarget.add(link) - fieldDict = self.nodeRefDict.setdefault(nodeRef, {}) - linksByField = fieldDict.setdefault(fieldName, set()) - linksByField.add(link) - - def searchForLinks(self, nodeRef, fieldName): - """Add or update link ref objects for this field. - - Arguments: - nodeRef -- the node with this link in a field - fieldName -- the field with theis link - """ - self.removeFieldLinks(nodeRef, fieldName) - for match in intLinkRegExp.finditer(nodeRef.data[fieldName]): - self.addLink(nodeRef, fieldName, match.group(1)) - - def removeFieldLinks(self, nodeRef, fieldName): - """Remove all link ref objects for this field. - - Arguments: - nodeRef -- the node with this link in a field - fieldName -- the field with theis link - """ - fieldDict = self.nodeRefDict.setdefault(nodeRef, {}) - linkSet = fieldDict.setdefault(fieldName, set()) - for link in linkSet: - linksByTarget = self.targetIdDict[link.targetId] - linksByTarget.remove(link) - if not linksByTarget: - del self.targetIdDict[link.targetId] - del fieldDict[fieldName] - if not fieldDict: - del self.nodeRefDict[nodeRef] - - def removeNodeLinks(self, nodeRef): - """Remove link ref objects for all fields of this node. - - Arguments: - nodeRef -- the node with this link in a field - """ - fieldDict = self.nodeRefDict.setdefault(nodeRef, {}) - for linkSet in fieldDict.values(): - for link in linkSet: - linksByTarget = self.targetIdDict[link.targetId] - linksByTarget.remove(link) - if not linksByTarget: - del self.targetIdDict[link.targetId] - del self.nodeRefDict[nodeRef] - - def linkCount(self, nodeRef, fieldName): - """Return the number of links stored for the given field. - - Arguments: - nodeRef -- the node with this link in a field - fieldName -- the field with this link - """ - fieldDict = self.nodeRefDict.get(nodeRef, {}) - linkSet = fieldDict.get(fieldName, set()) - return len(linkSet) - - def renameTarget(self, oldTarget, newTarget): - """Rename all link ref objects that point to a target. - - Arguments: - oldTarget -- the original target name - newTarget -- the new target name - """ - links = self.targetIdDict.get(oldTarget, None) - if links: - linkRegExp = re.compile(r']*href="#{}"[^>]*>(.*?)'. - format(oldTarget), re.I | re.S) - for link in links: - link.targetId = newTarget - link.nodeRef.data[link.fieldName] = \ - linkRegExp.sub(r'\1'.format(newTarget), - link.nodeRef.data[link.fieldName]) - del self.targetIdDict[oldTarget] - self.targetIdDict[newTarget] = links diff -Nru treeline-2.0.2/source/matheval.py treeline-3.0.1/source/matheval.py --- treeline-2.0.2/source/matheval.py 2015-03-27 01:36:04.000000000 +0000 +++ treeline-3.0.1/source/matheval.py 2018-04-08 19:19:50.000000000 +0000 @@ -4,7 +4,7 @@ # matheval.py, provides a safe eval of mathematical expressions # # TreeLine, an information storage program -# Copyright (C) 2015, Douglas W. Bell +# Copyright (C) 2017, 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 @@ -14,6 +14,7 @@ import re import ast +import enum import builtins import gennumber from math import * @@ -213,7 +214,7 @@ eqnNode -- the node containing the equation to evaluate zeroValue -- the value to use for blanks """ - zeroBlanks = eqnNode.modelRef.mathZeroBlanks + zeroBlanks = eqnNode.treeStructureRef().mathZeroBlanks inputs = [ref.referenceValue(eqnNode, zeroBlanks) for ref in self.fieldRefs] if not zeroBlanks and None in inputs: @@ -222,7 +223,7 @@ eqn = self.formattedEqnText.format(*inputs) try: return eval(eqn) - except (TypeError, ZeroDivisionError) as err: + except Exception as err: raise ValueError(err) def parseEquation(self, eqnText): @@ -253,7 +254,7 @@ # recursive equation ref eval directions -optional, upward, downward = range(3) +EvalDir = enum.IntEnum('EvalDir', 'optional upward downward') class EquationFieldRef: @@ -263,7 +264,7 @@ """ tagPrefix = '' testValue = 1 - evalDirection = optional + evalDirection = EvalDir.optional def __init__(self, fieldName): """Initialize the field references. @@ -285,7 +286,7 @@ zeroValue -- the value to use for blanks """ try: - return (eqnNode.nodeFormat().fieldDict[self.fieldName]. + return (eqnNode.formatRef.fieldDict[self.fieldName]. mathValue(eqnNode, zeroBlanks)) except KeyError: return zeroValue if zeroBlanks else None @@ -296,7 +297,7 @@ Arguments: refNode -- the node containing the referenced field """ - if refNode.formatName == self.eqnNodeTypeName: + if refNode.formatRef.name == self.eqnNodeTypeName: return [refNode] return [] @@ -306,7 +307,7 @@ """ tagPrefix = '*' testValue = 1 - evalDirection = downward + evalDirection = EvalDir.downward def referenceValue(self, eqnNode, zeroBlanks=True, zeroValue=0): """Return the parent field value referenced from a given node. @@ -318,11 +319,11 @@ zeroBlanks -- replace blank fields with zeroValue if True zeroValue -- the value to use for blanks """ - node = eqnNode.parent - if not node: + node = eqnNode.spotByNumber(0).parentSpot.nodeRef + if not node.formatRef: return zeroValue if zeroBlanks else None try: - return (node.nodeFormat().fieldDict[self.fieldName]. + return (node.formatRef.fieldDict[self.fieldName]. mathValue(node, zeroBlanks)) except KeyError: return zeroValue if zeroBlanks else None @@ -334,7 +335,7 @@ refNode -- the node containing the referenced field """ return [node for node in refNode.childList if - node.formatName == self.eqnNodeTypeName] + node.formatRef.name == self.eqnNodeTypeName] class EquationRootRef(EquationFieldRef): @@ -342,7 +343,7 @@ """ tagPrefix = '$' testValue = 1 - evalDirection = downward + evalDirection = EvalDir.downward def referenceValue(self, eqnNode, zeroBlanks=True, zeroValue=0): """Return the root field value referenced from a given node. @@ -354,9 +355,9 @@ zeroBlanks -- replace blank fields with zeroValue if True zeroValue -- the value to use for blanks """ - node = eqnNode.modelRef.root + node = eqnNode.spotByNumber(0).spotChain()[0].nodeRef try: - return (node.nodeFormat().fieldDict[self.fieldName]. + return (node.formatRef.fieldDict[self.fieldName]. mathValue(node, zeroBlanks)) except KeyError: return zeroValue if zeroBlanks else None @@ -367,10 +368,14 @@ Arguments: refNode -- the node containing the referenced field """ - if refNode.parent: + if 1 not in {len(spot.spotChain()) for spot in refNode.spotRefs}: + # not a root node return [] - return [node for node in refNode.selectiveDescendantGen() if - node.formatName == self.eqnNodeTypeName] + refs = [node for node in refNode.descendantGen() if + node.formatRef.name == self.eqnNodeTypeName] + if refs[0] is refNode: + refs = refs[1:] + return refs class EquationChildRef(EquationFieldRef): @@ -378,7 +383,7 @@ """ tagPrefix = '&' testValue = [1] - evalDirection = upward + evalDirection = EvalDir.upward def referenceValue(self, eqnNode, zeroBlanks=True, zeroValue=0): """Return a list with child field values referenced from a given node. @@ -393,7 +398,7 @@ result = [] for node in eqnNode.childList: try: - num = (node.nodeFormat().fieldDict[self.fieldName]. + num = (node.formatRef.fieldDict[self.fieldName]. mathValue(node, zeroBlanks)) if num == None: return None @@ -411,8 +416,8 @@ Arguments: refNode -- the node containing the referenced field """ - node = refNode.parent - if node and node.formatName == self.eqnNodeTypeName: + node = refNode.spotByNumber(0).parentSpot.nodeRef + if node.formatRef and node.formatRef.name == self.eqnNodeTypeName: return [node] return [] @@ -422,7 +427,7 @@ """ tagPrefix = '#' testValue = 1 - evalDirection = optional + evalDirection = EvalDir.optional def referenceValue(self, eqnNode, zeroBlanks=True, zeroValue=0): """Return the child count referenced from the given node. @@ -440,8 +445,8 @@ Arguments: refNode -- the node containing the referenced field """ - node = refNode.parent - if node and node.formatName == self.eqnNodeTypeName: + node = refNode.spotByNumber(0).parentSpot.nodeRef + if node and node.formatRef.name == self.eqnNodeTypeName: return [node] return [] @@ -462,7 +467,7 @@ self.eqnTypeName = eqnTypeName self.eqnField = eqnField self.evalSequence = 0 - self.evalDirection = optional + self.evalDirection = EvalDir.optional def setPriorities(self, visitedFields=None): """Recursively set sequence and direction for evaluation. @@ -480,7 +485,7 @@ for fieldRef in self.eqnField.equation.fieldRefs: if (fieldRef.fieldName in visitedFields and (self.eqnField.name != fieldRef.fieldName or - fieldRef.evalDirection == optional)): + fieldRef.evalDirection == EvalDir.optional)): raise CircularMathError() for eqnRef in self.recursiveRefDict.get(fieldRef.fieldName, []): eqnRef.setPriorities(visitedFields) @@ -488,7 +493,7 @@ self.evalDirection = fieldRef.evalDirection self.evalSequence = eqnRef.evalSequence if (self.evalDirection != eqnRef.evalDirection or - self.evalDirection == optional): + self.evalDirection == EvalDir.optional): self.evalSequence += 1 def __lt__(self, other): diff -Nru treeline-2.0.2/source/miscdialogs.py treeline-3.0.1/source/miscdialogs.py --- treeline-2.0.2/source/miscdialogs.py 2015-02-08 19:58:17.000000000 +0000 +++ treeline-3.0.1/source/miscdialogs.py 2018-09-22 12:25:15.000000000 +0000 @@ -4,7 +4,7 @@ # miscdialogs.py, provides classes for various control dialogs # # TreeLine, an information storage program -# Copyright (C) 2015, Douglas W. Bell +# Copyright (C) 2018, 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 @@ -12,19 +12,35 @@ # but WITTHOUT ANY WARRANTY. See the included LICENSE file for details. #****************************************************************************** +import enum import re import sys -import os.path import operator import collections -from PyQt4 import QtCore, QtGui +import datetime +import platform +import traceback +from PyQt5.QtCore import Qt, pyqtSignal, PYQT_VERSION_STR, qVersion +from PyQt5.QtGui import QFont, QKeySequence, QTextDocument, QTextOption +from PyQt5.QtWidgets import (QAbstractItemView, QApplication, QButtonGroup, + QCheckBox, QComboBox, QDialog, QGridLayout, + QGroupBox, QHBoxLayout, QLabel, QLineEdit, + QListWidget, QListWidgetItem, QMenu, QMessageBox, + QPlainTextEdit, QPushButton, QRadioButton, + QScrollArea, QSpinBox, QTabWidget, QTextEdit, + QTreeWidget, QTreeWidgetItem, QVBoxLayout, + QWidget) +import options import printdialogs import undo -import options import globalref +try: + from __main__ import __version__ +except ImportError: + __version__ = '' -class RadioChoiceDialog(QtGui.QDialog): +class RadioChoiceDialog(QDialog): """Dialog for choosing between a list of text items (radio buttons). Dialog title, group heading, button text and return text can be set. @@ -39,30 +55,34 @@ parent -- the parent window """ super().__init__(parent) - self.setWindowFlags(QtCore.Qt.Dialog | QtCore.Qt.WindowTitleHint | - QtCore.Qt.WindowSystemMenuHint) + self.setWindowFlags(Qt.Dialog | Qt.WindowTitleHint | + Qt.WindowCloseButtonHint) self.setWindowTitle(title) - topLayout = QtGui.QVBoxLayout(self) + topLayout = QVBoxLayout(self) self.setLayout(topLayout) - groupBox = QtGui.QGroupBox(heading) + groupBox = QGroupBox(heading) topLayout.addWidget(groupBox) - groupLayout = QtGui.QVBoxLayout(groupBox) - self.buttonGroup = QtGui.QButtonGroup(self) + groupLayout = QVBoxLayout(groupBox) + self.buttonGroup = QButtonGroup(self) for text, value in choiceList: - button = QtGui.QRadioButton(text) - button.returnValue = value - groupLayout.addWidget(button) - self.buttonGroup.addButton(button) + if value != None: + button = QRadioButton(text) + button.returnValue = value + groupLayout.addWidget(button) + self.buttonGroup.addButton(button) + else: # add heading if no return value + label = QLabel('{0}:'.format(text)) + groupLayout.addWidget(label) self.buttonGroup.buttons()[0].setChecked(True) - ctrlLayout = QtGui.QHBoxLayout() + ctrlLayout = QHBoxLayout() topLayout.addLayout(ctrlLayout) ctrlLayout.addStretch(0) - okButton = QtGui.QPushButton(_('&OK')) + okButton = QPushButton(_('&OK')) ctrlLayout.addWidget(okButton) okButton.clicked.connect(self.accept) - cancelButton = QtGui.QPushButton(_('&Cancel')) + cancelButton = QPushButton(_('&Cancel')) ctrlLayout.addWidget(cancelButton) cancelButton.clicked.connect(self.reject) groupBox.setFocus() @@ -74,10 +94,10 @@ heading -- the groupbox text text - the label text """ - labelBox = QtGui.QGroupBox(heading) + labelBox = QGroupBox(heading) self.layout().insertWidget(0, labelBox) - labelLayout = QtGui.QVBoxLayout(labelBox) - label = QtGui.QLabel(text) + labelLayout = QVBoxLayout(labelBox) + label = QLabel(text) labelLayout.addWidget(label) def selectedButton(self): @@ -86,7 +106,7 @@ return self.buttonGroup.checkedButton().returnValue -class FieldSelectDialog(QtGui.QDialog): +class FieldSelectDialog(QDialog): """Dialog for selecting a sequence from a list of field names. """ def __init__(self, title, heading, fieldList, parent=None): @@ -99,36 +119,36 @@ parent -- the parent window """ super().__init__(parent) - self.setWindowFlags(QtCore.Qt.Dialog | QtCore.Qt.WindowTitleHint | - QtCore.Qt.WindowSystemMenuHint) + self.setWindowFlags(Qt.Dialog | Qt.WindowTitleHint | + Qt.WindowCloseButtonHint) self.setWindowTitle(title) self.selectedFields = [] - topLayout = QtGui.QVBoxLayout(self) + topLayout = QVBoxLayout(self) self.setLayout(topLayout) - groupBox = QtGui.QGroupBox(heading) + groupBox = QGroupBox(heading) topLayout.addWidget(groupBox) - groupLayout = QtGui.QVBoxLayout(groupBox) + groupLayout = QVBoxLayout(groupBox) - self.listView = QtGui.QTreeWidget() + self.listView = QTreeWidget() groupLayout.addWidget(self.listView) self.listView.setHeaderLabels(['#', _('Fields')]) self.listView.setRootIsDecorated(False) self.listView.setSortingEnabled(False) - self.listView.setSelectionMode(QtGui.QAbstractItemView.MultiSelection) + self.listView.setSelectionMode(QAbstractItemView.MultiSelection) for field in fieldList: - QtGui.QTreeWidgetItem(self.listView, ['', field]) + QTreeWidgetItem(self.listView, ['', field]) self.listView.resizeColumnToContents(0) self.listView.resizeColumnToContents(1) self.listView.itemSelectionChanged.connect(self.updateSelectedFields) - ctrlLayout = QtGui.QHBoxLayout() + ctrlLayout = QHBoxLayout() topLayout.addLayout(ctrlLayout) ctrlLayout.addStretch(0) - self.okButton = QtGui.QPushButton(_('&OK')) + self.okButton = QPushButton(_('&OK')) ctrlLayout.addWidget(self.okButton) self.okButton.clicked.connect(self.accept) self.okButton.setEnabled(False) - cancelButton = QtGui.QPushButton(_('&Cancel')) + cancelButton = QPushButton(_('&Cancel')) ctrlLayout.addWidget(cancelButton) cancelButton.clicked.connect(self.reject) self.listView.setFocus() @@ -139,13 +159,13 @@ itemList = [self.listView.topLevelItem(i) for i in range(self.listView.topLevelItemCount())] for item in itemList: - if self.listView.isItemSelected(item): + if item.isSelected(): if item.text(1) not in self.selectedFields: self.selectedFields.append(item.text(1)) elif item.text(1) in self.selectedFields: self.selectedFields.remove(item.text(1)) for item in itemList: - if self.listView.isItemSelected(item): + if item.isSelected(): item.setText(0, str(self.selectedFields.index(item.text(1)) + 1)) else: @@ -153,7 +173,7 @@ self.okButton.setEnabled(len(self.selectedFields)) -class FilePropertiesDialog(QtGui.QDialog): +class FilePropertiesDialog(QDialog): """Dialog for setting file parameters like compression and encryption. """ def __init__(self, localControl, parent=None): @@ -165,45 +185,45 @@ """ super().__init__(parent) self.localControl = localControl - self.setWindowFlags(QtCore.Qt.Dialog | QtCore.Qt.WindowTitleHint | - QtCore.Qt.WindowSystemMenuHint) + self.setWindowFlags(Qt.Dialog | Qt.WindowTitleHint | + Qt.WindowCloseButtonHint) self.setWindowTitle(_('File Properties')) - topLayout = QtGui.QVBoxLayout(self) + topLayout = QVBoxLayout(self) self.setLayout(topLayout) - groupBox = QtGui.QGroupBox(_('File Storage')) + groupBox = QGroupBox(_('File Storage')) topLayout.addWidget(groupBox) - groupLayout = QtGui.QVBoxLayout(groupBox) - self.compressCheck = QtGui.QCheckBox(_('&Use file compression')) + groupLayout = QVBoxLayout(groupBox) + self.compressCheck = QCheckBox(_('&Use file compression')) self.compressCheck.setChecked(localControl.compressed) groupLayout.addWidget(self.compressCheck) - self.encryptCheck = QtGui.QCheckBox(_('Use file &encryption')) + self.encryptCheck = QCheckBox(_('Use file &encryption')) self.encryptCheck.setChecked(localControl.encrypted) groupLayout.addWidget(self.encryptCheck) - groupBox = QtGui.QGroupBox(_('Spell Check')) + groupBox = QGroupBox(_('Spell Check')) topLayout.addWidget(groupBox) - groupLayout = QtGui.QHBoxLayout(groupBox) - label = QtGui.QLabel(_('Language code or\ndictionary (optional)')) + groupLayout = QHBoxLayout(groupBox) + label = QLabel(_('Language code or\ndictionary (optional)')) groupLayout.addWidget(label) - self.spellCheckEdit = QtGui.QLineEdit() + self.spellCheckEdit = QLineEdit() self.spellCheckEdit.setText(self.localControl.spellCheckLang) groupLayout.addWidget(self.spellCheckEdit) - groupBox = QtGui.QGroupBox(_('Math Fields')) + groupBox = QGroupBox(_('Math Fields')) topLayout.addWidget(groupBox) - groupLayout = QtGui.QVBoxLayout(groupBox) - self.zeroBlanks = QtGui.QCheckBox(_('&Treat blank fields as zeros')) - self.zeroBlanks.setChecked(localControl.model.mathZeroBlanks) + groupLayout = QVBoxLayout(groupBox) + self.zeroBlanks = QCheckBox(_('&Treat blank fields as zeros')) + self.zeroBlanks.setChecked(localControl.structure.mathZeroBlanks) groupLayout.addWidget(self.zeroBlanks) - ctrlLayout = QtGui.QHBoxLayout() + ctrlLayout = QHBoxLayout() topLayout.addLayout(ctrlLayout) ctrlLayout.addStretch(0) - okButton = QtGui.QPushButton(_('&OK')) + okButton = QPushButton(_('&OK')) ctrlLayout.addWidget(okButton) okButton.clicked.connect(self.accept) - cancelButton = QtGui.QPushButton(_('&Cancel')) + cancelButton = QPushButton(_('&Cancel')) ctrlLayout.addWidget(cancelButton) cancelButton.clicked.connect(self.reject) @@ -213,24 +233,24 @@ if (self.localControl.compressed != self.compressCheck.isChecked() or self.localControl.encrypted != self.encryptCheck.isChecked() or self.localControl.spellCheckLang != self.spellCheckEdit.text() or - self.localControl.model.mathZeroBlanks != + self.localControl.structure.mathZeroBlanks != self.zeroBlanks.isChecked()): - undo.ParamUndo(self.localControl.model.undoList, + undo.ParamUndo(self.localControl.structure.undoList, [(self.localControl, 'compressed'), (self.localControl, 'encrypted'), (self.localControl, 'spellCheckLang'), - (self.localControl.model, 'mathZeroBlanks')]) + (self.localControl.structure, 'mathZeroBlanks')]) self.localControl.compressed = self.compressCheck.isChecked() self.localControl.encrypted = self.encryptCheck.isChecked() self.localControl.spellCheckLang = self.spellCheckEdit.text() - self.localControl.model.mathZeroBlanks = (self.zeroBlanks. - isChecked()) + self.localControl.structure.mathZeroBlanks = (self.zeroBlanks. + isChecked()) super().accept() else: super().reject() -class PasswordDialog(QtGui.QDialog): +class PasswordDialog(QDialog): """Dialog for password entry and optional re-entry. """ remember = True @@ -239,14 +259,15 @@ Arguments: retype -- require a 2nd password entry if True + fileLabel -- file name to show if given parent -- the parent window """ super().__init__(parent) - self.setWindowFlags(QtCore.Qt.Dialog | QtCore.Qt.WindowTitleHint | - QtCore.Qt.WindowSystemMenuHint) + self.setWindowFlags(Qt.Dialog | Qt.WindowTitleHint | + Qt.WindowCloseButtonHint) self.setWindowTitle(_('Encrypted File Password')) self.password = '' - topLayout = QtGui.QVBoxLayout(self) + topLayout = QVBoxLayout(self) self.setLayout(topLayout) if fileLabel: prompt = _('Type Password for "{0}":').format(fileLabel) @@ -259,19 +280,19 @@ topLayout)) self.editors[0].returnPressed.connect(self.editors[1].setFocus) self.editors[-1].returnPressed.connect(self.accept) - self.rememberCheck = QtGui.QCheckBox(_('Remember password during this ' + self.rememberCheck = QCheckBox(_('Remember password during this ' 'session')) self.rememberCheck.setChecked(PasswordDialog.remember) topLayout.addWidget(self.rememberCheck) - ctrlLayout = QtGui.QHBoxLayout() + ctrlLayout = QHBoxLayout() topLayout.addLayout(ctrlLayout) ctrlLayout.addStretch(0) - okButton = QtGui.QPushButton(_('&OK')) + okButton = QPushButton(_('&OK')) okButton.setAutoDefault(False) ctrlLayout.addWidget(okButton) okButton.clicked.connect(self.accept) - cancelButton = QtGui.QPushButton(_('&Cancel')) + cancelButton = QPushButton(_('&Cancel')) cancelButton.setAutoDefault(False) ctrlLayout.addWidget(cancelButton) cancelButton.clicked.connect(self.reject) @@ -283,10 +304,10 @@ labelText -- the text for the label layout -- the layout to append it """ - label = QtGui.QLabel(labelText) + label = QLabel(labelText) layout.addWidget(label) - editor = QtGui.QLineEdit() - editor.setEchoMode(QtGui.QLineEdit.Password) + editor = QLineEdit() + editor.setEchoMode(QLineEdit.Password) layout.addWidget(editor) return editor @@ -296,10 +317,10 @@ self.password = self.editors[0].text() PasswordDialog.remember = self.rememberCheck.isChecked() if not self.password: - QtGui.QMessageBox.warning(self, 'TreeLine', + QMessageBox.warning(self, 'TreeLine', _('Zero-length passwords are not permitted')) elif len(self.editors) > 1 and self.editors[1].text() != self.password: - QtGui.QMessageBox.warning(self, 'TreeLine', + QMessageBox.warning(self, 'TreeLine', _('Re-typed password did not match')) else: super().accept() @@ -312,23 +333,24 @@ """Helper class to store template paths and info. """ nameExp = re.compile(r'(\d+)([a-zA-Z]+?)_(.+)') - def __init__(self, fullPath): + def __init__(self, pathObj): """Initialize the path. Arguments: - fullPath -- the full path name + pathObj -- the full path object """ - self.fullPath = fullPath + self.pathObj = pathObj self.number = sys.maxsize - name = os.path.splitext(os.path.basename(fullPath))[0] - match = TemplateFileItem.nameExp.match(name) - if match: - num, self.langCode, self.name = match.groups() - self.number = int(num) - else: - self.langCode = '' - self.name = name - self.displayName = self.name.replace('_', ' ') + self.name = '' + self.displayName = '' + self.langCode = '' + if pathObj: + self.name = pathObj.stem + match = TemplateFileItem.nameExp.match(self.name) + if match: + num, self.langCode, self.name = match.groups() + self.number = int(num) + self.displayName = self.name.replace('_', ' ') def sortKey(self): """Return a key for sorting the items by number then name. @@ -350,7 +372,7 @@ return hash((self.langCode, self.displayName)) -class TemplateFileDialog(QtGui.QDialog): +class TemplateFileDialog(QDialog): """Dialog for listing available template files. """ def __init__(self, title, heading, searchPaths, addDefault=True, @@ -360,37 +382,37 @@ Arguments: title -- the window title heading -- the groupbox text - searchPaths -- list of directories with available templates + searchPaths -- list of path objects with available templates addDefault -- if True, add a default (no path) entry parent -- the parent window """ super().__init__(parent) - self.setWindowFlags(QtCore.Qt.Dialog | QtCore.Qt.WindowTitleHint | - QtCore.Qt.WindowSystemMenuHint) + self.setWindowFlags(Qt.Dialog | Qt.WindowTitleHint | + Qt.WindowCloseButtonHint) self.setWindowTitle(title) self.templateItems = [] if addDefault: - item = TemplateFileItem('') + item = TemplateFileItem(None) item.number = -1 item.displayName = _('Default - Single Line Text') self.templateItems.append(item) - topLayout = QtGui.QVBoxLayout(self) + topLayout = QVBoxLayout(self) self.setLayout(topLayout) - groupBox = QtGui.QGroupBox(heading) + groupBox = QGroupBox(heading) topLayout.addWidget(groupBox) - boxLayout = QtGui.QVBoxLayout(groupBox) - self.listBox = QtGui.QListWidget() + boxLayout = QVBoxLayout(groupBox) + self.listBox = QListWidget() boxLayout.addWidget(self.listBox) self.listBox.itemDoubleClicked.connect(self.accept) - ctrlLayout = QtGui.QHBoxLayout() + ctrlLayout = QHBoxLayout() topLayout.addLayout(ctrlLayout) ctrlLayout.addStretch(0) - self.okButton = QtGui.QPushButton(_('&OK')) + self.okButton = QPushButton(_('&OK')) ctrlLayout.addWidget(self.okButton) self.okButton.clicked.connect(self.accept) - cancelButton = QtGui.QPushButton(_('&Cancel')) + cancelButton = QPushButton(_('&Cancel')) ctrlLayout.addWidget(cancelButton) cancelButton.clicked.connect(self.reject) @@ -401,15 +423,14 @@ """Read template file paths into the templateItems list. Arguments: - searchPaths -- list of directories with available templates + searchPaths -- list of path objects with available templates """ templateItems = set() for path in searchPaths: - for name in os.listdir(path): - if name.endswith('.trl'): - templateItem = TemplateFileItem(os.path.join(path, name)) - if templateItem not in templateItems: - templateItems.add(templateItem) + for templatePath in path.glob('*.trln'): + templateItem = TemplateFileItem(templatePath) + if templateItem not in templateItems: + templateItems.add(templateItem) availLang = set([item.langCode for item in templateItems]) if len(availLang) > 1: lang = 'en' @@ -430,10 +451,10 @@ self.okButton.setEnabled(self.listBox.count() > 0) def selectedPath(self): - """Return the path from the selected item. + """Return the path object from the selected item. """ item = self.templateItems[self.listBox.currentRow()] - return item.fullPath + return item.pathObj def selectedName(self): """Return the displayed name with underscores from the selected item. @@ -442,90 +463,137 @@ return item.name -class FindFilterDialog(QtGui.QDialog): +class ExceptionDialog(QDialog): + """Dialog for showing debug info from an unhandled exception. + """ + def __init__(self, excType, value, tb, parent=None): + """Initialize the exception dialog. + + Arguments: + excType -- execption class + value -- execption error text + tb -- the traceback object + """ + super().__init__(parent) + self.setWindowFlags(Qt.Window | Qt.WindowStaysOnTopHint) + self.setWindowTitle(_('TreeLine - Serious Error')) + + topLayout = QVBoxLayout(self) + self.setLayout(topLayout) + label = QLabel(_('A serious error has occurred. TreeLine could be ' + 'in an unstable state.\n' + 'Recommend saving any file changes under another ' + 'filename and restart TreeLine.\n\n' + 'The debugging info shown below can be copied ' + 'and emailed to doug101@bellz.org along with\n' + 'an explanation of the circumstances.\n')) + topLayout.addWidget(label) + textBox = QTextEdit() + textBox.setReadOnly(True) + pyVersion = '.'.join([repr(num) for num in sys.version_info[:3]]) + textLines = ['When: {0}\n'.format(datetime.datetime.now(). + isoformat(' ')), + 'TreeLine Version: {0}\n'.format(__version__), + 'Python Version: {0}\n'.format(pyVersion), + 'Qt Version: {0}\n'.format(qVersion()), + 'PyQt Version: {0}\n'.format(PYQT_VERSION_STR), + 'OS: {0}\n'.format(platform.platform()), '\n'] + textLines.extend(traceback.format_exception(excType, value, tb)) + textBox.setPlainText(''.join(textLines)) + topLayout.addWidget(textBox) + + ctrlLayout = QHBoxLayout() + topLayout.addLayout(ctrlLayout) + ctrlLayout.addStretch(0) + closeButton = QPushButton(_('&Close')) + ctrlLayout.addWidget(closeButton) + closeButton.clicked.connect(self.close) + + +FindScope = enum.IntEnum('FindScope', 'fullData titlesOnly') +FindType = enum.IntEnum('FindType', 'keyWords fullWords fullPhrase regExp') + +class FindFilterDialog(QDialog): """Dialog for searching for text within tree titles and data. """ - dialogShown = QtCore.pyqtSignal(bool) - findDialog, filterDialog = range(2) - fullData, titlesOnly = range(2) - keyWords, fullWords, fullPhrase, regExp = range(4) - def __init__(self, dialogType, parent=None): + dialogShown = pyqtSignal(bool) + def __init__(self, isFilterDialog=False, parent=None): """Initialize the find dialog. Arguments: - dialogType -- either findDialog or filterDialog (button changes) + isFilterDialog -- True for filter dialog, False for find dialog parent -- the parent window """ super().__init__(parent) - self.dialogType = dialogType - self.setAttribute(QtCore.Qt.WA_QuitOnClose, False) - self.setWindowFlags(QtCore.Qt.Window | QtCore.Qt.WindowStaysOnTopHint) + self.isFilterDialog = isFilterDialog + self.setAttribute(Qt.WA_QuitOnClose, False) + self.setWindowFlags(Qt.Window | Qt.WindowStaysOnTopHint) - topLayout = QtGui.QVBoxLayout(self) + topLayout = QVBoxLayout(self) self.setLayout(topLayout) - textBox = QtGui.QGroupBox(_('&Search Text')) + textBox = QGroupBox(_('&Search Text')) topLayout.addWidget(textBox) - textLayout = QtGui.QVBoxLayout(textBox) - self.textEntry = QtGui.QLineEdit() + textLayout = QVBoxLayout(textBox) + self.textEntry = QLineEdit() textLayout.addWidget(self.textEntry) self.textEntry.textEdited.connect(self.updateAvail) - horizLayout = QtGui.QHBoxLayout() + horizLayout = QHBoxLayout() topLayout.addLayout(horizLayout) - whatBox = QtGui.QGroupBox(_('What to Search')) + whatBox = QGroupBox(_('What to Search')) horizLayout.addWidget(whatBox) - whatLayout = QtGui.QVBoxLayout(whatBox) - self.whatButtons = QtGui.QButtonGroup(self) - button = QtGui.QRadioButton(_('Full &data')) - self.whatButtons.addButton(button, FindFilterDialog.fullData) + whatLayout = QVBoxLayout(whatBox) + self.whatButtons = QButtonGroup(self) + button = QRadioButton(_('Full &data')) + self.whatButtons.addButton(button, FindScope.fullData) whatLayout.addWidget(button) - button = QtGui.QRadioButton(_('&Titles only')) - self.whatButtons.addButton(button, FindFilterDialog.titlesOnly) + button = QRadioButton(_('&Titles only')) + self.whatButtons.addButton(button, FindScope.titlesOnly) whatLayout.addWidget(button) - self.whatButtons.button(FindFilterDialog.fullData).setChecked(True) + self.whatButtons.button(FindScope.fullData).setChecked(True) - howBox = QtGui.QGroupBox(_('How to Search')) + howBox = QGroupBox(_('How to Search')) horizLayout.addWidget(howBox) - howLayout = QtGui.QVBoxLayout(howBox) - self.howButtons = QtGui.QButtonGroup(self) - button = QtGui.QRadioButton(_('&Key words')) - self.howButtons.addButton(button, FindFilterDialog.keyWords) + howLayout = QVBoxLayout(howBox) + self.howButtons = QButtonGroup(self) + button = QRadioButton(_('&Key words')) + self.howButtons.addButton(button, FindType.keyWords) howLayout.addWidget(button) - button = QtGui.QRadioButton(_('Key full &words')) - self.howButtons.addButton(button, FindFilterDialog.fullWords) + button = QRadioButton(_('Key full &words')) + self.howButtons.addButton(button, FindType.fullWords) howLayout.addWidget(button) - button = QtGui.QRadioButton(_('F&ull phrase')) - self.howButtons.addButton(button, FindFilterDialog.fullPhrase) + button = QRadioButton(_('F&ull phrase')) + self.howButtons.addButton(button, FindType.fullPhrase) howLayout.addWidget(button) - button = QtGui.QRadioButton(_('&Regular expression')) - self.howButtons.addButton(button, FindFilterDialog.regExp) + button = QRadioButton(_('&Regular expression')) + self.howButtons.addButton(button, FindType.regExp) howLayout.addWidget(button) - self.howButtons.button(FindFilterDialog.keyWords).setChecked(True) + self.howButtons.button(FindType.keyWords).setChecked(True) - ctrlLayout = QtGui.QHBoxLayout() + ctrlLayout = QHBoxLayout() topLayout.addLayout(ctrlLayout) - if self.dialogType == FindFilterDialog.findDialog: + if not self.isFilterDialog: self.setWindowTitle(_('Find')) - self.previousButton = QtGui.QPushButton(_('Find &Previous')) + self.previousButton = QPushButton(_('Find &Previous')) ctrlLayout.addWidget(self.previousButton) self.previousButton.clicked.connect(self.findPrevious) - self.nextButton = QtGui.QPushButton(_('Find &Next')) + self.nextButton = QPushButton(_('Find &Next')) self.nextButton.setDefault(True) ctrlLayout.addWidget(self.nextButton) self.nextButton.clicked.connect(self.findNext) - self.resultLabel = QtGui.QLabel() + self.resultLabel = QLabel() topLayout.addWidget(self.resultLabel) else: self.setWindowTitle(_('Filter')) - self.filterButton = QtGui.QPushButton(_('&Filter')) + self.filterButton = QPushButton(_('&Filter')) ctrlLayout.addWidget(self.filterButton) self.filterButton.clicked.connect(self.startFilter) - self.endFilterButton = QtGui.QPushButton(_('&End Filter')) + self.endFilterButton = QPushButton(_('&End Filter')) ctrlLayout.addWidget(self.endFilterButton) self.endFilterButton.clicked.connect(self.endFilter) - closeButton = QtGui.QPushButton(_('&Close')) + closeButton = QPushButton(_('&Close')) ctrlLayout.addWidget(closeButton) closeButton.clicked.connect(self.close) self.updateAvail('') @@ -544,19 +612,19 @@ fileChange -- True if window changed while dialog open """ hasEntry = len(self.textEntry.text().strip()) > 0 - if self.dialogType == FindFilterDialog.findDialog: + if not self.isFilterDialog: self.previousButton.setEnabled(hasEntry) self.nextButton.setEnabled(hasEntry) self.resultLabel.setText('') else: window = globalref.mainControl.activeControl.activeWindow - if fileChange and window.isFiltering(): + if fileChange and window.treeFilterView: filterView = window.treeFilterView self.textEntry.setText(filterView.filterStr) self.whatButtons.button(filterView.filterWhat).setChecked(True) self.howButtons.button(filterView.filterHow).setChecked(True) self.filterButton.setEnabled(hasEntry) - self.endFilterButton.setEnabled(window.isFiltering()) + self.endFilterButton.setEnabled(window.treeFilterView != None) def find(self, forward=True): """Find another match in the indicated direction. @@ -564,25 +632,25 @@ Arguments: forward -- next if True, previous if False """ + self.resultLabel.setText('') text = self.textEntry.text() - titlesOnly = self.whatButtons.checkedId() == (FindFilterDialog. - titlesOnly) + titlesOnly = self.whatButtons.checkedId() == (FindScope.titlesOnly) control = globalref.mainControl.activeControl - if self.howButtons.checkedId() == FindFilterDialog.regExp: + if self.howButtons.checkedId() == FindType.regExp: try: regExp = re.compile(text) except re.error: - QtGui.QMessageBox.warning(self, 'TreeLine', - _('Error - invalid regular expression')) + QMessageBox.warning(self, 'TreeLine', + _('Error - invalid regular expression')) return result = control.findNodesByRegExp([regExp], titlesOnly, forward) - elif self.howButtons.checkedId() == FindFilterDialog.fullWords: + elif self.howButtons.checkedId() == FindType.fullWords: regExpList = [] for word in text.lower().split(): regExpList.append(re.compile(r'(?i)\b{}\b'. format(re.escape(word)))) result = control.findNodesByRegExp(regExpList, titlesOnly, forward) - elif self.howButtons.checkedId() == FindFilterDialog.keyWords: + elif self.howButtons.checkedId() == FindType.keyWords: wordList = text.lower().split() result = control.findNodesByWords(wordList, titlesOnly, forward) else: # full phrase @@ -605,29 +673,26 @@ def startFilter(self): """Start filtering nodes. """ - if self.howButtons.checkedId() == FindFilterDialog.regExp: + if self.howButtons.checkedId() == FindType.regExp: try: re.compile(self.textEntry.text()) except re.error: - QtGui.QMessageBox.warning(self, 'TreeLine', + QMessageBox.warning(self, 'TreeLine', _('Error - invalid regular expression')) return - window = globalref.mainControl.activeControl.activeWindow - filterView = window.treeFilterView + filterView = (globalref.mainControl.activeControl.activeWindow. + filterView()) filterView.filterWhat = self.whatButtons.checkedId() filterView.filterHow = self.howButtons.checkedId() filterView.filterStr = self.textEntry.text() filterView.updateContents() - window.treeStack.setCurrentWidget(filterView) self.updateAvail() def endFilter(self): """Stop filtering nodes. """ - window = globalref.mainControl.activeControl.activeWindow - window.treeStack.setCurrentWidget(window.treeView) + globalref.mainControl.activeControl.activeWindow.removeFilterView() self.updateAvail() - globalref.mainControl.currentStatusBar().clearMessage() def closeEvent(self, event): """Signal that the dialog is closing. @@ -638,11 +703,12 @@ self.dialogShown.emit(False) -class FindReplaceDialog(QtGui.QDialog): +FindReplaceType = enum.IntEnum('FindReplaceType', 'anyMatch fullWord regExp') + +class FindReplaceDialog(QDialog): """Dialog for finding and replacing text in the node data. """ - dialogShown = QtCore.pyqtSignal(bool) - anyMatch, fullWord, regExp = range(3) + dialogShown = pyqtSignal(bool) def __init__(self, parent=None): """Initialize the find and replace dialog. @@ -650,78 +716,77 @@ parent -- the parent window """ super().__init__(parent) - self.setAttribute(QtCore.Qt.WA_QuitOnClose, False) - self.setWindowFlags(QtCore.Qt.Window | QtCore.Qt.WindowStaysOnTopHint) + self.setAttribute(Qt.WA_QuitOnClose, False) + self.setWindowFlags(Qt.Window | Qt.WindowStaysOnTopHint) self.setWindowTitle(_('Find and Replace')) - self.matchedNode = None - topLayout = QtGui.QGridLayout(self) + self.matchedSpot = None + topLayout = QGridLayout(self) self.setLayout(topLayout) - textBox = QtGui.QGroupBox(_('&Search Text')) + textBox = QGroupBox(_('&Search Text')) topLayout.addWidget(textBox, 0, 0) - textLayout = QtGui.QVBoxLayout(textBox) - self.textEntry = QtGui.QLineEdit() + textLayout = QVBoxLayout(textBox) + self.textEntry = QLineEdit() textLayout.addWidget(self.textEntry) - self.textEntry.textEdited.connect(self.updateAvail) self.textEntry.textEdited.connect(self.clearMatch) - replaceBox = QtGui.QGroupBox(_('Replacement &Text')) + replaceBox = QGroupBox(_('Replacement &Text')) topLayout.addWidget(replaceBox, 0, 1) - replaceLayout = QtGui.QVBoxLayout(replaceBox) - self.replaceEntry = QtGui.QLineEdit() + replaceLayout = QVBoxLayout(replaceBox) + self.replaceEntry = QLineEdit() replaceLayout.addWidget(self.replaceEntry) - howBox = QtGui.QGroupBox(_('How to Search')) + howBox = QGroupBox(_('How to Search')) topLayout.addWidget(howBox, 1, 0, 2, 1) - howLayout = QtGui.QVBoxLayout(howBox) - self.howButtons = QtGui.QButtonGroup(self) - button = QtGui.QRadioButton(_('Any &match')) - self.howButtons.addButton(button, FindReplaceDialog.anyMatch) + howLayout = QVBoxLayout(howBox) + self.howButtons = QButtonGroup(self) + button = QRadioButton(_('Any &match')) + self.howButtons.addButton(button, FindReplaceType.anyMatch) howLayout.addWidget(button) - button = QtGui.QRadioButton(_('Full &words')) - self.howButtons.addButton(button, FindReplaceDialog.fullWord) + button = QRadioButton(_('Full &words')) + self.howButtons.addButton(button, FindReplaceType.fullWord) howLayout.addWidget(button) - button = QtGui.QRadioButton(_('Re&gular expression')) - self.howButtons.addButton(button, FindReplaceDialog.regExp) + button = QRadioButton(_('Re&gular expression')) + self.howButtons.addButton(button, FindReplaceType.regExp) howLayout.addWidget(button) - self.howButtons.button(FindReplaceDialog.anyMatch).setChecked(True) + self.howButtons.button(FindReplaceType.anyMatch).setChecked(True) self.howButtons.buttonClicked.connect(self.clearMatch) - typeBox = QtGui.QGroupBox(_('&Node Type')) + typeBox = QGroupBox(_('&Node Type')) topLayout.addWidget(typeBox, 1, 1) - typeLayout = QtGui.QVBoxLayout(typeBox) - self.typeCombo = QtGui.QComboBox() + typeLayout = QVBoxLayout(typeBox) + self.typeCombo = QComboBox() typeLayout.addWidget(self.typeCombo) self.typeCombo.currentIndexChanged.connect(self.loadFieldNames) - fieldBox = QtGui.QGroupBox(_('N&ode Fields')) + fieldBox = QGroupBox(_('N&ode Fields')) topLayout.addWidget(fieldBox, 2, 1) - fieldLayout = QtGui.QVBoxLayout(fieldBox) - self.fieldCombo = QtGui.QComboBox() + fieldLayout = QVBoxLayout(fieldBox) + self.fieldCombo = QComboBox() fieldLayout.addWidget(self.fieldCombo) self.fieldCombo.currentIndexChanged.connect(self.clearMatch) - ctrlLayout = QtGui.QHBoxLayout() + ctrlLayout = QHBoxLayout() topLayout.addLayout(ctrlLayout, 3, 0, 1, 2) - self.previousButton = QtGui.QPushButton(_('Find &Previous')) + self.previousButton = QPushButton(_('Find &Previous')) ctrlLayout.addWidget(self.previousButton) self.previousButton.clicked.connect(self.findPrevious) - self.nextButton = QtGui.QPushButton(_('&Find Next')) + self.nextButton = QPushButton(_('&Find Next')) self.nextButton.setDefault(True) ctrlLayout.addWidget(self.nextButton) self.nextButton.clicked.connect(self.findNext) - self.replaceButton = QtGui.QPushButton(_('&Replace')) + self.replaceButton = QPushButton(_('&Replace')) ctrlLayout.addWidget(self.replaceButton) self.replaceButton.clicked.connect(self.replace) - self.replaceAllButton = QtGui.QPushButton(_('Replace &All')) + self.replaceAllButton = QPushButton(_('Replace &All')) ctrlLayout.addWidget(self.replaceAllButton) self.replaceAllButton.clicked.connect(self.replaceAll) - closeButton = QtGui.QPushButton(_('&Close')) + closeButton = QPushButton(_('&Close')) ctrlLayout.addWidget(closeButton) closeButton.clicked.connect(self.close) - self.resultLabel = QtGui.QLabel() + self.resultLabel = QLabel() topLayout.addWidget(self.resultLabel, 4, 0, 1, 2) self.loadTypeNames() self.updateAvail() @@ -732,9 +797,9 @@ hasEntry = len(self.textEntry.text().strip()) > 0 self.previousButton.setEnabled(hasEntry) self.nextButton.setEnabled(hasEntry) - match = bool(self.matchedNode and self.matchedNode is + match = bool(self.matchedSpot and self.matchedSpot is globalref.mainControl.activeControl. - currentSelectionModel().currentNode()) + currentSelectionModel().currentSpot()) self.replaceButton.setEnabled(match) self.replaceAllButton.setEnabled(match) self.resultLabel.setText('') @@ -742,14 +807,15 @@ def clearMatch(self): """Remove reference to matched node if search criteria changes. """ - self.matchedNode = None + self.matchedSpot = None + globalref.mainControl.activeControl.findReplaceSpotRef = (None, 0) self.updateAvail() def loadTypeNames(self): """Load format type names into combo box. """ origTypeName = self.typeCombo.currentText() - nodeFormats = globalref.mainControl.activeControl.model.formats + nodeFormats = globalref.mainControl.activeControl.structure.treeFormats self.typeCombo.blockSignals(True) self.typeCombo.clear() typeNames = nodeFormats.typeNames() @@ -764,7 +830,7 @@ """Load field names into combo box. """ origFieldName = self.fieldCombo.currentText() - nodeFormats = globalref.mainControl.activeControl.model.formats + nodeFormats = globalref.mainControl.activeControl.structure.treeFormats typeName = self.typeCombo.currentText() fieldNames = [] if typeName.startswith('['): @@ -779,7 +845,7 @@ origPos = self.fieldCombo.findText(origFieldName) if origPos >= 0: self.fieldCombo.setCurrentIndex(origPos) - self.matchedNode = None + self.matchedSpot = None self.updateAvail() def findParameters(self): @@ -790,9 +856,9 @@ text = self.textEntry.text() searchText = '' regExpObj = None - if self.howButtons.checkedId() == FindReplaceDialog.anyMatch: + if self.howButtons.checkedId() == FindReplaceType.anyMatch: searchText = text.lower().strip() - elif self.howButtons.checkedId() == FindReplaceDialog.fullWord: + elif self.howButtons.checkedId() == FindReplaceType.fullWord: regExpObj = re.compile(r'(?i)\b{}\b'.format(re.escape(text))) else: regExpObj = re.compile(text) @@ -810,18 +876,18 @@ Arguments: forward -- next if True, previous if False """ - self.matchedNode = None + self.matchedSpot = None try: searchText, regExpObj, typeName, fieldName = self.findParameters() except re.error: - QtGui.QMessageBox.warning(self, 'TreeLine', + QMessageBox.warning(self, 'TreeLine', _('Error - invalid regular expression')) self.updateAvail() return control = globalref.mainControl.activeControl if control.findNodesForReplace(searchText, regExpObj, typeName, fieldName, forward): - self.matchedNode = control.currentSelectionModel().currentNode() + self.matchedSpot = control.currentSelectionModel().currentSpot() self.updateAvail() else: self.updateAvail() @@ -848,9 +914,9 @@ fieldName, replaceText): self.find() else: - QtGui.QMessageBox.warning(self, 'TreeLine', + QMessageBox.warning(self, 'TreeLine', _('Error - replacement failed')) - self.matchedNode = None + self.matchedSpot = None self.updateAvail() def replaceAll(self): @@ -861,7 +927,7 @@ control = globalref.mainControl.activeControl qty = control.replaceAll(searchText, regExpObj, typeName, fieldName, replaceText) - self.matchedNode = None + self.matchedSpot = None self.updateAvail() self.resultLabel.setText(_('Replaced {0} matches').format(qty)) @@ -874,13 +940,15 @@ self.dialogShown.emit(False) -class SortDialog(QtGui.QDialog): +SortWhat = enum.IntEnum('SortWhat', + 'fullTree selectBranch selectChildren selectSiblings') +SortMethod = enum.IntEnum('SortMethod', 'fieldSort titleSort') +SortDirection = enum.IntEnum('SortDirection', 'forward reverse') + +class SortDialog(QDialog): """Dialog for defining sort operations. """ - dialogShown = QtCore.pyqtSignal(bool) - fullTree, selectBranch, selectChildren, selectSiblings = range(4) - fieldSort, titleSort = range(2) - forward, reverse = range(2) + dialogShown = pyqtSignal(bool) def __init__(self, parent=None): """Initialize the sort dialog. @@ -888,68 +956,68 @@ parent -- the parent window """ super().__init__(parent) - self.setAttribute(QtCore.Qt.WA_QuitOnClose, False) - self.setWindowFlags(QtCore.Qt.Window | QtCore.Qt.WindowStaysOnTopHint) + self.setAttribute(Qt.WA_QuitOnClose, False) + self.setWindowFlags(Qt.Window | Qt.WindowStaysOnTopHint) self.setWindowTitle(_('Sort Nodes')) - topLayout = QtGui.QVBoxLayout(self) + topLayout = QVBoxLayout(self) self.setLayout(topLayout) - horizLayout = QtGui.QHBoxLayout() + horizLayout = QHBoxLayout() topLayout.addLayout(horizLayout) - whatBox = QtGui.QGroupBox(_('What to Sort')) + whatBox = QGroupBox(_('What to Sort')) horizLayout.addWidget(whatBox) - whatLayout = QtGui.QVBoxLayout(whatBox) - self.whatButtons = QtGui.QButtonGroup(self) - button = QtGui.QRadioButton(_('&Entire tree')) - self.whatButtons.addButton(button, SortDialog.fullTree) + whatLayout = QVBoxLayout(whatBox) + self.whatButtons = QButtonGroup(self) + button = QRadioButton(_('&Entire tree')) + self.whatButtons.addButton(button, SortWhat.fullTree) whatLayout.addWidget(button) - button = QtGui.QRadioButton(_('Selected &branches')) - self.whatButtons.addButton(button, SortDialog.selectBranch) + button = QRadioButton(_('Selected &branches')) + self.whatButtons.addButton(button, SortWhat.selectBranch) whatLayout.addWidget(button) - button = QtGui.QRadioButton(_('Selection\'s childre&n')) - self.whatButtons.addButton(button, SortDialog.selectChildren) + button = QRadioButton(_('Selection\'s childre&n')) + self.whatButtons.addButton(button, SortWhat.selectChildren) whatLayout.addWidget(button) - button = QtGui.QRadioButton(_('Selection\'s &siblings')) - self.whatButtons.addButton(button, SortDialog.selectSiblings) + button = QRadioButton(_('Selection\'s &siblings')) + self.whatButtons.addButton(button, SortWhat.selectSiblings) whatLayout.addWidget(button) - self.whatButtons.button(SortDialog.fullTree).setChecked(True) + self.whatButtons.button(SortWhat.fullTree).setChecked(True) - vertLayout = QtGui.QVBoxLayout() + vertLayout = QVBoxLayout() horizLayout.addLayout(vertLayout) - methodBox = QtGui.QGroupBox(_('Sort Method')) + methodBox = QGroupBox(_('Sort Method')) vertLayout.addWidget(methodBox) - methodLayout = QtGui.QVBoxLayout(methodBox) - self.methodButtons = QtGui.QButtonGroup(self) - button = QtGui.QRadioButton(_('&Predefined Key Fields')) - self.methodButtons.addButton(button, SortDialog.fieldSort) + methodLayout = QVBoxLayout(methodBox) + self.methodButtons = QButtonGroup(self) + button = QRadioButton(_('&Predefined Key Fields')) + self.methodButtons.addButton(button, SortMethod.fieldSort) methodLayout.addWidget(button) - button = QtGui.QRadioButton(_('Node &Titles')) - self.methodButtons.addButton(button, SortDialog.titleSort) + button = QRadioButton(_('Node &Titles')) + self.methodButtons.addButton(button, SortMethod.titleSort) methodLayout.addWidget(button) - self.methodButtons.button(SortDialog.fieldSort).setChecked(True) + self.methodButtons.button(SortMethod.fieldSort).setChecked(True) - directionBox = QtGui.QGroupBox(_('Sort Direction')) + directionBox = QGroupBox(_('Sort Direction')) vertLayout.addWidget(directionBox) - directionLayout = QtGui.QVBoxLayout(directionBox) - self.directionButtons = QtGui.QButtonGroup(self) - button = QtGui.QRadioButton(_('&Forward')) - self.directionButtons.addButton(button, SortDialog.forward) + directionLayout = QVBoxLayout(directionBox) + self.directionButtons = QButtonGroup(self) + button = QRadioButton(_('&Forward')) + self.directionButtons.addButton(button, SortDirection.forward) directionLayout.addWidget(button) - button = QtGui.QRadioButton(_('&Reverse')) - self.directionButtons.addButton(button, SortDialog.reverse) + button = QRadioButton(_('&Reverse')) + self.directionButtons.addButton(button, SortDirection.reverse) directionLayout.addWidget(button) - self.directionButtons.button(SortDialog.forward).setChecked(True) + self.directionButtons.button(SortDirection.forward).setChecked(True) - ctrlLayout = QtGui.QHBoxLayout() + ctrlLayout = QHBoxLayout() topLayout.addLayout(ctrlLayout) ctrlLayout.addStretch() - okButton = QtGui.QPushButton(_('&OK')) + okButton = QPushButton(_('&OK')) ctrlLayout.addWidget(okButton) okButton.clicked.connect(self.sortAndClose) - applyButton = QtGui.QPushButton(_('&Apply')) + applyButton = QPushButton(_('&Apply')) ctrlLayout.addWidget(applyButton) applyButton.clicked.connect(self.sortNodes) - closeButton = QtGui.QPushButton(_('&Close')) + closeButton = QPushButton(_('&Close')) ctrlLayout.addWidget(closeButton) closeButton.clicked.connect(self.close) self.updateCommandsAvail() @@ -957,52 +1025,52 @@ def updateCommandsAvail(self): """Set what to sort options available based on tree selections. """ - selNodes = globalref.mainControl.activeControl.currentSelectionModel() + selModel = globalref.mainControl.activeControl.currentSelectionModel() hasChild = False hasSibling = False - for node in selNodes.selectedNodes(): - if node.childList: + for spot in selModel.selectedSpots(): + if spot.nodeRef.childList: hasChild = True - if node.parent and len(node.parent.childList) > 1: + if spot.parentSpot and len(spot.parentSpot.nodeRef.childList) > 1: hasSibling = True - self.whatButtons.button(SortDialog.selectBranch).setEnabled(hasChild) - self.whatButtons.button(SortDialog.selectChildren).setEnabled(hasChild) - self.whatButtons.button(SortDialog. - selectSiblings).setEnabled(hasSibling) + self.whatButtons.button(SortWhat.selectBranch).setEnabled(hasChild) + self.whatButtons.button(SortWhat.selectChildren).setEnabled(hasChild) + self.whatButtons.button(SortWhat.selectSiblings).setEnabled(hasSibling) if not self.whatButtons.checkedButton().isEnabled(): - self.whatButtons.button(SortDialog.fullTree).setChecked(True) + self.whatButtons.button(SortWhat.fullTree).setChecked(True) def sortNodes(self): """Perform the sorting operation. """ - QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) + QApplication.setOverrideCursor(Qt.WaitCursor) control = globalref.mainControl.activeControl - selNodes = control.currentSelectionModel().selectedNodes() - if self.whatButtons.checkedId() == SortDialog.fullTree: - selNodes = [control.model.root] - elif self.whatButtons.checkedId() == SortDialog.selectSiblings: - selNodes = [node.parent for node in selNodes if node.parent] - if self.whatButtons.checkedId() in (SortDialog.fullTree, - SortDialog.selectBranch): - rootNodes = selNodes[:] - selNodes = [] - for root in rootNodes: - for node in root.descendantGen(): - if node.childList: - selNodes.append(node) - undo.ChildListUndo(control.model.undoList, selNodes) - forward = self.directionButtons.checkedId() == SortDialog.forward - if self.methodButtons.checkedId() == SortDialog.fieldSort: - for node in selNodes: - node.sortChildrenByField(False, forward) + selSpots = control.currentSelectionModel().selectedSpots() + if self.whatButtons.checkedId() == SortWhat.fullTree: + selSpots = [control.structure.spotByNumber(0)] + elif self.whatButtons.checkedId() == SortWhat.selectSiblings: + selSpots = [spot.parentSpot for spot in selSpots] + if self.whatButtons.checkedId() in (SortWhat.fullTree, + SortWhat.selectBranch): + rootSpots = selSpots[:] + selSpots = [] + for root in rootSpots: + for spot in root.spotDescendantGen(): + if spot.nodeRef.childList: + selSpots.append(spot) + undo.ChildListUndo(control.structure.undoList, + [spot.nodeRef for spot in selSpots]) + forward = self.directionButtons.checkedId() == SortDirection.forward + if self.methodButtons.checkedId() == SortMethod.fieldSort: + for spot in selSpots: + spot.nodeRef.sortChildrenByField(False, forward) # reset temporary sort field storage - for nodeFormat in control.model.formats.values(): + for nodeFormat in control.structure.treeFormats.values(): nodeFormat.sortFields = [] else: - for node in selNodes: - node.sortChildrenByTitle(False, forward) + for spot in selSpots: + spot.nodeRef.sortChildrenByTitle(False, forward) control.updateAll() - QtGui.QApplication.restoreOverrideCursor() + QApplication.restoreOverrideCursor() def sortAndClose(self): """Perform the sorting operation and close the dialog. @@ -1019,12 +1087,15 @@ self.dialogShown.emit(False) -class NumberingDialog(QtGui.QDialog): +NumberingScope = enum.IntEnum('NumberingScope', + 'fullTree selectBranch selectChildren') +NumberingNoField = enum.IntEnum('NumberingNoField', + 'ignoreNoField restartAfterNoField reserveNoField') + +class NumberingDialog(QDialog): """Dialog for updating node nuumbering fields. """ - dialogShown = QtCore.pyqtSignal(bool) - fullTree, selectBranch, selectChildren = range(3) - ignoreNoField, restartAfterNoField, reserveNoField = range(3) + dialogShown = pyqtSignal(bool) def __init__(self, parent=None): """Initialize the numbering dialog. @@ -1032,62 +1103,62 @@ parent -- the parent window """ super().__init__(parent) - self.setAttribute(QtCore.Qt.WA_QuitOnClose, False) - self.setWindowFlags(QtCore.Qt.Window | QtCore.Qt.WindowStaysOnTopHint) + self.setAttribute(Qt.WA_QuitOnClose, False) + self.setWindowFlags(Qt.Window | Qt.WindowStaysOnTopHint) self.setWindowTitle(_('Update Node Numbering')) - topLayout = QtGui.QVBoxLayout(self) + topLayout = QVBoxLayout(self) self.setLayout(topLayout) - whatBox = QtGui.QGroupBox(_('What to Update')) + whatBox = QGroupBox(_('What to Update')) topLayout.addWidget(whatBox) - whatLayout = QtGui.QVBoxLayout(whatBox) - self.whatButtons = QtGui.QButtonGroup(self) - button = QtGui.QRadioButton(_('&Entire tree')) - self.whatButtons.addButton(button, NumberingDialog.fullTree) + whatLayout = QVBoxLayout(whatBox) + self.whatButtons = QButtonGroup(self) + button = QRadioButton(_('&Entire tree')) + self.whatButtons.addButton(button, NumberingScope.fullTree) whatLayout.addWidget(button) - button = QtGui.QRadioButton(_('Selected &branches')) - self.whatButtons.addButton(button, NumberingDialog.selectBranch) + button = QRadioButton(_('Selected &branches')) + self.whatButtons.addButton(button, NumberingScope.selectBranch) whatLayout.addWidget(button) - button = QtGui.QRadioButton(_('&Selection\'s children')) - self.whatButtons.addButton(button, NumberingDialog.selectChildren) + button = QRadioButton(_('&Selection\'s children')) + self.whatButtons.addButton(button, NumberingScope.selectChildren) whatLayout.addWidget(button) - self.whatButtons.button(NumberingDialog.fullTree).setChecked(True) + self.whatButtons.button(NumberingScope.fullTree).setChecked(True) - rootBox = QtGui.QGroupBox(_('Root Node')) + rootBox = QGroupBox(_('Root Node')) topLayout.addWidget(rootBox) - rootLayout = QtGui.QVBoxLayout(rootBox) - self.rootCheck = QtGui.QCheckBox(_('Include top-level nodes')) + rootLayout = QVBoxLayout(rootBox) + self.rootCheck = QCheckBox(_('Include top-level nodes')) rootLayout.addWidget(self.rootCheck) self.rootCheck.setChecked(True) - noFieldBox = QtGui.QGroupBox(_('Handling Nodes without Numbering ' + noFieldBox = QGroupBox(_('Handling Nodes without Numbering ' 'Fields')) topLayout.addWidget(noFieldBox) - noFieldLayout = QtGui.QVBoxLayout(noFieldBox) - self.noFieldButtons = QtGui.QButtonGroup(self) - button = QtGui.QRadioButton(_('&Ignore and skip')) - self.noFieldButtons.addButton(button, NumberingDialog.ignoreNoField) + noFieldLayout = QVBoxLayout(noFieldBox) + self.noFieldButtons = QButtonGroup(self) + button = QRadioButton(_('&Ignore and skip')) + self.noFieldButtons.addButton(button, NumberingNoField.ignoreNoField) noFieldLayout.addWidget(button) - button = QtGui.QRadioButton(_('&Restart numbers for next siblings')) + button = QRadioButton(_('&Restart numbers for next siblings')) self.noFieldButtons.addButton(button, - NumberingDialog.restartAfterNoField) + NumberingNoField.restartAfterNoField) noFieldLayout.addWidget(button) - button = QtGui.QRadioButton(_('Reserve &numbers')) - self.noFieldButtons.addButton(button, NumberingDialog.reserveNoField) + button = QRadioButton(_('Reserve &numbers')) + self.noFieldButtons.addButton(button, NumberingNoField.reserveNoField) noFieldLayout.addWidget(button) - self.noFieldButtons.button(NumberingDialog. + self.noFieldButtons.button(NumberingNoField. ignoreNoField).setChecked(True) - ctrlLayout = QtGui.QHBoxLayout() + ctrlLayout = QHBoxLayout() topLayout.addLayout(ctrlLayout) ctrlLayout.addStretch() - okButton = QtGui.QPushButton(_('&OK')) + okButton = QPushButton(_('&OK')) ctrlLayout.addWidget(okButton) okButton.clicked.connect(self.numberAndClose) - applyButton = QtGui.QPushButton(_('&Apply')) + applyButton = QPushButton(_('&Apply')) ctrlLayout.addWidget(applyButton) applyButton.clicked.connect(self.updateNumbering) - closeButton = QtGui.QPushButton(_('&Close')) + closeButton = QPushButton(_('&Close')) ctrlLayout.addWidget(closeButton) closeButton.clicked.connect(self.close) self.updateCommandsAvail() @@ -1100,10 +1171,10 @@ for node in selNodes.selectedNodes(): if node.childList: hasChild = True - self.whatButtons.button(NumberingDialog. + self.whatButtons.button(NumberingScope. selectChildren).setEnabled(hasChild) if not self.whatButtons.checkedButton().isEnabled(): - self.whatButtons.button(NumberingDialog.fullTree).setChecked(True) + self.whatButtons.button(NumberingScope.fullTree).setChecked(True) def checkForNumberingFields(self): """Check that the tree formats have numbering formats. @@ -1111,41 +1182,44 @@ Return a dict of numbering field names by node format name. If not found, warn user. """ - fieldDict = (globalref.mainControl.activeControl.model.formats. + fieldDict = (globalref.mainControl.activeControl.structure.treeFormats. numberingFieldDict()) if not fieldDict: - QtGui.QMessageBox.warning(self, _('TreeLine Numbering'), + QMessageBox.warning(self, _('TreeLine Numbering'), _('No numbering fields were found in data types')) return fieldDict def updateNumbering(self): """Perform the numbering update operation. """ - QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) + QApplication.setOverrideCursor(Qt.WaitCursor) fieldDict = self.checkForNumberingFields() if fieldDict: control = globalref.mainControl.activeControl selNodes = control.currentSelectionModel().selectedNodes() - if self.whatButtons.checkedId() == NumberingDialog.fullTree: - selNodes = [control.model.root] - undo.BranchUndo(control.model.undoList, selNodes) + if (self.whatButtons.checkedId() == NumberingScope.fullTree or + len(selNodes) == 0): + selNodes = control.structure.childList + undo.DataUndo(control.structure.undoList, selNodes, addBranch=True) reserveNums = (self.noFieldButtons.checkedId() == - NumberingDialog.reserveNoField) + NumberingNoField.reserveNoField) restartSetting = (self.noFieldButtons.checkedId() == - NumberingDialog.restartAfterNoField) + NumberingNoField.restartAfterNoField) includeRoot = self.rootCheck.isChecked() - if self.whatButtons.checkedId() == NumberingDialog.selectChildren: + if self.whatButtons.checkedId() == NumberingScope.selectChildren: levelLimit = 2 else: levelLimit = sys.maxsize startNum = [1] + completedClones = set() for node in selNodes: node.updateNumbering(fieldDict, startNum, levelLimit, - includeRoot, reserveNums, restartSetting) + completedClones, includeRoot, + reserveNums, restartSetting) if not restartSetting: startNum[0] += 1 control.updateAll() - QtGui.QApplication.restoreOverrideCursor() + QApplication.restoreOverrideCursor() def numberAndClose(self): """Perform the numbering update operation and close the dialog. @@ -1167,11 +1241,12 @@ (N_('Node Menu'), _('Node')), (N_('Data Menu'), _('Data')), (N_('Tools Menu'), _('Tools')), + (N_('Format Menu'), _('Format')), (N_('View Menu'), _('View')), (N_('Window Menu'), _('Window')), (N_('Help Menu'), _('Help'))]) -class CustomShortcutsDialog(QtGui.QDialog): +class CustomShortcutsDialog(QDialog): """Dialog for customizing keyboard commands. """ def __init__(self, allActions, parent=None): @@ -1182,44 +1257,44 @@ parent -- the parent window """ super().__init__(parent) - self.setWindowFlags(QtCore.Qt.Dialog | QtCore.Qt.WindowTitleHint | - QtCore.Qt.WindowSystemMenuHint) + self.setWindowFlags(Qt.Dialog | Qt.WindowTitleHint | + Qt.WindowCloseButtonHint) self.setWindowTitle(_('Keyboard Shortcuts')) - topLayout = QtGui.QVBoxLayout(self) + topLayout = QVBoxLayout(self) self.setLayout(topLayout) - scrollArea = QtGui.QScrollArea() + scrollArea = QScrollArea() topLayout.addWidget(scrollArea) - viewport = QtGui.QWidget() - viewLayout = QtGui.QGridLayout(viewport) + viewport = QWidget() + viewLayout = QGridLayout(viewport) scrollArea.setWidget(viewport) - scrollArea.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) scrollArea.setWidgetResizable(True) self.editors = [] for i, keyOption in enumerate(globalref.keyboardOptions.values()): + category = menuNames.get(keyOption.category, _('No menu')) try: - category = menuNames[keyOption.category] action = allActions[keyOption.name] except KeyError: pass else: text = '{0} > {1}'.format(category, action.toolTip()) - label = QtGui.QLabel(text) + label = QLabel(text) viewLayout.addWidget(label, i, 0) editor = KeyLineEdit(keyOption, action, self) viewLayout.addWidget(editor, i, 1) self.editors.append(editor) - ctrlLayout = QtGui.QHBoxLayout() + ctrlLayout = QHBoxLayout() topLayout.addLayout(ctrlLayout) - restoreButton = QtGui.QPushButton(_('&Restore Defaults')) + restoreButton = QPushButton(_('&Restore Defaults')) ctrlLayout.addWidget(restoreButton) restoreButton.clicked.connect(self.restoreDefaults) ctrlLayout.addStretch(0) - self.okButton = QtGui.QPushButton(_('&OK')) + self.okButton = QPushButton(_('&OK')) ctrlLayout.addWidget(self.okButton) self.okButton.clicked.connect(self.accept) - cancelButton = QtGui.QPushButton(_('&Cancel')) + cancelButton = QPushButton(_('&Cancel')) ctrlLayout.addWidget(cancelButton) cancelButton.clicked.connect(self.reject) self.editors[0].setFocus() @@ -1243,7 +1318,7 @@ super().accept() -class KeyLineEdit(QtGui.QLineEdit): +class KeyLineEdit(QLineEdit): """Line editor for keyboad sequence entry. """ usedKeySet = set() @@ -1294,7 +1369,7 @@ Arguments: key - the QKeySequence to add """ - keyText = key.toString(QtGui.QKeySequence.NativeText) + keyText = key.toString(QKeySequence.NativeText) self.setText(keyText) self.key = key KeyLineEdit.usedKeySet.add(keyText) @@ -1306,7 +1381,7 @@ if staySelected: self.selectAll() if self.key: - KeyLineEdit.usedKeySet.remove(self.key.toString(QtGui.QKeySequence. + KeyLineEdit.usedKeySet.remove(self.key.toString(QKeySequence. NativeText)) self.key = None self.modified = True @@ -1319,7 +1394,7 @@ if self.key: self.keyAction.setShortcut(self.key) else: - self.keyAction.setShortcut(QtGui.QKeySequence()) + self.keyAction.setShortcut(QKeySequence()) def keyPressEvent(self, event): """Capture key strokes and update the editor if valid. @@ -1327,34 +1402,34 @@ Arguments: event -- the key press event """ - if event.key() in (QtCore.Qt.Key_Shift, QtCore.Qt.Key_Control, - QtCore.Qt.Key_Meta, QtCore.Qt.Key_Alt, - QtCore.Qt.Key_AltGr, QtCore.Qt.Key_CapsLock, - QtCore.Qt.Key_NumLock, QtCore.Qt.Key_ScrollLock, - QtCore.Qt.Key_Pause, QtCore.Qt.Key_Print, - QtCore.Qt.Key_Cancel): + if event.key() in (Qt.Key_Shift, Qt.Key_Control, + Qt.Key_Meta, Qt.Key_Alt, + Qt.Key_AltGr, Qt.Key_CapsLock, + Qt.Key_NumLock, Qt.Key_ScrollLock, + Qt.Key_Pause, Qt.Key_Print, + Qt.Key_Cancel): event.ignore() - elif event.key() in (QtCore.Qt.Key_Backspace, QtCore.Qt.Key_Escape): + elif event.key() in (Qt.Key_Backspace, Qt.Key_Escape): self.clearKey() event.accept() else: modifier = event.modifiers() - if modifier & QtCore.Qt.KeypadModifier: - modifier = modifier ^ QtCore.Qt.KeypadModifier - key = QtGui.QKeySequence(event.key() + int(modifier)) + if modifier & Qt.KeypadModifier: + modifier = modifier ^ Qt.KeypadModifier + key = QKeySequence(event.key() + int(modifier)) if key != self.key: - keyText = key.toString(QtGui.QKeySequence.NativeText) + keyText = key.toString(QKeySequence.NativeText) if keyText not in KeyLineEdit.usedKeySet: if self.key: KeyLineEdit.usedKeySet.remove(self.key. - toString(QtGui.QKeySequence. + toString(QKeySequence. NativeText)) self.setKey(key) self.selectAll() self.modified = True else: text = _('Key {0} is already used').format(keyText) - QtGui.QMessageBox.warning(self.parent(), 'TreeLine', text) + QMessageBox.warning(self.parent(), 'TreeLine', text) event.accept() def contextMenuEvent(self, event): @@ -1363,7 +1438,7 @@ Arguments: event -- the menu event """ - menu = QtGui.QMenu(self) + menu = QMenu(self) menu.addAction(_('Clear &Key'), self.clearKey) menu.exec_(event.globalPos()) @@ -1409,7 +1484,7 @@ super().focusInEvent(event) -class CustomToolbarDialog(QtGui.QDialog): +class CustomToolbarDialog(QDialog): """Dialog for customizing toolbar buttons. """ separatorString = _('--Separator--') @@ -1422,8 +1497,8 @@ parent -- the parent window """ super().__init__(parent) - self.setWindowFlags(QtCore.Qt.Dialog | QtCore.Qt.WindowTitleHint | - QtCore.Qt.WindowSystemMenuHint) + self.setWindowFlags(Qt.Dialog | Qt.WindowTitleHint | + Qt.WindowCloseButtonHint) self.setWindowTitle(_('Customize Toolbars')) self.allActions = allActions self.updateFunction = updateFunction @@ -1433,90 +1508,90 @@ self.availableCommands = [] self.toolbarLists = [] - topLayout = QtGui.QVBoxLayout(self) + topLayout = QVBoxLayout(self) self.setLayout(topLayout) - gridLayout = QtGui.QGridLayout() + gridLayout = QGridLayout() topLayout.addLayout(gridLayout) - sizeBox = QtGui.QGroupBox(_('Toolbar &Size')) + sizeBox = QGroupBox(_('Toolbar &Size')) gridLayout.addWidget(sizeBox, 0, 0, 1, 2) - sizeLayout = QtGui.QVBoxLayout(sizeBox) - self.sizeCombo = QtGui.QComboBox() + sizeLayout = QVBoxLayout(sizeBox) + self.sizeCombo = QComboBox() sizeLayout.addWidget(self.sizeCombo) self.sizeCombo.addItems([_('Small Icons'), _('Large Icons')]) self.sizeCombo.currentIndexChanged.connect(self.setModified) - numberBox = QtGui.QGroupBox(_('Toolbar Quantity')) + numberBox = QGroupBox(_('Toolbar Quantity')) gridLayout.addWidget(numberBox, 0, 2) - numberLayout = QtGui.QHBoxLayout(numberBox) - self.quantitySpin = QtGui.QSpinBox() + numberLayout = QHBoxLayout(numberBox) + self.quantitySpin = QSpinBox() numberLayout.addWidget(self.quantitySpin) self.quantitySpin.setRange(0, 20) - numberlabel = QtGui.QLabel(_('&Toolbars')) + numberlabel = QLabel(_('&Toolbars')) numberLayout.addWidget(numberlabel) numberlabel.setBuddy(self.quantitySpin) self.quantitySpin.valueChanged.connect(self.changeQuantity) - availableBox = QtGui.QGroupBox(_('A&vailable Commands')) + availableBox = QGroupBox(_('A&vailable Commands')) gridLayout.addWidget(availableBox, 1, 0) - availableLayout = QtGui.QVBoxLayout(availableBox) - menuCombo = QtGui.QComboBox() + availableLayout = QVBoxLayout(availableBox) + menuCombo = QComboBox() availableLayout.addWidget(menuCombo) menuCombo.addItems([_(name) for name in menuNames.keys()]) menuCombo.currentIndexChanged.connect(self.updateAvailableCommands) - self.availableListWidget = QtGui.QListWidget() + self.availableListWidget = QListWidget() availableLayout.addWidget(self.availableListWidget) - buttonLayout = QtGui.QVBoxLayout() + buttonLayout = QVBoxLayout() gridLayout.addLayout(buttonLayout, 1, 1) - self.addButton = QtGui.QPushButton('>>') + self.addButton = QPushButton('>>') buttonLayout.addWidget(self.addButton) self.addButton.setMaximumWidth(self.addButton.sizeHint().height()) self.addButton.clicked.connect(self.addTool) - self.removeButton = QtGui.QPushButton('<<') + self.removeButton = QPushButton('<<') buttonLayout.addWidget(self.removeButton) self.removeButton.setMaximumWidth(self.removeButton.sizeHint(). height()) self.removeButton.clicked.connect(self.removeTool) - toolbarBox = QtGui.QGroupBox(_('Tool&bar Commands')) + toolbarBox = QGroupBox(_('Tool&bar Commands')) gridLayout.addWidget(toolbarBox, 1, 2) - toolbarLayout = QtGui.QVBoxLayout(toolbarBox) - self.toolbarCombo = QtGui.QComboBox() + toolbarLayout = QVBoxLayout(toolbarBox) + self.toolbarCombo = QComboBox() toolbarLayout.addWidget(self.toolbarCombo) self.toolbarCombo.currentIndexChanged.connect(self. updateToolbarCommands) - self.toolbarListWidget = QtGui.QListWidget() + self.toolbarListWidget = QListWidget() toolbarLayout.addWidget(self.toolbarListWidget) self.toolbarListWidget.currentRowChanged.connect(self. setButtonsAvailable) - moveLayout = QtGui.QHBoxLayout() + moveLayout = QHBoxLayout() toolbarLayout.addLayout(moveLayout) - self.moveUpButton = QtGui.QPushButton(_('Move &Up')) + self.moveUpButton = QPushButton(_('Move &Up')) moveLayout.addWidget(self.moveUpButton) self.moveUpButton.clicked.connect(self.moveUp) - self.moveDownButton = QtGui.QPushButton(_('Move &Down')) + self.moveDownButton = QPushButton(_('Move &Down')) moveLayout.addWidget(self.moveDownButton) self.moveDownButton.clicked.connect(self.moveDown) - ctrlLayout = QtGui.QHBoxLayout() + ctrlLayout = QHBoxLayout() topLayout.addLayout(ctrlLayout) - restoreButton = QtGui.QPushButton(_('&Restore Defaults')) + restoreButton = QPushButton(_('&Restore Defaults')) ctrlLayout.addWidget(restoreButton) restoreButton.clicked.connect(self.restoreDefaults) ctrlLayout.addStretch() - self.okButton = QtGui.QPushButton(_('&OK')) + self.okButton = QPushButton(_('&OK')) ctrlLayout.addWidget(self.okButton) self.okButton.clicked.connect(self.accept) - self.applyButton = QtGui.QPushButton(_('&Apply')) + self.applyButton = QPushButton(_('&Apply')) ctrlLayout.addWidget(self.applyButton) self.applyButton.clicked.connect(self.applyChanges) self.applyButton.setEnabled(False) - cancelButton = QtGui.QPushButton(_('&Cancel')) + cancelButton = QPushButton(_('&Cancel')) ctrlLayout.addWidget(cancelButton) cancelButton.clicked.connect(self.reject) @@ -1551,22 +1626,29 @@ Arguments: defaultOnly -- if True, load default settings """ - size = globalref.toolbarOptions.getValue('ToolbarSize', defaultOnly) + size = (globalref.toolbarOptions['ToolbarSize'] if not defaultOnly else + globalref.toolbarOptions.getDefaultValue('ToolbarSize')) self.sizeCombo.blockSignals(True) if size < 24: self.sizeCombo.setCurrentIndex(0) else: self.sizeCombo.setCurrentIndex(1) self.sizeCombo.blockSignals(False) - self.numToolbars = globalref.toolbarOptions.getValue('ToolbarQuantity', - defaultOnly) + self.numToolbars = (globalref.toolbarOptions['ToolbarQuantity'] if not + defaultOnly else globalref.toolbarOptions. + getDefaultValue('ToolbarQuantity')) self.quantitySpin.blockSignals(True) self.quantitySpin.setValue(self.numToolbars) self.quantitySpin.blockSignals(False) - self.toolbarLists = [globalref.toolbarOptions.getValue('Toolbar{0}'. - format(num), - defaultOnly). - split(',') for num in range(self.numToolbars)] + self.toolbarLists = [] + commands = (globalref.toolbarOptions['ToolbarCommands'] if not + defaultOnly else globalref.toolbarOptions. + getDefaultValue('ToolbarCommands')) + self.toolbarLists = [cmd.split(',') for cmd in commands] + # account for toolbar quantity mismatch (should not happen) + del self.toolbarLists[self.numToolbars:] + while len(self.toolbarLists) < self.numToolbars: + self.toolbarLists.append([]) self.updateToolbarCombo() def updateToolbarCombo(self): @@ -1595,9 +1677,9 @@ icon = action.icon() if not icon.isNull(): self.availableCommands.append(option.name) - QtGui.QListWidgetItem(icon, action.toolTip(), + QListWidgetItem(icon, action.toolTip(), self.availableListWidget) - QtGui.QListWidgetItem(CustomToolbarDialog.separatorString, + QListWidgetItem(CustomToolbarDialog.separatorString, self.availableListWidget) self.availableListWidget.setCurrentRow(0) @@ -1613,10 +1695,10 @@ for command in self.toolbarLists[toolbarNum]: if command: action = self.allActions[command] - QtGui.QListWidgetItem(action.icon(), action.toolTip(), + QListWidgetItem(action.icon(), action.toolTip(), self.toolbarListWidget) else: # separator - QtGui.QListWidgetItem(CustomToolbarDialog.separatorString, + QListWidgetItem(CustomToolbarDialog.separatorString, self.toolbarListWidget) if self.toolbarLists[toolbarNum]: self.toolbarListWidget.setCurrentRow(0) @@ -1642,10 +1724,10 @@ command = self.availableCommands[self.availableListWidget. currentRow()] action = self.allActions[command] - item = QtGui.QListWidgetItem(action.icon(), action.toolTip()) + item = QListWidgetItem(action.icon(), action.toolTip()) except IndexError: command = '' - item = QtGui.QListWidgetItem(CustomToolbarDialog.separatorString) + item = QListWidgetItem(CustomToolbarDialog.separatorString) if self.toolbarLists[toolbarNum]: pos = self.toolbarListWidget.currentRow() + 1 else: @@ -1709,12 +1791,8 @@ globalref.toolbarOptions.changeValue('ToolbarQuantity', self.numToolbars) del self.toolbarLists[self.numToolbars:] - for num, toolbarList in enumerate(self.toolbarLists): - name = 'Toolbar{0}'.format(num) - if name not in globalref.toolbarOptions: - options.StringOptionItem(globalref.toolbarOptions, name, '', - 'Toolbar Commands') - globalref.toolbarOptions.changeValue(name, ','.join(toolbarList)) + commands = [','.join(cmds) for cmds in self.toolbarLists] + globalref.toolbarOptions.changeValue('ToolbarCommands', commands) globalref.toolbarOptions.writeFile() self.modified = False self.applyButton.setEnabled(False) @@ -1733,17 +1811,21 @@ Acts as a stand-in for PrintData class in the font page of the dialog. """ - def __init__(self, fontOption): + def __init__(self, fontOption, useAppDefault=True): """Initialize the font data. Arguments: fontOption -- the name of the font setting to retrieve + useAppDefault -- use app default if true, o/w use sys default """ self.fontOption = fontOption - self.defaultFont = QtGui.QTextDocument().defaultFont() + if useAppDefault: + self.defaultFont = QTextDocument().defaultFont() + else: + self.defaultFont = QFont(globalref.mainControl.systemFont) self.useDefaultFont = True - self.mainFont = QtGui.QTextDocument().defaultFont() - fontName = globalref.miscOptions.getValue(fontOption) + self.mainFont = QFont(self.defaultFont) + fontName = globalref.miscOptions[self.fontOption] if fontName: self.mainFont.fromString(fontName) self.useDefaultFont = False @@ -1758,12 +1840,12 @@ self.mainFont.toString()) -class CustomFontDialog(QtGui.QDialog): +class CustomFontDialog(QDialog): """Dialog for selecting custom fonts. Uses the print setup dialog's font page for the details. """ - updateRequired = QtCore.pyqtSignal() + updateRequired = pyqtSignal() def __init__(self, parent=None): """Create a font customization dialog. @@ -1771,41 +1853,66 @@ parent -- the parent window """ super().__init__(parent) - self.setWindowFlags(QtCore.Qt.Dialog | QtCore.Qt.WindowTitleHint | - QtCore.Qt.WindowSystemMenuHint) - self.setWindowTitle(_('Customize Toolbars')) + self.setWindowFlags(Qt.Dialog | Qt.WindowTitleHint | + Qt.WindowCloseButtonHint) + self.setWindowTitle(_('Customize Fonts')) - topLayout = QtGui.QVBoxLayout(self) + topLayout = QVBoxLayout(self) self.setLayout(topLayout) - tabs = QtGui.QTabWidget() - topLayout.addWidget(tabs) + self.tabs = QTabWidget() + topLayout.addWidget(self.tabs) + self.tabs.setUsesScrollButtons(False) + self.tabs.currentChanged.connect(self.updateTabDefault) self.pages = [] - treeFontPage = printdialogs.FontPage(CustomFontData('TreeFont'), True) + defaultLabel = _('&Use system default font') + appFontPage = printdialogs.FontPage(CustomFontData('AppFont', False), + defaultLabel) + self.pages.append(appFontPage) + self.tabs.addTab(appFontPage, _('App Default Font')) + defaultLabel = _('&Use app default font') + treeFontPage = printdialogs.FontPage(CustomFontData('TreeFont'), + defaultLabel) self.pages.append(treeFontPage) - tabs.addTab(treeFontPage, _('Tree View Font')) + self.tabs.addTab(treeFontPage, _('Tree View Font')) outputFontPage = printdialogs.FontPage(CustomFontData('OutputFont'), - True) + defaultLabel) self.pages.append(outputFontPage) - tabs.addTab(outputFontPage, _('Output View Font')) + self.tabs.addTab(outputFontPage, _('Output View Font')) editorFontPage = printdialogs.FontPage(CustomFontData('EditorFont'), - True) + defaultLabel) self.pages.append(editorFontPage) - tabs.addTab(editorFontPage, _('Editor View Font')) + self.tabs.addTab(editorFontPage, _('Editor View Font')) - ctrlLayout = QtGui.QHBoxLayout() + ctrlLayout = QHBoxLayout() topLayout.addLayout(ctrlLayout) ctrlLayout.addStretch() - self.okButton = QtGui.QPushButton(_('&OK')) + self.okButton = QPushButton(_('&OK')) ctrlLayout.addWidget(self.okButton) self.okButton.clicked.connect(self.accept) - self.applyButton = QtGui.QPushButton(_('&Apply')) + self.applyButton = QPushButton(_('&Apply')) ctrlLayout.addWidget(self.applyButton) self.applyButton.clicked.connect(self.applyChanges) - cancelButton = QtGui.QPushButton(_('&Cancel')) + cancelButton = QPushButton(_('&Cancel')) ctrlLayout.addWidget(cancelButton) cancelButton.clicked.connect(self.reject) + def updateTabDefault(self): + """Update the default font on the newly shown page. + """ + appFontWidget = self.tabs.widget(0) + currentWidget = self.tabs.currentWidget() + if appFontWidget is not currentWidget: + if appFontWidget.defaultCheck.isChecked(): + defaultFont = QFont(globalref.mainControl.systemFont) + else: + defaultFont = appFontWidget.readFont() + currentWidget.printData.defaultFont = defaultFont + if currentWidget.defaultCheck.isChecked(): + currentWidget.printData.mainFont = QFont(defaultFont) + currentWidget.currentFont = currentWidget.printData.mainFont + currentWidget.setFont(defaultFont) + def applyChanges(self): """Apply any changes from the dialog. """ @@ -1825,33 +1932,46 @@ super().accept() -class PluginListDialog(QtGui.QDialog): - """Dialog for listing loaded plugin modules. +class AboutDialog(QDialog): + """Show program info in a text box. """ - def __init__(self, pluginList, parent=None): - """Create a plugin list dialog. + def __init__(self, title, textLines, icon=None, parent=None): + """Create the dialog. Arguments: - pluginList -- a list of plugin descriptions to show + title -- the window title text + textLines -- a list of lines to show + icon -- an icon to show if given parent -- the parent window """ super().__init__(parent) - self.setWindowFlags(QtCore.Qt.Dialog | QtCore.Qt.WindowTitleHint | - QtCore.Qt.WindowSystemMenuHint) - self.setWindowTitle(_('TreeLine Plugins')) + self.setWindowFlags(Qt.Dialog | Qt.WindowTitleHint | + Qt.WindowCloseButtonHint) + self.setWindowTitle(title) - topLayout = QtGui.QVBoxLayout(self) + topLayout = QVBoxLayout(self) self.setLayout(topLayout) - label = QtGui.QLabel(_('Plugin Modules Loaded')) - topLayout.addWidget(label) - listBox = QtGui.QListWidget() - listBox.setSelectionMode(QtGui.QAbstractItemView.NoSelection) - listBox.addItems(pluginList) - topLayout.addWidget(listBox) + mainLayout = QHBoxLayout() + topLayout.addLayout(mainLayout) + iconLabel = QLabel() + iconLabel.setPixmap(icon.pixmap(128, 128)) + mainLayout.addWidget(iconLabel) + textBox = QPlainTextEdit() + textBox.setReadOnly(True) + textBox.setWordWrapMode(QTextOption.NoWrap) + textBox.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + textBox.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + text = '\n'.join(textLines) + textBox.setPlainText(text) + size = textBox.fontMetrics().size(0, text) + size.setHeight(size.height() + 10) + size.setWidth(size.width() + 10) + textBox.setMinimumSize(size) + mainLayout.addWidget(textBox) - ctrlLayout = QtGui.QHBoxLayout() + ctrlLayout = QHBoxLayout() topLayout.addLayout(ctrlLayout) ctrlLayout.addStretch() - self.okButton = QtGui.QPushButton(_('&OK')) - ctrlLayout.addWidget(self.okButton) - self.okButton.clicked.connect(self.accept) + okButton = QPushButton(_('&OK')) + ctrlLayout.addWidget(okButton) + okButton.clicked.connect(self.accept) diff -Nru treeline-2.0.2/source/nodeformat.py treeline-3.0.1/source/nodeformat.py --- treeline-2.0.2/source/nodeformat.py 2015-03-08 20:17:49.000000000 +0000 +++ treeline-3.0.1/source/nodeformat.py 2018-04-08 19:19:50.000000000 +0000 @@ -4,7 +4,7 @@ # nodeformat.py, provides a class to handle node format objects # # TreeLine, an information storage program -# Copyright (C) 2015, Douglas W. Bell +# Copyright (C) 2018, 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 @@ -16,22 +16,18 @@ import collections import os.path import sys -import stat import copy import operator +import datetime import xml.sax.saxutils if not sys.platform.startswith('win'): import pwd -from PyQt4 import QtCore, QtGui import fieldformat import conditional -import imports defaultFieldName = _('Name') -uniqueIdFieldName = 'Node_Unique_ID' _defaultOutputSeparator = ', ' -_lineAttrRe = re.compile(r'line\d+$') _fieldSplitRe = re.compile(r'({\*(?:\**|\?|!|&|#)[\w_\-.]+\*})') _fieldPartRe = re.compile(r'{\*(\**|\?|!|&|#)([\w_\-.]+)\*}') _endTagRe = re.compile(r'.*(|||)$') @@ -39,60 +35,120 @@ class NodeFormat: """Class to handle node format info - + Stores node field lists and line formatting. Provides methods to return formatted data. """ - def __init__(self, name, parentFormats, attrs=None, addDefaultField=False): - """Initialize a tree node. + def __init__(self, name, parentFormats, formatData=None, + addDefaultField=False): + """Initialize a tree format. Arguments: name -- the type name string parentFormats -- a ref to TreeFormats class for outside field refs - attrs -- the attributes that define this type's format + formatData -- the JSON dict for this format addDefaultField -- if true, adds a default initial field """ self.name = name self.parentFormats = parentFormats - self.idField = None - if not attrs: - attrs = {} - self.spaceBetween = attrs.get('spacebetween', 'y').startswith('y') - self.formatHtml = attrs.get('formathtml', '').startswith('y') - self.useBullets = attrs.get('bullets', '').startswith('y') - self.useTables = attrs.get('tables', '').startswith('y') - self.childType = attrs.get('childtype', '') - self.genericType = attrs.get('generic', '') - self.conditional = conditional.Conditional(attrs.get('condition', '')) - self.iconName = attrs.get('icon', '') - self.outputSeparator = attrs.get('outputsep', _defaultOutputSeparator) self.savedConditionText = {} - for key in attrs.keys(): - if key.startswith('cond-'): - self.savedConditionText[key[5:]] = attrs[key] - self.fieldDict = collections.OrderedDict() - self.derivedTypes = [] + self.conditional = None + self.childTypeLimit = set() + self.readFormat(formatData) self.siblingPrefix = '' self.siblingSuffix = '' + self.derivedTypes = [] + self.origOutputLines = [] # lines without bullet or table modifications self.sortFields = [] # temporary storage while sorting if addDefaultField: self.addFieldIfNew(defaultFieldName) - attrs['line0'] = '{{*{0}*}}'.format(defaultFieldName) - attrs['line1'] = '{{*{0}*}}'.format(defaultFieldName) - self.lineList = [['']] - self.origLineList = [] # lines without bullet or table modifications - lineNums = sorted([int(key[4:]) for key in attrs.keys() - if _lineAttrRe.match(key)]) - if lineNums: - self.lineList = [[attrs['line{0}'.format(keyNum)]] for keyNum in - lineNums] - if addDefaultField: - self.updateLineParsing() + self.titleLine = ['{{*{0}*}}'.format(defaultFieldName)] + self.outputLines = [['{{*{0}*}}'.format(defaultFieldName)]] + self.updateLineParsing() if self.useBullets: self.addBullets() if self.useTables: self.addTables() + def readFormat(self, formatData=None): + """Read JSON format data into this format. + + Arguments: + formatData -- JSON dict for this format (None for default settings) + """ + self.fieldDict = collections.OrderedDict() + if formatData: + for fieldData in formatData.get('fields', []): + fieldName = fieldData['fieldname'] + self.addField(fieldName, fieldData) + else: + formatData = {} + self.titleLine = [formatData.get('titleline', '')] + self.outputLines = [[line] for line in + formatData.get('outputlines', [])] + self.spaceBetween = formatData.get('spacebetween', True) + self.formatHtml = formatData.get('formathtml', False) + self.useBullets = formatData.get('bullets', False) + self.useTables = formatData.get('tables', False) + self.childType = formatData.get('childtype', '') + self.genericType = formatData.get('generic', '') + if 'condition' in formatData: + self.conditional = conditional.Conditional(formatData['condition']) + if 'childTypeLimit' in formatData: + self.childTypeLimit = set(formatData['childTypeLimit']) + self.iconName = formatData.get('icon', '') + self.outputSeparator = formatData.get('outputsep', + _defaultOutputSeparator) + for key in formatData.keys(): + if key.startswith('cond-'): + self.savedConditionText[key[5:]] = formatData[key] + + def storeFormat(self): + """Return JSON format data for this format. + """ + formatData = {} + formatData['formatname'] = self.name + formatData['fields'] = [field.formatData() for field in self.fields()] + formatData['titleline'] = self.getTitleLine() + formatData['outputlines'] = self.getOutputLines() + if not self.spaceBetween: + formatData['spacebetween'] = False + if self.formatHtml: + formatData['formathtml'] = True + if self.useBullets: + formatData['bullets'] = True + if self.useTables: + formatData['tables'] = True + if self.childType: + formatData['childtype'] = self.childType + if self.genericType: + formatData['generic'] = self.genericType + if self.conditional: + formatData['condition'] = self.conditional.conditionStr() + if self.childTypeLimit: + formatData['childTypeLimit'] = sorted(list(self.childTypeLimit)) + if self.iconName: + formatData['icon'] = self.iconName + if self.outputSeparator != _defaultOutputSeparator: + formatData['outputsep'] = self.outputSeparator + for key, text in self.savedConditionText.items(): + formatData['cond-' + key] = text + return formatData + + def copySettings(self, sourceFormat): + """Copy all settings from another format to this one. + + Arguments: + sourceFormat -- the format to copy + """ + self.name = sourceFormat.name + self.readFormat(sourceFormat.storeFormat()) + self.siblingPrefix = sourceFormat.siblingPrefix + self.siblingSuffix = sourceFormat.siblingSuffix + self.outputLines = sourceFormat.getOutputLines(False) + self.origOutputLines = sourceFormat.getOutputLines() + self.updateLineParsing() + def fields(self): """Return list of all fields. """ @@ -103,27 +159,30 @@ """ return list(self.fieldDict.keys()) - def formatTitle(self, node): + def formatTitle(self, node, spotRef=None): """Return a string with formatted title data. Arguments: node -- the node used to get data for fields + spotRef -- optional, used for ancestor field refs """ line = ''.join([part.outputText(node, True, self.formatHtml) if hasattr(part, 'outputText') else part - for part in self.lineList[0]]) + for part in self.titleLine]) return line.strip().split('\n', 1)[0] # truncate to 1st line - def formatOutput(self, node, plainText=False, keepBlanks=False): + def formatOutput(self, node, plainText=False, keepBlanks=False, + spotRef=None): """Return a list of formatted text output lines. Arguments: node -- the node used to get data for fields plainText -- if True, remove HTML markup from fields and formats keepBlanks -- if True, keep lines with empty fields + spotRef -- optional, used for ancestor field refs """ result = [] - for lineData in self.lineList[1:]: + for lineData in self.outputLines: line = '' numEmptyFields = 0 numFullFields = 0 @@ -150,33 +209,29 @@ result[-1] += endTagMatch.group(1) return result - def addField(self, name, attrs=None): + def addField(self, name, fieldData=None): """Add a field type with its format to the field list. Arguments: name -- the field name string - attrs -- the attributes that define this field's format + fieldData -- the dict that defines this field's format """ - if not attrs: - attrs = {} - typeName = '{}Field'.format(attrs.get('type', 'Text')) - field = getattr(fieldformat, typeName, fieldformat.TextField)(name, - attrs) + if not fieldData: + fieldData = {} + typeName = '{}Field'.format(fieldData.get('fieldtype', 'Text')) + fieldClass = getattr(fieldformat, typeName, fieldformat.TextField) + field = fieldClass(name, fieldData) self.fieldDict[name] = field - if attrs.get('idref', '').startswith('y'): - self.idField = field - elif not self.idField: - self.idField = list(self.fieldDict.values())[0] - def addFieldIfNew(self, name, attrs=None): + def addFieldIfNew(self, name, fieldData=None): """Add a field type to the field list if not already there. Arguments: name -- the field name string - attrs -- the attributes that define this field's format + fieldData -- the dict that defines this field's format """ if name not in self.fieldDict: - self.addField(name, attrs) + self.addField(name, fieldData) def addFieldList(self, nameList, addFirstTitle=False, addToOutput=False): """Add text fields with names given in list. @@ -185,7 +240,7 @@ Arguments: nameList -- the list of names to add addFirstTitle -- if True, use first field for title output format - addToOutput -- repelace output lines with all fields if True + addToOutput -- replace output lines with all fields if True """ for name in nameList: self.addFieldIfNew(name) @@ -207,25 +262,19 @@ self.fieldDict = newFieldDict def removeField(self, field): - """Remove all occurances of field from output lines. + """Remove all occurances of field from title and output lines. Arguments: field -- the field to be removed """ - for lineData in self.lineList: + while field in self.titleLine: + self.titleLine.remove(field) + for lineData in self.outputLines: while field in lineData: lineData.remove(field) - self.lineList = [line for line in self.lineList if line] - while len(self.lineList) < 2: - self.lineList.append(['']) - self.conditional.removeField(field.name) - savedConditions = {} - for name, text in self.savedConditionText.items(): - condition = conditional.Conditional(text, self.name) - condition.removeField(field.name) - if condition: - savedConditions[name] = condition.conditionStr() - self.savedConditionText = savedConditions + self.outputLines = [line for line in self.outputLines if line] + # if len(self.lineList) == 0: + # self.lineList.append(['']) def setInitDefaultData(self, data, overwrite=False): """Add initial default data from fields into supplied data dict. @@ -234,7 +283,7 @@ data -- the data dict to modify overwrite -- if true, replace previous data entries """ - for field in self.fieldDict.values(): + for field in self.fields(): text = field.getInitDefault() if text and (overwrite or not data.get(field.name, '')): data[field.name] = text @@ -245,10 +294,12 @@ Converts lines back to whole lines with embedded field names, then parse back to individual fields and text. """ - self.lineList = [self.parseLine(line) for line in self.getLines(False)] - if self.origLineList: - self.origLineList = [self.parseLine(line) for line in - self.getLines(True)] + self.titleLine = self.parseLine(self.getTitleLine()) + self.outputLines = [self.parseLine(line) for line in + self.getOutputLines(False)] + if self.origOutputLines: + self.origOutputLines = [self.parseLine(line) for line in + self.getOutputLines(True)] def parseLine(self, text): """Parse text format line, return list of field types and text. @@ -274,7 +325,7 @@ try: if not modifier: return self.fieldDict[fieldName] - elif modifier[0] == '*' and modifier == '*' * len(modifier): + elif modifier == '*' * len(modifier): return fieldformat.AncestorLevelField(fieldName, len(modifier)) elif modifier == '?': @@ -287,27 +338,28 @@ level = int(match.group(1)) return fieldformat.DescendantCountField(fieldName, level) - else: - return text elif modifier == '!': - if fieldName == uniqueIdFieldName: - return fieldformat.UniqueIdField(fieldName) - else: # file info field - return (self.parentFormats.fileInfoFormat. - fieldDict[fieldName]) + return (self.parentFormats.fileInfoFormat. + fieldDict[fieldName]) except KeyError: pass return text - def getLines(self, useOriginal=True): - """Return text list of lines with field names embedded. + def getTitleLine(self): + """Return text of title format with field names embedded. + """ + return ''.join([part.sepName() if hasattr(part, 'sepName') else part + for part in self.titleLine]) + + def getOutputLines(self, useOriginal=True): + """Return text list of output format lines with field names embedded. Arguments: useOriginal -- use original line list, wothout bullet or table mods """ - lines = self.lineList - if useOriginal and self.origLineList: - lines = self.origLineList + lines = self.outputLines + if useOriginal and self.origOutputLines: + lines = self.origOutputLines lines = [''.join([part.sepName() if hasattr(part, 'sepName') else part for part in line]) for line in lines] @@ -319,10 +371,9 @@ Arguments: text -- the new title format line """ - newLine = self.parseLine(text) - if not newLine: - newLine = [''] - self.lineList[0] = newLine + self.titleLine = self.parseLine(text) + if not self.titleLine: + self.titleLine = [''] def changeOutputLines(self, lines, keepBlanks=False): """Replace the output format lines with given list. @@ -331,18 +382,16 @@ lines -- a list of replacement format lines keepBlanks -- if False, ignore blank lines """ - self.lineList = self.lineList[:1] - if not self.lineList: - self.lineList = [''] + self.outputLines = [] for line in lines: newLine = self.parseLine(line) if keepBlanks or newLine: - self.lineList.append(newLine) + self.outputLines.append(newLine) if self.useBullets: - self.origLineList = self.lineList[:] + self.origOutputLines = self.outputLines[:] self.addBullets() if self.useTables: - self.origLineList = self.lineList[:] + self.origOutputLines = self.outputLines[:] self.addTables() def addOutputLine(self, line): @@ -351,39 +400,9 @@ Arguments: line -- the text line to add """ - if not self.lineList: - self.lineList = [''] newLine = self.parseLine(line) if newLine: - self.lineList.append(newLine) - - def xmlAttr(self): - """Return a dictionary of this type's attributes. - """ - attrs = {} - for i, line in enumerate(self.getLines()): - attrs['line{}'.format(i)] = line - if not self.spaceBetween: - attrs['spacebetween'] = 'n' - if self.formatHtml: - attrs['formathtml'] = 'y' - if self.useBullets: - attrs['bullets'] = 'y' - if self.useTables: - attrs['tables'] = 'y' - if self.childType: - attrs['childtype'] = self.childType - if self.genericType: - attrs['generic'] = self.genericType - if self.conditional: - attrs['condition'] = self.conditional.conditionStr() - if self.iconName: - attrs['icon'] = self.iconName - if self.outputSeparator != _defaultOutputSeparator: - attrs['outputsep'] = self.outputSeparator - for key, text in self.savedConditionText.items(): - attrs['cond-' + key] = text - return attrs + self.outputLines.append(newLine) def extractTitleData(self, titleString, data): """Modifies the data dictionary based on a title string. @@ -396,7 +415,7 @@ fields = [] pattern = '' extraText = '' - for seg in self.lineList[0]: + for seg in self.titleLine: if hasattr(seg, 'name'): # a field segment fields.append(seg) pattern += '(.*)' @@ -404,21 +423,29 @@ pattern += re.escape(seg) extraText += seg match = re.match(pattern, titleString) - if match: - for num, field in enumerate(fields): - text = match.group(num + 1) - if field.useRichText: - text = xml.sax.saxutils.escape(text) - data[field.name] = text - elif not extraText.strip(): - # assign to 1st field if sep is only spaces - data[fields[0].name] = titleString - for field in fields[1:]: - data[field.name] = '' - else: + try: + if match: + for num, field in enumerate(fields): + text = match.group(num + 1) + data[field.name] = field.storedTextFromTitle(text) + elif not extraText.strip(): + # assign to 1st field if sep is only spaces + text = fields[0].storedTextFromTitle(titleString) + data[fields[0].name] = text + for field in fields[1:]: + data[field.name] = '' + else: + return False + except ValueError: return False return True + def updateDerivedTypes(self): + """Update derived types after changes to this generic type. + """ + for derivedType in self.derivedTypes: + derivedType.updateFromGeneric(self) + def updateFromGeneric(self, genericType=None, formatsRef=None): """Update fields and field types to match a generic type. @@ -440,7 +467,6 @@ else: newFields[field.name] = copy.deepcopy(field) self.fieldDict = newFields - self.idField = self.fieldDict[genericType.idField.name] self.updateLineParsing() def addBullets(self): @@ -448,19 +474,18 @@ """ self.siblingPrefix = '
    ' self.siblingSuffix = '
' - lines = self.getLines() - if len(lines) > 1: - lines[1] = '
  • ' + lines[1] + lines = self.getOutputLines() + if lines != ['']: + lines[0] = '
  • ' + lines[0] lines[-1] += '
  • ' - self.origLineList = self.lineList[:] - self.lineList = lines + self.origOutputLines = self.outputLines[:] + self.outputLines = lines self.updateLineParsing() def addTables(self): """Add table HTML tags to sibling prefix, suffix and output lines. """ - origLines = self.getLines() - lines = [line for line in origLines[1:] if line] + lines = [line for line in self.getOutputLines() if line] newLines = [] headings = [] for line in lines: @@ -481,9 +506,8 @@ newLines = ['{0}'.format(line) for line in newLines] newLines[0] = '' + newLines[0] newLines[-1] += '' - self.origLineList = self.lineList[:] - self.lineList = newLines - self.lineList.insert(0, origLines[0]) + self.origOutputLines = self.outputLines[:] + self.outputLines = newLines self.updateLineParsing() def clearBulletsAndTables(self): @@ -491,32 +515,10 @@ """ self.siblingPrefix = '' self.siblingSuffix = '' - if self.origLineList: - self.lineList = self.origLineList + if self.origOutputLines: + self.outputLines = self.origOutputLines self.updateLineParsing() - self.origLineList = [] - - def updateDerivedTypes(self): - """Update derived types after changes to this generic type. - """ - for derivedType in self.derivedTypes: - derivedType.updateFromGeneric(self) - - def findLinkField(self): - """Return the field most likely to contain a bookmark URL. - - Return None if there are no matches. - """ - availFields = [field for field in self.fieldDict.values() if - field.typeName == 'ExternalLink'] - if not availFields: - return None - bestFields = [field for field in availFields if - field.name.lower() == - imports.bookmarkLinkFieldName.lower()] - if bestFields: - return bestFields[0] - return availFields[0] + self.origOutputLines = [] def numberingFieldList(self): """Return a list of numbering field names. @@ -552,17 +554,14 @@ numPagesFieldName = 'Number_of_Pages' def __init__(self, parentFormats): """Create a file info format. - - Arguments: - parentFormats -- a ref to TreeFormats class """ super().__init__(FileInfoFormat.typeName, parentFormats) self.fieldFormatModified = False self.addField(FileInfoFormat.fileFieldName) self.addField(FileInfoFormat.pathFieldName) - self.addField(FileInfoFormat.sizeFieldName, {'type': 'Number'}) - self.addField(FileInfoFormat.dateFieldName, {'type': 'Date'}) - self.addField(FileInfoFormat.timeFieldName, {'type': 'Time'}) + self.addField(FileInfoFormat.sizeFieldName, {'fieldtype': 'Number'}) + self.addField(FileInfoFormat.dateFieldName, {'fieldtype': 'Date'}) + self.addField(FileInfoFormat.timeFieldName, {'fieldtype': 'Time'}) if not sys.platform.startswith('win'): self.addField(FileInfoFormat.ownerFieldName) # page info only for print header: @@ -573,39 +572,34 @@ for field in self.fields(): field.useFileInfo = True - def updateFileInfo(self, fileName, fileInfoNode): + def updateFileInfo(self, fileObj, fileInfoNode): """Update data of file info node. Arguments: - fileName -- the TreeLine file path + fileObj -- the TreeLine file path object fileInfoNode -- the node to update """ try: - status = os.stat(fileName) - except OSError: + status = fileObj.stat() + except (AttributeError, OSError): fileInfoNode.data = {} return - fileInfoNode.data[FileInfoFormat.fileFieldName] = (os.path. - basename(fileName)) - fileInfoNode.data[FileInfoFormat.pathFieldName] = (os.path. - dirname(fileName)) - fileInfoNode.data[FileInfoFormat.sizeFieldName] = str(status[stat. - ST_SIZE]) - modDateTime = QtCore.QDateTime() - modDateTime.setTime_t(status[stat.ST_MTIME]) - modDateTime = modDateTime.toLocalTime() - modDate = modDateTime.date().toString(QtCore.Qt.ISODate) - modTime = modDateTime.time().toString() + fileInfoNode.data[FileInfoFormat.fileFieldName] = fileObj.name + fileInfoNode.data[FileInfoFormat.pathFieldName] = fileObj.parent + fileInfoNode.data[FileInfoFormat.sizeFieldName] = str(status.st_size) + modDateTime = datetime.datetime.fromtimestamp(status.st_mtime) + modDate = modDateTime.date().strftime(fieldformat.DateField.isoFormat) + modTime = modDateTime.time().strftime(fieldformat.TimeField.isoFormat) fileInfoNode.data[FileInfoFormat.dateFieldName] = modDate fileInfoNode.data[FileInfoFormat.timeFieldName] = modTime if not sys.platform.startswith('win'): try: - owner = pwd.getpwuid(status[stat.ST_UID])[0] + owner = pwd.getpwuid(status.st_uid)[0] except KeyError: - owner = repr(status[stat.ST_UID]) + owner = repr(status.st_uid) fileInfoNode.data[FileInfoFormat.ownerFieldName] = owner - def duplicateFieldFormats(self, altFileFormat): + def duplicateFileInfo(self, altFileFormat): """Copy field format settings from alternate file format. Arguments: diff -Nru treeline-2.0.2/source/optiondefaults.py treeline-3.0.1/source/optiondefaults.py --- treeline-2.0.2/source/optiondefaults.py 2015-02-07 18:55:21.000000000 +0000 +++ treeline-3.0.1/source/optiondefaults.py 2018-08-16 00:55:25.000000000 +0000 @@ -4,7 +4,7 @@ # optiondefaults.py, defines defaults for config options # # TreeLine, an information storage program -# Copyright (C) 2015, Douglas W. Bell +# Copyright (C) 2018, 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 @@ -29,21 +29,30 @@ BoolOptionItem(generalOptions, 'AutoFileOpen', False, _('Startup Condition'), _('Automatically open last file used')) - BoolOptionItem(generalOptions, 'ShowChildPane', True, + BoolOptionItem(generalOptions, 'InitShowBreadcrumb', True, _('Startup Condition'), - _('Show child pane in right hand view')) - BoolOptionItem(generalOptions, 'ShowDescendants', False, + _('Show breadcrumb ancestor view')) + BoolOptionItem(generalOptions, 'InitShowChildPane', True, + _('Startup Condition'), + _('Show child pane in right hand views')) + BoolOptionItem(generalOptions, 'InitShowDescendants', False, _('Startup Condition'), _('Show descendants in output view')) BoolOptionItem(generalOptions, 'SaveTreeStates', True, _('Startup Condition'), _('Restore tree view states of recent files')) + BoolOptionItem(generalOptions, 'PurgeRecentFiles', True, + _('Startup Condition'), + _('Remove inaccessible recent file entries')) BoolOptionItem(generalOptions, 'SaveWindowGeom', True, _('Startup Condition'), _('Restore previous window geometry')) BoolOptionItem(generalOptions, 'OpenNewWindow', True, _('Features Available'), _('Open files in new windows')) + BoolOptionItem(generalOptions, 'EditorOnHover', True, + _('Features Available'), + _('Activate data editors on mouse hover')) BoolOptionItem(generalOptions, 'ClickRename', True, _('Features Available'), _('Click node to rename')) BoolOptionItem(generalOptions, 'RenameNewNodes', True, @@ -52,9 +61,6 @@ _('Tree drag && drop available')) BoolOptionItem(generalOptions, 'ShowTreeIcons', True, _('Features Available'), _('Show icons in the tree view')) - BoolOptionItem(generalOptions, 'ShowUniqueID', False, - _('Features Available'), - _('Show node unique IDs in the Data Edit view')) BoolOptionItem(generalOptions, 'ShowMath', True, _('Features Available'), _('Show math fields in the Data Edit view')) BoolOptionItem(generalOptions, 'EditNumbering', False, @@ -66,9 +72,9 @@ _('Minutes between saves\n(set to 0 to disable)'), 1) IntOptionItem(generalOptions, 'RecentFiles', 4, 0, 99, _('Recent Files'), _('Number of recent files \nin the file menu'), 1) - StringOptionItem(generalOptions, 'EditTimeFormat', 'H:mm:ss', False, - _('Data Editor Formats'), _('Times'), 1) - StringOptionItem(generalOptions, 'EditDateFormat', 'MM/dd/yy', False, + StringOptionItem(generalOptions, 'EditTimeFormat', '%-H:%M:%S', False, + True, _('Data Editor Formats'), _('Times'), 1) + StringOptionItem(generalOptions, 'EditDateFormat', '%m/%d/%y', False, True, _('Data Editor Formats'), _('Dates'), 1) ListOptionItem(generalOptions, 'WeekStart', daysOfWeek[-1], daysOfWeek, _('Data Editor Formats'), _('First day\nof week'), 1) @@ -79,65 +85,51 @@ """Set defaults for miscellaneous config options. """ StringOptionItem = options.StringOptionItem - StringOptionItem(miscOptions, 'PrintUnits', 'in', False, - _('Printer Settings')) - StringOptionItem(miscOptions, 'SpellCheckPath', '', True, _('Spell Check')) - StringOptionItem(miscOptions, 'TreeFont', '', True, _('Fonts')) - StringOptionItem(miscOptions, 'OutputFont', '', True, _('Fonts')) - StringOptionItem(miscOptions, 'EditorFont', '', True, _('Fonts')) + StringOptionItem(miscOptions, 'PrintUnits', 'in', False, True) + StringOptionItem(miscOptions, 'SpellCheckPath', '') + StringOptionItem(miscOptions, 'AppFont', '', True, True) + StringOptionItem(miscOptions, 'TreeFont', '', True, True) + StringOptionItem(miscOptions, 'OutputFont', '', True, True) + StringOptionItem(miscOptions, 'EditorFont', '', True, True) def setHistOptionDefaults(historyOptions): """Set defaults for history config options. """ - StringOptionItem = options.StringOptionItem IntOptionItem = options.IntOptionItem - IntOptionItem(historyOptions, 'WindowXSize', 640, 10, 10000, - _('Window Geometry')) - IntOptionItem(historyOptions, 'WindowYSize', 640, 10, 10000, - _('Window Geometry')) - IntOptionItem(historyOptions, 'WindowXPos', -1000, -1000, 10000, - _('Window Geometry')) - IntOptionItem(historyOptions, 'WindowYPos', -1000, -1000, 10000, - _('Window Geometry')) - IntOptionItem(historyOptions, 'TreeSplitPercent', 40, 1, 99, - _('Window Geometry')) - IntOptionItem(historyOptions, 'OutputSplitPercent', 20, 1, 99, - _('Window Geometry')) - IntOptionItem(historyOptions, 'EditorSplitPercent', 25, 1, 99, - _('Window Geometry')) - IntOptionItem(historyOptions, 'TitleSplitPercent', 10, 1, 99, - _('Window Geometry')) - IntOptionItem(historyOptions, 'ActiveRightView', 0, 0, 2, - _('Window Geometry')) - IntOptionItem(historyOptions, 'PrintPrevXSize', 0, 0, 10000, - _('Window Geometry')) - IntOptionItem(historyOptions, 'PrintPrevYSize', 0, 0, 10000, - _('Window Geometry')) - IntOptionItem(historyOptions, 'PrintPrevXPos', -1000, -1000, 10000, - _('Window Geometry')) - IntOptionItem(historyOptions, 'PrintPrevYPos', -1000, -1000, 10000, - _('Window Geometry')) + DataListOptionItem = options.DataListOptionItem + IntOptionItem(historyOptions, 'WindowXSize', 640, 10, 10000) + IntOptionItem(historyOptions, 'WindowYSize', 640, 10, 10000) + IntOptionItem(historyOptions, 'WindowXPos', -1000, -1000, 10000) + IntOptionItem(historyOptions, 'WindowYPos', -1000, -1000, 10000) + IntOptionItem(historyOptions, 'CrumbSplitPercent', 10, 1, 99) + IntOptionItem(historyOptions, 'TreeSplitPercent', 40, 1, 99) + IntOptionItem(historyOptions, 'OutputSplitPercent', 20, 1, 99) + IntOptionItem(historyOptions, 'EditorSplitPercent', 25, 1, 99) + IntOptionItem(historyOptions, 'TitleSplitPercent', 10, 1, 99) + IntOptionItem(historyOptions, 'ActiveRightView', 0, 0, 2) + IntOptionItem(historyOptions, 'PrintPrevXSize', 0, 0, 10000) + IntOptionItem(historyOptions, 'PrintPrevYSize', 0, 0, 10000) + IntOptionItem(historyOptions, 'PrintPrevXPos', -1000, -1000, 10000) + IntOptionItem(historyOptions, 'PrintPrevYPos', -1000, -1000, 10000) + DataListOptionItem(historyOptions, 'RecentFiles', []) def setToolbarOptionDefaults(toolbarOptions): """Set defaults for toolbar geometry and buttons. """ StringOptionItem = options.StringOptionItem + DataListOptionItem = options.DataListOptionItem IntOptionItem = options.IntOptionItem - IntOptionItem(toolbarOptions, 'ToolbarQuantity', 2, 0, 20, - _('Toolbar Geometry')) - IntOptionItem(toolbarOptions, 'ToolbarSize', 16, 1, 128, - _('Toolbar Geometry')) - StringOptionItem(toolbarOptions, 'ToolbarPosition', '', True, - _('Toolbar Geometry')) - StringOptionItem(toolbarOptions, 'Toolbar0', - 'FileNew,FileOpen,FileSave,,FilePrintPreview,FilePrint,,' - 'EditUndo,EditRedo,,EditCut,EditCopy,EditPaste,,' - 'DataConfigType', True, _('Toolbar Commands')) - StringOptionItem(toolbarOptions, 'Toolbar1', - 'NodeInsertAfter,NodeAddChild,,NodeDelete,NodeIndent,' - 'NodeUnindent,,NodeMoveUp,NodeMoveDown,,' - 'ViewPrevSelect,ViewNextSelect,,ViewShowDescend', - True, _('Toolbar Commands')) + IntOptionItem(toolbarOptions, 'ToolbarQuantity', 2, 0, 20) + IntOptionItem(toolbarOptions, 'ToolbarSize', 16, 1, 128) + StringOptionItem(toolbarOptions, 'ToolbarPosition', '') + DataListOptionItem(toolbarOptions, 'ToolbarCommands', + ['FileNew,FileOpen,FileSave,,FilePrintPreview,' + 'FilePrint,,EditUndo,EditRedo,,EditCut,EditCopy,' + 'EditPaste,,DataConfigType,ToolsFindText', + 'NodeInsertAfter,NodeAddChild,,NodeDelete,NodeIndent,' + 'NodeUnindent,,NodeMoveUp,NodeMoveDown,,' + 'ViewExpandBranch,ViewCollapseBranch,,' + 'ViewPrevSelect,ViewNextSelect,,ViewShowDescend']) def setKeyboardOptionDefaults(keyboardOptions): """Set defaults for keyboard shortcuts. @@ -162,19 +154,16 @@ KeyOptionItem(keyboardOptions, 'EditCopy', 'Ctrl+C', 'Edit Menu') KeyOptionItem(keyboardOptions, 'EditPaste', 'Ctrl+V', 'Edit Menu') KeyOptionItem(keyboardOptions, 'EditPastePlain', '', 'Edit Menu') - KeyOptionItem(keyboardOptions, 'EditBoldFont', '', 'Edit Menu') - KeyOptionItem(keyboardOptions, 'EditItalicFont', '', 'Edit Menu') - KeyOptionItem(keyboardOptions, 'EditUnderlineFont', '', 'Edit Menu') - KeyOptionItem(keyboardOptions, 'EditFontSize', '', 'Edit Menu') - KeyOptionItem(keyboardOptions, 'EditFontColor', '', 'Edit Menu') - KeyOptionItem(keyboardOptions, 'EditExtLink', '', 'Edit Menu') - KeyOptionItem(keyboardOptions, 'EditIntLink', '', 'Edit Menu') - KeyOptionItem(keyboardOptions, 'EditSelectAll', 'Ctrl+L', 'Edit Menu') - KeyOptionItem(keyboardOptions, 'EditClearFormat', '', 'Edit Menu') + KeyOptionItem(keyboardOptions, 'EditPasteChild', '', 'Edit Menu') + KeyOptionItem(keyboardOptions, 'EditPasteBefore', '', 'Edit Menu') + KeyOptionItem(keyboardOptions, 'EditPasteAfter', '', 'Edit Menu') + KeyOptionItem(keyboardOptions, 'EditPasteCloneChild', '', 'Edit Menu') + KeyOptionItem(keyboardOptions, 'EditPasteCloneBefore', '', 'Edit Menu') + KeyOptionItem(keyboardOptions, 'EditPasteCloneAfter', '', 'Edit Menu') KeyOptionItem(keyboardOptions, 'NodeRename', 'Ctrl+R', 'Node Menu') + KeyOptionItem(keyboardOptions, 'NodeAddChild', 'Ctrl+A', 'Node Menu') KeyOptionItem(keyboardOptions, 'NodeInsertBefore', 'Ctrl+B', 'Node Menu') KeyOptionItem(keyboardOptions, 'NodeInsertAfter', 'Ctrl+I', 'Node Menu') - KeyOptionItem(keyboardOptions, 'NodeAddChild', 'Ctrl+A', 'Node Menu') KeyOptionItem(keyboardOptions, 'NodeDelete', 'Del', 'Node Menu') KeyOptionItem(keyboardOptions, 'NodeIndent', 'Ctrl+Shift+Right', 'Node Menu') @@ -190,10 +179,11 @@ KeyOptionItem(keyboardOptions, 'DataCopyType', '', 'Data Menu') KeyOptionItem(keyboardOptions, 'DataSortNodes', '', 'Data Menu') KeyOptionItem(keyboardOptions, 'DataNumbering', '', 'Data Menu') + KeyOptionItem(keyboardOptions, 'DataCloneMatches', '', 'Data Menu') + KeyOptionItem(keyboardOptions, 'DataDetachClones', '', 'Data Menu') KeyOptionItem(keyboardOptions, 'DataFlatCategory', '', 'Data Menu') KeyOptionItem(keyboardOptions, 'DataAddCategory', '', 'Data Menu') - KeyOptionItem(keyboardOptions, 'DataFlatLink', '', 'Data Menu') - KeyOptionItem(keyboardOptions, 'DataArrangeLink', '', 'Data Menu') + KeyOptionItem(keyboardOptions, 'DataSwapCategory', '', 'Data Menu') KeyOptionItem(keyboardOptions, 'ToolsFindText', 'Ctrl+F', 'Tools Menu') KeyOptionItem(keyboardOptions, 'ToolsFindCondition', '', 'Tools Menu') KeyOptionItem(keyboardOptions, 'ToolsFindReplace', '', 'Tools Menu') @@ -204,6 +194,15 @@ KeyOptionItem(keyboardOptions, 'ToolsShortcuts', '', 'Tools Menu') KeyOptionItem(keyboardOptions, 'ToolsToolbars', '', 'Tools Menu') KeyOptionItem(keyboardOptions, 'ToolsFonts', '', 'Tools Menu') + KeyOptionItem(keyboardOptions, 'FormatBoldFont', '', 'Format Menu') + KeyOptionItem(keyboardOptions, 'FormatItalicFont', '', 'Format Menu') + KeyOptionItem(keyboardOptions, 'FormatUnderlineFont', '', 'Format Menu') + KeyOptionItem(keyboardOptions, 'FormatFontSize', '', 'Format Menu') + KeyOptionItem(keyboardOptions, 'FormatFontColor', '', 'Format Menu') + KeyOptionItem(keyboardOptions, 'FormatExtLink', '', 'Format Menu') + KeyOptionItem(keyboardOptions, 'FormatIntLink', '', 'Format Menu') + KeyOptionItem(keyboardOptions, 'FormatSelectAll', 'Ctrl+L', 'Format Menu') + KeyOptionItem(keyboardOptions, 'FormatClearFormat', '', 'Format Menu') KeyOptionItem(keyboardOptions, 'ViewExpandBranch', 'Ctrl+Right', 'View Menu') KeyOptionItem(keyboardOptions, 'ViewCollapseBranch', 'Ctrl+Left', @@ -218,6 +217,7 @@ 'View Menu') KeyOptionItem(keyboardOptions, 'ViewTitleList', 'Ctrl+Shift+T', 'View Menu') + KeyOptionItem(keyboardOptions, 'ViewBreadcrumb', '', 'View Menu') KeyOptionItem(keyboardOptions, 'ViewShowChildPane', 'Ctrl+Shift+C', 'View Menu') KeyOptionItem(keyboardOptions, 'ViewShowDescend', 'Ctrl+Shift+D', @@ -227,4 +227,6 @@ KeyOptionItem(keyboardOptions, 'HelpBasic', '', 'Help Menu') KeyOptionItem(keyboardOptions, 'HelpFull', '', 'Help Menu') KeyOptionItem(keyboardOptions, 'HelpAbout', '', 'Help Menu') - KeyOptionItem(keyboardOptions, 'HelpPlugin', '', 'Help Menu') + KeyOptionItem(keyboardOptions, 'IncremSearchStart', 'Ctrl+/', 'No Menu') + KeyOptionItem(keyboardOptions, 'IncremSearchNext', 'F3', 'No Menu') + KeyOptionItem(keyboardOptions, 'IncremSearchPrev', 'Shift+F3', 'No Menu') diff -Nru treeline-2.0.2/source/options.py treeline-3.0.1/source/options.py --- treeline-2.0.2/source/options.py 2015-02-07 18:55:21.000000000 +0000 +++ treeline-3.0.1/source/options.py 2018-04-08 19:19:50.000000000 +0000 @@ -3,7 +3,7 @@ #****************************************************************************** # options.py, provides a class to manage config options # -# Copyright (C) 2015, Douglas W. Bell +# Copyright (C) 2018, 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 @@ -13,10 +13,20 @@ from collections import OrderedDict import sys +import re +import pathlib import os.path -from PyQt4 import QtCore, QtGui +import json +from PyQt5.QtCore import Qt +from PyQt5.QtGui import QKeySequence +from PyQt5.QtWidgets import (QButtonGroup, QCheckBox, QComboBox, QDialog, + QDoubleSpinBox, QGridLayout, QGroupBox, + QHBoxLayout, QLabel, QLineEdit, QPushButton, + QRadioButton, QSpinBox, QVBoxLayout) import miscdialogs +multipleSpaceRegEx = re.compile(r' {2,}') + class StringOptionItem: """Class to store and control a string-based config option entry. @@ -24,8 +34,8 @@ Stores the name, value, category and description, provides validation, config file output and dialog controls. """ - def __init__(self, optionDict, name, value, emptyOK=True, category='', - description='', columnNum=0): + def __init__(self, optionDict, name, value, emptyOK=True, + stripSpaces=False, category='', description='', columnNum=0): """Set the parameters and initial value and add to optionDict. Raises a ValueError if initial validation fails. @@ -34,6 +44,7 @@ name -- the string key for the option value -- the string value emptyOK -- if False, does not allow empty string + stripSpaces -- if True, remove leading, trailing & double spaces category -- a string for the option group this belongs to description -- a string for use in the control dialog columnNum -- the column position for this control in the dialog @@ -43,6 +54,7 @@ self.description = description self.columnNum = columnNum self.emptyOK = emptyOK + self.stripSpaces = stripSpaces self.dialogControl = None self.value = None self.setValue(value) @@ -59,6 +71,8 @@ value -- the string value to set """ value = str(value) + if self.stripSpaces: + value = multipleSpaceRegEx.sub(' ', value.strip()) if value != self.value and (value or self.emptyOK): self.value = value return True @@ -66,36 +80,23 @@ raise ValueError return False - def valueString(self): - """Return a string representation of the value. + def storedValue(self): + """Return the value to be stored in the JSON file. """ - return str(self.value) + return self.value - def outputLine(self, padding=20): - """Return an output line for writing to a config file. - - Arguments: - padding -- the width to reserve for the name key - """ - return '{0:{1}}{2}\n'.format(self.name, padding, self.valueString()) - - def addDialogControl(self, rowLayout, currentGroupBox): + def addDialogControl(self, groupBox): """Add the labels and controls to a dialog box for this option item. - Uses the current group box if the category matches, otherwise - starts a new one. Returns the current group box. Arguments: - rowLayout -- the vertical box layout holding the group boxes - currentGroupBox -- the currently used group box + groupBox -- the current group box """ - groupBox = self.getGroupBox(rowLayout, currentGroupBox) gridLayout = groupBox.layout() row = gridLayout.rowCount() - label = QtGui.QLabel(self.description, groupBox) + label = QLabel(self.description, groupBox) gridLayout.addWidget(label, row, 0) - self.dialogControl = QtGui.QLineEdit(self.value, groupBox) + self.dialogControl = QLineEdit(self.value, groupBox) gridLayout.addWidget(self.dialogControl, row, 1) - return groupBox def updateFromDialog(self): """Set the value of this item from the dialog contents. @@ -104,22 +105,6 @@ """ return self.setValue(self.dialogControl.text()) - def getGroupBox(self, rowLayout, currentGroupBox): - """Return the group box for use with this option item category. - - Group box is same if category matches, creates one otherwise. - Arguments: - rowLayout -- the vertical box layout holding the group boxes - currentGroupBox -- the currently used group box - """ - if currentGroupBox and currentGroupBox.title() == self.category: - return currentGroupBox - newGroupBox = QtGui.QGroupBox(self.category, - rowLayout.parentWidget()) - rowLayout.addWidget(newGroupBox) - QtGui.QGridLayout(newGroupBox) - return newGroupBox - class IntOptionItem(StringOptionItem): """Class to store and control an integer-based config option entry. @@ -144,8 +129,8 @@ """ self.minimum = minimum self.maximum = maximum - super().__init__(optionDict, name, value, False, category, description, - columnNum) + super().__init__(optionDict, name, value, False, False, category, + description, columnNum) def setValue(self, value): """Sets the value and validates, returns True if OK. @@ -171,28 +156,23 @@ return True return False - def addDialogControl(self, rowLayout, currentGroupBox): + def addDialogControl(self, groupBox): """Add the labels and controls to a dialog box for this option item. - Uses the current group box if the category matches, otherwise - starts a new one. Returns the current group box. Arguments: - rowLayout -- the vertical box layout holding the group boxes - currentGroupBox -- the currently used group box + groupBox -- the current group box """ - groupBox = self.getGroupBox(rowLayout, currentGroupBox) gridLayout = groupBox.layout() row = gridLayout.rowCount() - label = QtGui.QLabel(self.description, groupBox) + label = QLabel(self.description, groupBox) gridLayout.addWidget(label, row, 0) - self.dialogControl = QtGui.QSpinBox(groupBox) + self.dialogControl = QSpinBox(groupBox) self.dialogControl.setValue(self.value) if self.minimum != None: self.dialogControl.setMinimum(self.minimum) if self.maximum != None: self.dialogControl.setMaximum(self.maximum) gridLayout.addWidget(self.dialogControl, row, 1) - return groupBox def updateFromDialog(self): """Set the value of this item from the dialog contents. @@ -225,8 +205,8 @@ """ self.minimum = minimum self.maximum = maximum - super().__init__(optionDict, name, value, False, category, description, - columnNum) + super().__init__(optionDict, name, value, False, False, category, + description, columnNum) def setValue(self, value): """Sets the value and validates, returns True if OK. @@ -252,28 +232,23 @@ return True return False - def addDialogControl(self, rowLayout, currentGroupBox): + def addDialogControl(self, groupBox): """Add the labels and controls to a dialog box for this option item. - Uses the current group box if the category matches, otherwise - starts a new one. Returns the current group box. Arguments: - rowLayout -- the vertical box layout holding the group boxes - currentGroupBox -- the currently used group box + groupBox -- the current group box """ - groupBox = self.getGroupBox(rowLayout, currentGroupBox) gridLayout = groupBox.layout() row = gridLayout.rowCount() - label = QtGui.QLabel(self.description, groupBox) + label = QLabel(self.description, groupBox) gridLayout.addWidget(label, row, 0) - self.dialogControl = QtGui.QDoubleSpinBox(groupBox) + self.dialogControl = QDoubleSpinBox(groupBox) self.dialogControl.setValue(self.value) if self.minimum != None: self.dialogControl.setMinimum(self.minimum) if self.maximum != None: self.dialogControl.setMaximum(self.maximum) gridLayout.addWidget(self.dialogControl, row, 1) - return groupBox def updateFromDialog(self): """Set the value of this item from the dialog contents. @@ -302,8 +277,8 @@ description -- a string for use in the control dialog columnNum -- the column position for this control in the dialog """ - super().__init__(optionDict, name, value, False, category, description, - columnNum) + super().__init__(optionDict, name, value, False, False, category, + description, columnNum) def setValue(self, value): """Sets the value and validates, returns True if OK. @@ -328,27 +303,17 @@ raise ValueError return False - def valueString(self): - """Return a string representation of the value. - """ - return 'yes' if self.value else 'no' - - def addDialogControl(self, rowLayout, currentGroupBox): + def addDialogControl(self, groupBox): """Add the labels and controls to a dialog box for this option item. - Uses the current group box if the category matches, otherwise - starts a new one. Returns the current group box. Arguments: - rowLayout -- the vertical box layout holding the group boxes - currentGroupBox -- the currently used group box + groupBox -- the current group box """ - groupBox = self.getGroupBox(rowLayout, currentGroupBox) gridLayout = groupBox.layout() row = gridLayout.rowCount() - self.dialogControl = QtGui.QCheckBox(self.description, groupBox) + self.dialogControl = QCheckBox(self.description, groupBox) self.dialogControl.setChecked(self.value) gridLayout.addWidget(self.dialogControl, row, 0, 1, 2) - return groupBox def updateFromDialog(self): """Set the value of this item from the dialog contents. @@ -379,8 +344,8 @@ columnNum -- the column position for this control in the dialog """ self.choices = choices - super().__init__(optionDict, name, value, False, category, description, - columnNum) + super().__init__(optionDict, name, value, False, False, category, + description, columnNum) def setValue(self, value): """Sets the value and validates, returns True if OK. @@ -399,25 +364,20 @@ raise ValueError return False - def addDialogControl(self, rowLayout, currentGroupBox): + def addDialogControl(self, groupBox): """Add the labels and controls to a dialog box for this option item. - Uses the current group box if the category matches, otherwise - starts a new one. Returns the current group box. Arguments: - rowLayout -- the vertical box layout holding the group boxes - currentGroupBox -- the currently used group box + groupBox -- the current group box """ - groupBox = self.getGroupBox(rowLayout, currentGroupBox) gridLayout = groupBox.layout() row = gridLayout.rowCount() - label = QtGui.QLabel(self.description, groupBox) + label = QLabel(self.description, groupBox) gridLayout.addWidget(label, row, 0) - self.dialogControl = QtGui.QComboBox(groupBox) + self.dialogControl = QComboBox(groupBox) self.dialogControl.addItems(self.choices) self.dialogControl.setCurrentIndex(self.choices.index(self.value)) gridLayout.addWidget(self.dialogControl, row, 1) - return groupBox def updateFromDialog(self): """Set the value of this item from the dialog contents. @@ -447,7 +407,7 @@ columnNum -- the column position for this control in the dialog """ self.choices = choices - super().__init__(optionDict, name, value, False, category, '', + super().__init__(optionDict, name, value, False, False, category, '', columnNum) def setValue(self, value): @@ -467,26 +427,22 @@ raise ValueError return False - def addDialogControl(self, rowLayout, currentGroupBox): + def addDialogControl(self, groupBox): """Add the labels and controls to a dialog box for this option item. - Always starts a new group box, returns None (group not reused). Arguments: - rowLayout -- the vertical box layout holding the group boxes - currentGroupBox -- the currently used group box + groupBox -- the current group box """ - groupBox = QtGui.QGroupBox(self.category, rowLayout.parentWidget()) rowLayout.addWidget(groupBox) - QtGui.QGridLayout(groupBox) + QGridLayout(groupBox) gridLayout = groupBox.layout() - self.dialogControl = QtGui.QButtonGroup(groupBox) + self.dialogControl = QButtonGroup(groupBox) row = 0 for choice in self.choices: - button = QtGui.QRadioButton(choice, groupBox) + button = QRadioButton(choice, groupBox) self.dialogControl.addButton(button) gridLayout.addWidget(button, row, 0, 1, 2) row += 1 - return None def updateFromDialog(self): """Set the value of this item from the dialog contents. @@ -499,11 +455,10 @@ class KeyOptionItem(StringOptionItem): """Class to store and control a keyboard key based config option entry. - Stores the name, value, category and description, provides validation, - config file output and dialog controls. + Stores the name, value and category, provides validation and config file + output. """ - def __init__(self, optionDict, name, value, category='', description='', - columnNum=0): + def __init__(self, optionDict, name, value, category=''): """Set the parameters and initial value and add to optionDict. Raises a ValueError if initial validation fails. @@ -512,11 +467,8 @@ name -- the string key for the option value -- a numeric or string-based value category -- a string for the option group this belongs to - description -- a string for use in the control dialog - columnNum -- the column position for this control in the dialog """ - super().__init__(optionDict, name, value, True, category, description, - columnNum) + super().__init__(optionDict, name, value, True, False, category) def setValue(self, value): """Sets the value and validates, returns True if OK. @@ -525,9 +477,9 @@ or if the value is unchanged. Raises a ValueError if validation fails without an old value. Arguments: - value -- a numeric or string-based value to set + value -- a key string value to set """ - key = QtGui.QKeySequence(value) + key = QKeySequence(value) if value and key.isEmpty(): if self.value == None: raise ValueError @@ -537,35 +489,49 @@ return True return False - def valueString(self): - """Return a string representation of the value. + def storedValue(self): + """Return the value to be stored in the JSON file. """ return self.value.toString() - def addDialogControl(self, rowLayout, currentGroupBox): - """Add the labels and controls to a dialog box for this option item. - Not implemented yet (needed here?). - Returns the current group box. +class DataListOptionItem(StringOptionItem): + """Class to store an arbitrary length list containing various data. + + Stores the name and value, provides validation and config file output. + """ + def __init__(self, optionDict, name, value): + """Set the parameters and initial value and add to optionDict. + + Raises a ValueError if initial validation fails. Arguments: - rowLayout -- the vertical box layout holding the group boxes - currentGroupBox -- the currently used group box + optionDict -- a dictionary to add this option item to + name -- the string key for the option + value -- a list containg other basic data types """ - return currentGroupBox + super().__init__(optionDict, name, value) - def updateFromDialog(self): - """Set the value of this item from the dialog contents. + def setValue(self, value): + """Sets the value and validates, returns True if OK. - Not implemented yet (needed here?). - Return True if successfully set. + Returns False if validation fails but the old value is OK, + or if the value is unchanged. + Raises a ValueError if validation fails without an old value. + Arguments: + value -- a list containg other basic data types """ + if isinstance(value, list) and value != self.value: + self.value = value + return True + if self.value == None: + raise ValueError return False class Options(OrderedDict): """Class to store and control the config options for a program. """ - basePath = '' + basePath = None def __init__(self, fileName='', progName='', version='', coDirName=''): """Initialize and set the path to the config file. @@ -579,68 +545,62 @@ """ super().__init__() self.modified = False - self.path = '' + self.path = pathlib.Path() if not fileName: return # no storage without fileName (temporary options only) appDirName = '{0}-{1}'.format(progName.lower(), version) - if sys.platform.startswith('win'): # Windows - fileNameSuffix = '.ini' - if not Options.basePath: - userPath = os.path.join(os.environ.get('APPDATA', ''), - coDirName, appDirName) - else: # Linux, etc. - fileNameSuffix = 'rc' - if not Options.basePath: - userPath = os.path.join(os.environ.get('HOME', ''), - '.' + appDirName) + fileNameSuffix = '.ini' if sys.platform.startswith('win') else 'rc' + if not Options.basePath: - if os.path.exists(userPath): + if sys.platform.startswith('win'): # Windows + userPath = (pathlib.Path(os.environ.get('APPDATA', '')) / + coDirName / appDirName) + else: # Linux, etc. + userPath = (pathlib.Path(os.path.expanduser('~')) / + ('.' + appDirName)) + if userPath.is_dir(): Options.basePath = userPath else: - modPath = os.path.dirname(os.path.abspath(sys.path[0])) - modConfigPath = os.path.join(modPath, 'config') - if os.path.exists(modConfigPath): + modPath = pathlib.Path(sys.path[0]).resolve() + modConfigPath = modPath / 'config' + if modConfigPath.is_dir(): Options.basePath = modConfigPath - elif os.access(modPath, os.W_OK): + elif os.access(str(modPath), os.W_OK): dialog = miscdialogs.RadioChoiceDialog(progName, _('Choose configuration file location'), [(_('User\'s home directory (recommended)'), 0), (_('Program directory (for portable use)'), 1)]) - if dialog.exec_() != QtGui.QDialog.Accepted: + if dialog.exec_() != QDialog.Accepted: sys.exit(0) if dialog.selectedButton() == 1: Options.basePath = modConfigPath if not Options.basePath: Options.basePath = userPath try: - if not os.path.exists(Options.basePath): - os.makedirs(Options.basePath) - iconPath = os.path.join(Options.basePath, 'icons') - if not os.path.exists(iconPath): - os.makedirs(iconPath) - templatePath = os.path.join(Options.basePath, 'templates') - if not os.path.exists(templatePath): - os.makedirs(templatePath) - pluginPath = os.path.join(Options.basePath, 'plugins') - if not os.path.exists(pluginPath): - os.makedirs(pluginPath) + if not Options.basePath.is_dir(): + Options.basePath.mkdir(parents=True) + iconPath = Options.basePath / 'icons' + if not iconPath.is_dir(): + iconPath.mkdir() + templatePath = Options.basePath / 'templates' + if not templatePath.is_dir(): + templatePath.mkdir() + templateExportPath = templatePath / 'exports' + if not templateExportPath.is_dir(): + templateExportPath.mkdir() except OSError: - Options.basePath = '' + Options.basePath = None if Options.basePath: - self.path = os.path.join(Options.basePath, - fileName + fileNameSuffix) + self.path = Options.basePath / (fileName + fileNameSuffix) - def getValue(self, name, defaultValue=False): - """Return a value from the given option key. + def __getitem__(self, name): + """Return the value of an option when called as options[name]. Arguments: name -- the string key for the option - defaultValue -- if True, return the default value, not current one """ - if not defaultValue: - return self[name].value - return self[name].defaultValue + return self.get(name).value def getDefaultValue(self, name): """Return the initially set default value from the given option key. @@ -648,7 +608,7 @@ Arguments: name -- the string key for the option """ - return self[name].defaultValue + return self.get(name).defaultValue def changeValue(self, name, value): """Set a new value for the given option key. @@ -658,11 +618,21 @@ name -- the string key for the option value -- a value or a string defining the value """ - if self[name].setValue(value): + if self.get(name).setValue(value): self.modified = True return True return False + def resetToDefaults(self, keyList): + """Reset all options with the given keys to original default values. + + Arguments: + keyList -- a list of option names to reset + """ + for key in keyList: + self.get(key).setValue(self.get(key).defaultValue) + self.modified = True + def removeValue(self, name): """Remove the value from the option list if possible. @@ -683,25 +653,14 @@ Only updates existing config items. """ try: - with open(self.path, 'r', encoding='utf-8') as f: - lastCategory = '' - for line in f: - line = line.strip() - if line and not line.startswith('#'): - try: - name, value = line.split(None, 1) - except ValueError: - name = line - value = '' - try: - self[name].setValue(value) - lastCategory = self[name].category - except KeyError: - # create a blank default entry if none exists - StringOptionItem(self, name, '', True, - lastCategory) - self[name].setValue(value) - except IOError: + with self.path.open('r', encoding='utf-8') as f: + data = json.load(f) + for key, value in data.items(): + try: + self.get(key).setValue(value) + except AttributeError: + pass + except (IOError, ValueError): self.writeFile() def writeFile(self): @@ -709,45 +668,45 @@ Raises IOError on failure. """ - with open(self.path, 'w', encoding='utf-8') as f: - padding = max(len(option.name) for option in self.values()) + 2 - prevCategory = '' - for option in self.values(): - if option.category and option.category != prevCategory: - f.write('\n# {}:\n'.format(option.category)) - prevCategory = option.category - f.write(option.outputLine(padding)) + with self.path.open('w', encoding='utf-8') as f: + data = OrderedDict([(key, obj.storedValue()) for (key, obj) in + self.items()]) + json.dump(data, f, indent=0) self.modified = False -class OptionDialog(QtGui.QDialog): +class OptionDialog(QDialog): """Provides a dialog with controls for all options in an Options instance. """ def __init__(self, options, parent=None): super().__init__(parent) self.options = options - self.setWindowFlags(QtCore.Qt.Dialog | QtCore.Qt.WindowTitleHint | - QtCore.Qt.WindowSystemMenuHint) - topLayout = QtGui.QVBoxLayout(self) + self.setWindowFlags(Qt.Dialog | Qt.WindowTitleHint | + Qt.WindowCloseButtonHint) + topLayout = QVBoxLayout(self) self.setLayout(topLayout) - columnLayout = QtGui.QHBoxLayout() + columnLayout = QHBoxLayout() topLayout.addLayout(columnLayout) - rowLayout = QtGui.QVBoxLayout() + rowLayout = QVBoxLayout() columnLayout.addLayout(rowLayout) groupBox = None for option in self.options.values(): if option.columnNum > columnLayout.count() - 1: - rowLayout = QtGui.QVBoxLayout() + rowLayout = QVBoxLayout() columnLayout.addLayout(rowLayout) - groupBox = option.addDialogControl(rowLayout, groupBox) + if not groupBox or groupBox.title() != option.category: + groupBox = QGroupBox(option.category) + rowLayout.addWidget(groupBox) + QGridLayout(groupBox) + option.addDialogControl(groupBox) - ctrlLayout = QtGui.QHBoxLayout() + ctrlLayout = QHBoxLayout() topLayout.addLayout(ctrlLayout) ctrlLayout.addStretch(0) - okButton = QtGui.QPushButton(_('&OK')) + okButton = QPushButton(_('&OK')) ctrlLayout.addWidget(okButton) okButton.clicked.connect(self.accept) - cancelButton = QtGui.QPushButton(_('&Cancel')) + cancelButton = QPushButton(_('&Cancel')) ctrlLayout.addWidget(cancelButton) cancelButton.clicked.connect(self.reject) diff -Nru treeline-2.0.2/source/outputview.py treeline-3.0.1/source/outputview.py --- treeline-2.0.2/source/outputview.py 2015-03-06 03:22:26.000000000 +0000 +++ treeline-3.0.1/source/outputview.py 2018-04-08 19:19:50.000000000 +0000 @@ -4,7 +4,7 @@ # outputview.py, provides a class for the data output view # # TreeLine, an information storage program -# Copyright (C) 2015, Douglas W. Bell +# Copyright (C) 2017, 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 @@ -12,68 +12,73 @@ # but WITTHOUT ANY WARRANTY. See the included LICENSE file for details. #****************************************************************************** -import os.path -from PyQt4 import QtCore, QtGui -import treenode +from PyQt5.QtCore import Qt +from PyQt5.QtGui import QPalette, QTextCursor +from PyQt5.QtWidgets import QTextBrowser, QTextEdit import treeoutput import urltools import dataeditors import globalref -class OutputView(QtGui.QTextBrowser): +class OutputView(QTextBrowser): """Class override for the data output view. Sets view defaults and updates the content. """ - def __init__(self, selectModel, isChildView=True, parent=None): + def __init__(self, treeView, isChildView=True, parent=None): """Initialize the output view. Arguments: - selectModel - the tree view's selection model + treeView - the tree view, needed for the current selection model isChildView -- shows selected nodes if false, child nodes if true parent -- the parent main window """ super().__init__(parent) - self.selectModel = selectModel + self.treeView = treeView self.isChildView = isChildView - self.hideChildView = not globalref.genOptions.getValue('ShowChildPane') - self.showDescendants = globalref.genOptions.getValue('ShowDescendants') - self.setFocusPolicy(QtCore.Qt.NoFocus) + self.hideChildView = not globalref.genOptions['InitShowChildPane'] + self.showDescendants = globalref.genOptions['InitShowDescendants'] + self.setFocusPolicy(Qt.NoFocus) def updateContents(self): """Reload the view's content if the view is shown. Avoids update if view is not visible or has zero height or width. """ - selNodes = self.selectModel.selectedNodes() - if self.isChildView and (len(selNodes) != 1 or self.hideChildView or - not selNodes[0].childList): + selSpots = self.treeView.selectionModel().selectedSpots() + if self.isChildView: + if (len(selSpots) > 1 or self.hideChildView or + (selSpots and not selSpots[0].nodeRef.childList)): + self.hide() + return + if not selSpots: + # use top node childList from tree structure + selSpots = [globalref.mainControl.activeControl.structure. + structSpot()] + elif not selSpots: self.hide() - else: - self.show() - if not self.isVisible() or self.height() == 0 or self.width() == 0: return - if not selNodes: - self.setHtml('') + self.show() + if not self.isVisible() or self.height() == 0 or self.width() == 0: return if self.isChildView: if self.showDescendants: - outputGroup = treeoutput.OutputGroup(selNodes, False, True) + outputGroup = treeoutput.OutputGroup(selSpots, False, True) if outputGroup.hasPrefixes(): outputGroup.combineAllSiblings() outputGroup.addBlanksBetween() outputGroup.addAbsoluteIndents() else: - outputGroup = treeoutput.OutputGroup(selNodes[0].childList) + outputGroup = treeoutput.OutputGroup(selSpots[0].childSpots()) outputGroup.addBlanksBetween() outputGroup.addSiblingPrefixes() else: - outputGroup = treeoutput.OutputGroup(selNodes) + outputGroup = treeoutput.OutputGroup(selSpots) outputGroup.addBlanksBetween() outputGroup.addSiblingPrefixes() self.setHtml('\n'.join(outputGroup.getLines())) - self.setSearchPaths([globalref.mainControl.defaultFilePath(True)]) + self.setSearchPaths([str(globalref.mainControl.defaultPathObj(True))]) def setSource(self, url): """Called when a user clicks on a URL link. @@ -84,12 +89,12 @@ """ name = url.toString() if name.startswith('#'): - if not self.selectModel.selectNodeById(name[1:]): + if not self.treeView.selectionModel().selectNodeById(name[1:]): super().setSource(url) else: if urltools.isRelative(name): # check for relative path - defaultPath = globalref.mainControl.defaultFilePath(True) - name = urltools.toAbsolute(name, defaultPath) + defaultPath = globalref.mainControl.defaultPathObj(True) + name = urltools.toAbsolute(name, str(defaultPath)) dataeditors.openExtUrl(name) def hasSelectedText(self): @@ -104,10 +109,10 @@ wordList -- list of words to highlight regExpList -- a list of regular expression objects to highlight """ - backColor = self.palette().brush(QtGui.QPalette.Active, - QtGui.QPalette.Highlight) - foreColor = self.palette().brush(QtGui.QPalette.Active, - QtGui.QPalette.HighlightedText) + backColor = self.palette().brush(QPalette.Active, + QPalette.Highlight) + foreColor = self.palette().brush(QPalette.Active, + QPalette.HighlightedText) if wordList is None: wordList = [] if regExpList is None: @@ -120,12 +125,12 @@ selections = [] for word in wordList: while self.find(word): - extraSel = QtGui.QTextEdit.ExtraSelection() + extraSel = QTextEdit.ExtraSelection() extraSel.cursor = self.textCursor() extraSel.format.setBackground(backColor) extraSel.format.setForeground(foreColor) selections.append(extraSel) - cursor = QtGui.QTextCursor(self.document()) + cursor = QTextCursor(self.document()) self.setTextCursor(cursor) # reset main cursor/selection self.setExtraSelections(selections) diff -Nru treeline-2.0.2/source/plugininterface.py treeline-3.0.1/source/plugininterface.py --- treeline-2.0.2/source/plugininterface.py 2015-09-18 00:45:24.000000000 +0000 +++ treeline-3.0.1/source/plugininterface.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,1239 +0,0 @@ -#!/usr/bin/env python3 - -#****************************************************************************** -# plugininterface.py, provides an interface class for plugin extension modules -# -# TreeLine, an information storage program -# Copyright (C) 2015, 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 -# version. This program is distributed in the hope that it will be useful, -# but WITTHOUT ANY WARRANTY. See the included LICENSE file for details. -#****************************************************************************** - - -"""Plugin Interface Rules - - Plugins are python files located in a plugins directory, either one created - by the TreeLine installer or one in the user's config file location. - - Plugins must define a function named "main" that takes an instance of the - PluginInterface class as its only argument. The function should initialize - the plugin. It is called after the TreeLine GUI is initialized and an - initial file (new or automatic) is loaded. The return value of the - function is stored by TreeLine to avoid garbage collection of the - reference. - - There should be a module doc string defined by the plugin. The first line - is used as the plugin listing in Help->About Plugins. It should contain the - plugin name and a very brief description. - - To avoid problems with plugins breaking when TreeLine is revised, the plugin - API is restricted to the methods of the PluginInterface class. References - from within the method code and elsewhere in TreeLine code should not be - used. Exceptions to this rule include certain data members of node objects - (childList, parent and data). Of course, if a method returns a Qt object, - the normal Qt API is available. - - There are methods that setup callback functions for various TreeLine - operations. The functions are called when various actions are taken - in TreeLine. - - Note that the plugins are initialized after TreeLine has fully initiallized - and the first file (new or auomatically opened) has been loaded. This - allows plugins to access the full TreeLine interface when initializing, but - it means that callback functions (new window, new file amd open file) are - not invoked for the first file. - - Plugins used with windows binary installations are limited to the Python - modules that are used somewhere in TreeLine itself. No other modules are - available, with the exception of urllib which is specifically included in - the binary for plugin use. -""" - -import sys -import os.path -import io -import tempfile -from xml.etree import ElementTree -from PyQt4 import QtCore, QtGui -import treeoutput -import nodeformat -import fieldformat -import treeopener -import dataeditview -import exports -import options -import globalref - - -class PluginInterface(): - """Defines the available interface for the plugins. - """ - def __init__(self): - """Initialize the interface. - """ - self.dataChangeCallbacks = [] # internal use only - self.formatChangeCallbacks = [] # internal use only - self.selectChangeCallbacks = [] # internal use only - self.newWindowCallbacks = [] # internal use only - self.fileModCallbacks = [] # internal use only - self.fileNewCallbacks = [] # internal use only - self.fileOpenCallbacks = [] # internal use only - self.fileSaveCallbacks = [] # internal use only - - def mainControl(self): - """Return the main control object. - - Can be used as a presistent parent QObject for new actions. - """ - return globalref.mainControl - - def pluginPath(self): - """Return the path of this plugin's directory. - """ - try: - frame = sys._getframe(1) - fileName = frame.f_code.co_filename - finally: - del frame - return os.path.dirname(fileName) - - def getLanguage(self): - """Return language code used by TreeLine for translations. - """ - return globalref.lang - - - #************************************************************************** - # Node Interfaces: - #************************************************************************** - - def getCurrentNode(self): - """Return a reference to the currently active node in the tree view. - """ - return (globalref.mainControl.activeControl.currentSelectionModel(). - currentNode()) - - def getSelectedNodes(self): - """Return a list of currently selected nodes. - """ - return (globalref.mainControl.activeControl.currentSelectionModel(). - selectedNodes()) - - def changeSelection(self, newSelectList): - """Clear the current selection and select the nodes in the given list. - - Also updates the right-view after the change. - Arguments: - newSelectList -- a list of nodes to be selected - """ - (globalref.mainControl.activeControl.currentSelectionModel(). - selectNodes(newSelectList)) - - def getRootNode(self): - """Return a reference to the root node. - """ - return globalref.mainControl.activeControl.model.root - - def getNodeChildList(self, node): - """Return a list of the given node's child nodes. - - This is provided for completeness, node.childList may be used directly. - Arguments: - node -- the given parent node - """ - return node.childList - - def getNodeParent(self, node): - """Return the given node's parent node. - - This is provided for completeness, node.parent may be used directly. - Arguments: - node -- the given child node - """ - return node.parent - - def getNodeDescendantList(self, node): - """Return a list of the given node and all descendant nodes. - - Arguments: - node -- the given parent node - """ - return list(node.descendantGen()) - - def addChildNode(self, parent, text='New'): - """Add new child node at the end of the current child list. - - Returns the new node. - Arguments: - parent -- the parent node - text -- the new node's title text - """ - return parent.addNewChild(newTitle=text) - - def insertSibling(self, siblingNode, text='New', inAfter=False): - """Add new sibling before or after sibling - return new item. - - Arguments: - siblingNode -- the node adjacent to the new node - text -- the new node's title text - inAfter -- insert after the sibling if True - """ - return siblingNode.parent.addNewChild(siblingNode, not inAfter, text) - - def setNodeOpenClosed(self, node, setOpen=True): - """Open children in tree if open is True, close children if False. - - Arguments: - node -- the parent node - setOpen -- expand if True, collapse if False - """ - view = globalref.mainControl.activeControl.activeWindow.treeView - if setOpen: - view.expand(node.index()) - else: - view.collapse(node.index()) - - def setBranchOpenClosed(self, node, setOpen=True): - """Open all chhildren in tree if open True, close children if False. - - Arguments: - node -- the parent node - setOpen -- expand if True, collapse if False - """ - view = globalref.mainControl.activeControl.activeWindow.treeView - if setOpen: - view.expandBranch(node) - else: - view.collapseBranch(node) - - def getNodeDataDict(self, node): - """Return the given node's data dictionary. - - This is provided for completeness, node.data may be used directly. - Arguments: - node -- the given node - """ - return node.data - - def getNodeUniqueId(self, node): - """Return the given node's unique ID string. - - Arguments: - node -- the given node - """ - return node.uniqueId - - def updateUniqueId(self, node): - """Update the node's unique ID string based on data or format changes. - - Arguments: - node -- the given node - """ - node.setUniqueId(True) - - def getNodeByUniqueId(self, uniqueId): - """Return the node matching the given unique ID. - - Raises KeyError if no matching ID is found. - Arguments: - uniqueId -- the unique ID string to find - """ - return globalref.mainControl.activeControl.model.nodeIdDict[uniqueId] - - def getNodeTitle(self, node): - """Return the formatted text for the node's title in the tree view. - - Arguments: - node -- the given node - """ - return node.title() - - def setNodeTitle(self, node, titleText): - """Set this node's title based on a provided string. - - Modifies the field data used in the title format. - Returns True if successful, False otherwise. - Arguments: - node -- the given node - titleText -- the new title string - """ - return node.setTitle(titleText) - - def getNodeOutput(self, node, lineSep='
    \n'): - """Return the formatted HTML text for the node's output. - - Arguments: - node -- the given node - lineSep -- separator for text lines - """ - return lineSep.join(node.formatOutput()) - - def getChildNodeOutput(self, node): - """Return the formatted HTML text for the node children's output. - - Arguments: - node -- the given parent node - """ - outputGroup = treeoutput.OutputGroup(node.childList) - outputGroup.addBlanksBetween() - outputGroup.addAbsoluteIndents() - return '\n'.join(outputGroup.getLines()) - - def getFieldOutput(self, node, fieldName): - """Return formatted text for the given fieldName data. - - Arguments: - node -- the given node - fieldName -- the field name with the data - """ - nodeFormat = node.nodeFormat() - try: - field = nodeFormat.fieldDict[fieldName] - return field.outputText(node, False, nodeFormat.formatHtml) - except KeyError: - return '' - - def getNodeFormatName(self, node): - """Return the format type name for the given node. - - Arguments: - node -- the given node - """ - return node.formatName - - def setNodeFormat(self, node, formatName): - """Set the given node to the given node format type. - - Arguments: - node -- the given node - formatName -- the given type format name - """ - node.changeDataType(formatName) - - def setDataChangeCallback(self, callbackFunc): - """Set callback function called when a node's dict data is changed. - - The callbackFunc must take one argument: the node being changed. - Arguments: - callbackFunc -- the function to be called - """ - self.dataChangeCallbacks.append(callbackFunc) - - - #************************************************************************** - # Format Interfaces: - #************************************************************************** - - def getNodeFormatNames(self): - """Return text list of available node format names. - """ - return (globalref.mainControl.activeControl.model.formats. - typeNames()) - - def newNodeFormat(self, formatName, defaultFieldName='Name'): - """Create a new node format and add it to the available tree formats. - - The format name must only contain characters [a-zA-Z0-9_.-]. - If defaultFieldName, a text field is created and added to the title - line and the first output line. - Arguments: - formatName -- the new format name - defaultFieldName -- if defined, a text field is created and added - """ - treeFormats = globalref.mainControl.activeControl.model.formats - newFormat = nodeformat.NodeFormat(formatName, treeFormats) - if defaultFieldName: - newFormat.addFieldIfNew(defaultFieldName) - newFormat.lineList = ['{{*{0}*}}'.format(defaultFieldName)] * 2 - newFormat.updateLineParsing() - treeFormats[formatName] = newFormat - - def copyFileFormat(self, fileRef): - """Copy the configuration from another TreeLine file, - - If fileRef is a file-like object, fileRef.name must be defined. - Returns True/False on success/failure. - Arguments: - fileRef -- either a file path string or a file-like object - """ - try: - opener = treeopener.TreeOpener() - tmpModel = opener.readFile(fileName) - except (IOError, treeopener.ParseError): - return False - model = globalref.mainControl.activeControl.model - model.formats.copyTypes(tmpModel.formats, model) - return True - - def getFormatIconName(self, formatName): - """Return the node format's currently set icon name. - - A default setting will return an empty string, - blank will return 'NoIcon'. - Arguments: - formatName -- the given format name - """ - nodeFormat = (globalref.mainControl.activeControl.model. - formats[formatName]) - return nodeFormat.iconName - - def setFormatIconName(self, formatName, iconName): - """Set the node format's icon to iconName. - - An empty string will get the default icon, use 'NoIcon' to get a blank. - Arguments: - formatName -- the given format nam - iconName -- the new icon name - """ - nodeFormat = (globalref.mainControl.activeControl.model. - formats[formatName]) - nodeFormat.iconName = iconName - - def addTreeIcon(self, name, image): - """Add an icon to those available for use in the tree. - - The icon data can be in any image format supported by Qt. - If the name matches one already loaded, the earlier one is replaced. - Arguments: - name -- the new icon name - image -- the image data - """ - icon = QtGui.QIcon() - pixmap = QtGui.QPixmap(image) - if not pixmap.isNull(): - icon.addPixmap(pixmap) - globalref.treeIcons[name] = icon - - def getTitleLineFormat(self, formatName): - """Return the format's title formatting line with field names embedded. - - Arguments: - formatName -- the given format name - """ - nodeFormat = (globalref.mainControl.activeControl.model. - formats[formatName]) - return nodeFormat.getLines()[0] - - def setTitleLineFormat(self, formatName, newLine): - """Set the node format's title formatting line to newLine. - - Arguments: - formatName -- the given format name - newLine -- the line to set - """ - nodeFormat = (globalref.mainControl.activeControl.model. - formats[formatName]) - nodeFormat.changeTitleLine(newLine) - nodeFormat.updateLineParsing() - - def getOutputFormatLines(self, formatName): - """Return a list of output formatting lines with field names embedded. - - Arguments: - formatName -- the given format name - """ - nodeFormat = (globalref.mainControl.activeControl.model. - formats[formatName]) - return nodeFormat.getLines()[0] - - def setOutputFormatLines(self, formatName, lineList): - """Set the node format's output formatting lines to lineList. - - Arguments: - formatName -- the given format name - lineList -- a list of lines to set - """ - nodeFormat = (globalref.mainControl.activeControl.model. - formats[formatName]) - nodeFormat.changeOutputLines(lineList) - nodeFormat.updateLineParsing() - - def getFormatSpaceBetween(self, formatName): - """Return True if the type is set to add a space between nodes. - - Arguments: - formatName -- the given format name - """ - nodeFormat = (globalref.mainControl.activeControl.model. - formats[formatName]) - return nodeFormat.spaceBetween - - def setFormatSpaceBetween(self, formatName, spaceBetween=True): - """Change the type space between node setting. - - Arguments: - formatName -- the given format name - spaceBetween -- True for spaces between nodes, False otherwise - """ - nodeFormat = (globalref.mainControl.activeControl.model. - formats[formatName]) - nodeFormat.spaceBetween = spaceBetween - - def getFormatHtmlProp(self, formatName): - """Return True if the type uses HTML in formats, False for plain text. - - Arguments: - formatName -- the given format name - """ - nodeFormat = (globalref.mainControl.activeControl.model. - formats[formatName]) - return nodeFormat.formatHtml - - def setFormatHtmlProp(self, formatName, htmlProp=True): - """Change the format HTML handling of the given type. - - Arguments: - formatName -- the given format name - htmlProp -- True for HTML in formats, False for plain text - """ - nodeFormat = (globalref.mainControl.activeControl.model. - formats[formatName]) - nodeFormat.formatHtml = htmlProp - - def getFormatFieldNames(self, formatName): - """Return a list of the node format's field names. - - Arguments: - formatName -- the given format name - """ - nodeFormat = (globalref.mainControl.activeControl.model. - formats[formatName]) - return nodeFormat.fieldNames() - - def addNewFormatField(self, formatName, fieldName, fieldType='Text'): - """Add a new field to the node format. - - Type should be one of: Text, HtmlText, OneLineText, SpacedText, - Number, Math, Numbering, Boolean, Date, Time, Choice, AutoChoice, - Combination, AutoCombination, ExternalLink, InternalLink, Picture, - RegularExpression. - Arguments: - formatName -- the given format name - fieldName -- the new field name - fieldType -- the new field type - """ - nodeFormat = (globalref.mainControl.activeControl.model. - formats[formatName]) - nodeFormat.addField(fieldName, {'type': fieldType}) - - def getFormatFieldType(self, formatName, fieldName): - """Return the type string of the given field in the given format. - - Arguments: - formatName -- the given format name - fieldName -- the given field name - """ - nodeFormat = (globalref.mainControl.activeControl.model. - formats[formatName]) - field = nodeFormat.fieldDict[fieldName] - return field.typeName - - def changeFormatFieldType(self, formatName, fieldName, newFieldType): - """Change the type of the given field in the given format. - - Field type should be one of: Text, HtmlText, OneLineText, SpacedText, - Number, Math, Numbering, Boolean, Date, Time, Choice, AutoChoice, - Combination, AutoCombination, ExternalLink, InternalLink, Picture, - RegularExpression. - Arguments: - formatName -- the given format name - fieldName -- the given field name - newFieldType -- the new field type name string - """ - nodeFormat = (globalref.mainControl.activeControl.model. - formats[formatName]) - field = nodeFormat.fieldDict[fieldName] - field.changeType(newFieldType) - - def registerNewFieldType(self, fieldTypeClass): - """Make a new field type class available. - - The new class should be derived from an existing filed class (see - getFieldClass method, below). - The class name must end in 'Field' and it must have a typeName - attribute that is the name without the 'Field' ending. - Arguments: - fieldTypeClass -- the new field type class - """ - fieldformat.fieldTypes.append(fieldTypeClass.typeName) - setattr(fieldformat, fieldTypeClass.__name__, fieldTypeClass) - - def getFieldClass(self, fieldTypeName): - """Return the class associated with the given field type name. - - The returned class can be used as a base class for new field types. - Field type should be one of: Text, HtmlText, OneLineText, SpacedText, - Number, Math, Numbering, Boolean, Date, Time, Choice, AutoChoice, - Combination, AutoCombination, ExternalLink, InternalLink, Picture, - RegularExpression. - Arguments: - fieldTypeName -- the field type name string - """ - fieldTypeName = '{}Field'.format(fieldTypeName) - return getattr(fieldformat, fieldTypeName) - - def getFormatFieldFormat(self, formatName, fieldName): - """Return the format code string of the given field. - - Arguments: - formatName -- the given format name - fieldName -- the given field name - """ - nodeFormat = (globalref.mainControl.activeControl.model. - formats[formatName]) - field = nodeFormat.fieldDict[fieldName] - return field.format - - def setFormatFieldFormat(self, formatName, fieldName, newFieldFormat): - """Change the format code string of the given field. - - Arguments: - formatName -- the given format name - fieldName -- the given field name - newFieldFormat -- the new field formatting string - """ - nodeFormat = (globalref.mainControl.activeControl.model. - formats[formatName]) - field = nodeFormat.fieldDict[fieldName] - field.setFormat(newFieldFormat) - - def getFormatFieldExtraText(self, formatName, fieldName): - """Return a tuple of the prefix and suffix text of the given field. - - Arguments: - formatName -- the given format name - fieldName -- the given field name - """ - nodeFormat = (globalref.mainControl.activeControl.model. - formats[formatName]) - field = nodeFormat.fieldDict[fieldName] - return (field.prefix, field.suffix) - - def setFormatFieldExtraText(self, formatName, fieldName, newPrefix='', - newSuffix=''): - """Set the format prefix and suffix text of the given field. - - Arguments: - formatName -- the given format name - fieldName -- the given field name - newPrefix -- a new prefix (blank for none) - newSuffix -- a new suffix (blank for none) - """ - nodeFormat = (globalref.mainControl.activeControl.model. - formats[formatName]) - field = nodeFormat.fieldDict[fieldName] - field.prefix = newPrefix - field.suffix = newSuffix - - def getFormatFieldNumLines(self, formatName, fieldName): - """Return the number of lines set for the given field. - - Arguments: - formatName -- the given format name - fieldName -- the given field name - """ - nodeFormat = (globalref.mainControl.activeControl.model. - formats[formatName]) - field = nodeFormat.fieldDict[fieldName] - return field.numLines - - def setFormatFieldNumLines(self, formatName, fieldName, numLines): - """Set the number of lines set for the given field. - - Arguments: - formatName -- the given format name - fieldName -- the given field name - numLines -- the new number of lines - """ - nodeFormat = (globalref.mainControl.activeControl.model. - formats[formatName]) - field = nodeFormat.fieldDict[fieldName] - field.numLines = numLines - - def setFormatChangeCallback(self, callbackFunc): - """Set callback function called when the data format is modified. - - The callbackFunc must take no arguments. - Arguments: - callbackFunc -- the function to be called - """ - self.formatChangeCallbacks.append(callbackFunc) - - - #************************************************************************** - # View Interfaces: - #************************************************************************** - - def updateViews(self): - """Refresh all tree and right-hand views using current data. - """ - globalref.mainControl.activeControl.updateAll(False) - - def updateRightViews(self): - """Refresh all right-hand views using current data. - """ - globalref.mainControl.activeControl.updateRightViews() - - def updateTreeNode(self, node): - """Update the given node in all tree views. - - Arguments: - node -- the node to be updated - """ - globalref.mainControl.activeControl.updateTreeNode(node, False) - - def getActiveWindow(self): - """Return the currently active main window. - - Can be used as a QWidet parent. - """ - return globalref.mainControl.activeControl.activeWindow - - def getActiveEditView(self): - """Return the active editor in the Data Editor right-hand view. - - Returns None if something else has the focus. - """ - widget = QtGui.QApplication.focusWidget() - if (hasattr(widget, 'paste') and - isinstance(widget.parent().parent(), dataeditview.DataEditView)): - return widget - return None - - def insertEditViewText(self, text, usePlainText=True): - """Inserts text in the active Data Editor right-hand editor. - - Does nothing if something else has the focus. - Arguments: - usePlainText -- insert as formatted HTML if False - """ - editor = self.getActiveEditView() - if editor: - if hasattr(editor, 'lineEdit'): - editor.lineEdit().insert(text) - elif usePlainText: - editor.insertPlainText(text) - else: - editor.insertHtml(text) - - def setSelectChangeCallback(self, callbackFunc): - """Set callback function called when the tree selection is changed. - - The callbackFunc must take no arguments. - Arguments: - callbackFunc -- the function to be called - """ - self.selectChangeCallbacks.append(callbackFunc) - - def setNewWindowCallback(self, callbackFunc): - """Set callback function called when a new main window is created. - - The callbackFunc must take no arguments. - Arguments: - callbackFunc -- the function to be called - """ - self.newWindowCallbacks.append(callbackFunc) - - - #************************************************************************** - # File Interfaces: - #************************************************************************** - - def openFile(self, fileRef, checkModified=True, importOnFail=True): - """Open file given by fileRef interactively. - - Uses QMessageBox to inform user of issues/failures. - If fileRef is a file-like object, fileRef.name must be defined. - Arguments: - fileRef -- either a file path string or a file-like object - checkModified -- if True, prompt user about current modified file - importOnFail -- if True, prompts for import on non-TreeLine files - """ - if hasattr(fileRef, 'read'): - fd, fileName = tempfile.mkstemp(text=True) - os.write(fd, fileRef.read()) - os.close(fd) - fileRef.close() - else: - fileName = fileRef - globalref.mainControl.openFile(fileName, checkModified, importOnFail) - if hasattr(fileRef, 'read'): - os.remove(fileName) - globalref.mainControl.activeControl.filePath = fileRef.name - globalref.mainControl.activeControl.updateWindowCaptions() - globalref.mainControl.recentFiles.removeItem(fileName) - - def readFile(self, fileRef): - """Open TreeLine file given by fileRef non-interactively. - - Returns True/False on success/failure. - Does not work with compressed or encrypted files. - If fileRef is a file-like object, fileRef.name must be defined. - Arguments: - fileRef -- either a file path string or a file-like object - """ - try: - globalref.mainControl.createLocalControl(fileRef) - return True - except (treeopener.ParseError, IOError): - return False - - def newFile(self): - """Start a new file interactively. - - Prompts user for template to be used. - """ - globalref.mainControl.fileNew() - - def createFile(self): - """Start a new file non-interactively. - - Uses the default single-line template. - """ - globalref.mainControl.createLocalControl() - - def saveFile(self): - """Save TreeLine file interactively. - - Uses QMessageBox for possible password prompts or failures. - """ - globalref.mainControl.activeControl.fileSave() - - def writeFile(self, fileRef): - """Save TreeLine file to fileRef non-interactively. - - Returns True/False on success/failure. - Does not compress or encrypt files. - If fileRef is a file-like object, fileRef.name must be defined. - Arguments: - fileRef -- either a file path string or a file-like object - """ - rootElement = globalref.mainControl.model.root.elementXml() - rootElement.attrib.update(globalref.mainControl.model.formats. - xmlAttr()) - rootElement.attrib.update(globalref.mainControl.printData.xmlAttr()) - if globalref.mainControl.spellCheckLang: - rootElement.set('spellchk', globalref.mainControl.spellCheckLang) - if not globalref.mainControl.model.mathZeroBlanks: - rootElement.set('zeroblanks', 'n') - elementTree = ElementTree.ElementTree(rootElement) - try: - # use binary for regular files to avoid newline translation - fileIO = io.BytesIO() - elementTree.write(fileIO, 'utf-8', True) - data = fileIO.getvalue() - fileIO.close() - if hasattr(fileRef, 'write'): - fileRef.write(data) - else: - with open(saveFilePath, 'wb') as f: - f.write(data) - return True - except IOError: - return False - - def getFileFilters(self): - """Return a dictionary of all available file filters. - - See globalref.py file for the key values. - """ - return globalref.fileFilters - - def getCurrentFileName(self): - """Return the currently open file path. - """ - return globalref.mainControl.activeControl.filePath - - def getDocModified(self): - """Return True if the current document is marked as modified. - """ - return globalref.mainControl.activeControl.modified - - def setDocModified(self, value=True): - """Set the document status to modified or unmodified. - - Also updates command availablility. - Arguments: - value -- if True sets to modified, if False sets to unmodified - """ - globalref.mainControl.activeControl.setModified(value) - - def fileExport(self): - """Interactive export of data to html,xml, etc. via dialog. - """ - globalref.mainControl.activeControl.fileExport() - - def exportHtmlSingle(self, filePath, includeRoot=True, openOnly=False, - addHeader=False, numColumns=1): - """Export selected branches to single-page HTML. - - Return True on successful export. - Arguments: - filePath -- the path and filename to export to - includeRoot -- if True, include the root of the selection - openOnly -- if True, do not include collapsed nodes - addHeader -- if True, include the print header in the export - numColumns -- the number of columns in the exported page - """ - exports.ExportDialog.includeRoot = includeRoot - exports.ExportDialog.openOnly = openOnly - exports.ExportDialog.addHeader = addHeader - exports.ExportDialog.numColumns = numColumns - exports.ExportDialog.exportWhat = exports.ExportDialog.selectBranch - localControl = globalref.mainControl.activeControl - exportControl = exports.ExportControl(localControl.model.root, - localControl.currentSelectionModel().selectedNodes(), - globalref.mainControl.defaultFilePath()) - try: - return exportControl.exportHtmlSingle(filePath) - except IOError: - return False - - def exportHtmlNavSingle(self, filePath, includeRoot=True, openOnly=False, - addHeader=False, navPaneLevels=1): - """Export selected branches to single-page HTML with a navigation pane. - - Return True on successful export. - Arguments: - filePath -- the path and filename to export to - includeRoot -- if True, include the root of the selection - openOnly -- if True, do not include collapsed nodes - addHeader -- if True, include the print header in the export - numColumns -- the number of columns in the exported page - """ - exports.ExportDialog.includeRoot = includeRoot - exports.ExportDialog.openOnly = openOnly - exports.ExportDialog.addHeader = addHeader - exports.ExportDialog.navPaneLevels = navPaneLevels - exports.ExportDialog.exportWhat = exports.ExportDialog.selectBranch - localControl = globalref.mainControl.activeControl - exportControl = exports.ExportControl(localControl.model.root, - localControl.currentSelectionModel().selectedNodes(), - globalref.mainControl.defaultFilePath()) - try: - return exportControl.exportHtmlNavSingle(filePath) - except IOError: - return False - - def exportHtmlPages(self, filePath): - """Export selected branches to multiple web pages with navigation pane. - - Return True on successful export. - Arguments: - filePath -- the directory path to export to - """ - exports.ExportDialog.exportWhat = exports.ExportDialog.selectBranch - localControl = globalref.mainControl.activeControl - exportControl = exports.ExportControl(localControl.model.root, - localControl.currentSelectionModel().selectedNodes(), - globalref.mainControl.defaultFilePath()) - try: - return exportControl.exportHtmlPages(filePath) - except IOError: - return False - - def exportHtmlTables(self, filePath, addHeader=False): - """Export selected branches to multiple web page tables. - - Return True on successful export. - Arguments: - filePath -- the directory path to export to - addHeader -- if True, include the print header in the export - """ - exports.ExportDialog.addHeader = addHeader - exports.ExportDialog.exportWhat = exports.ExportDialog.selectBranch - localControl = globalref.mainControl.activeControl - exportControl = exports.ExportControl(localControl.model.root, - localControl.currentSelectionModel().selectedNodes(), - globalref.mainControl.defaultFilePath()) - try: - return exportControl.exportHtmlTables(filePath) - except IOError: - return False - - def exportTextTitles(self, filePath, includeRoot=True, openOnly=False): - """Export selected branches to tabbed title text. - - Return True on successful export. - Arguments: - filePath -- the path and filename to export to - includeRoot -- if True, include the root of the selection - openOnly -- if True, do not include collapsed nodes - """ - exports.ExportDialog.includeRoot = includeRoot - exports.ExportDialog.openOnly = openOnly - exports.ExportDialog.exportWhat = exports.ExportDialog.selectBranch - localControl = globalref.mainControl.activeControl - exportControl = exports.ExportControl(localControl.model.root, - localControl.currentSelectionModel().selectedNodes(), - globalref.mainControl.defaultFilePath()) - try: - return exportControl.exportTextTitles(filePath) - except IOError: - return False - - def exportTextPlain(self, filePath, includeRoot=True, openOnly=False): - """Export selected branches to unformatted output text. - - Return True on successful export. - Arguments: - filePath -- the path and filename to export to - includeRoot -- if True, include the root of the selection - openOnly -- if True, do not include collapsed nodes - """ - exports.ExportDialog.includeRoot = includeRoot - exports.ExportDialog.openOnly = openOnly - exports.ExportDialog.exportWhat = exports.ExportDialog.selectBranch - localControl = globalref.mainControl.activeControl - exportControl = exports.ExportControl(localControl.model.root, - localControl.currentSelectionModel().selectedNodes(), - globalref.mainControl.defaultFilePath()) - try: - return exportControl.exportTextPlain(filePath) - except IOError: - return False - - def exportTextTables(self, filePath): - """Export the children of the selection to a tab delimited text table. - - Return True on successful export. - Arguments: - filePath -- the path and filename to export to - """ - exports.ExportDialog.exportWhat = exports.ExportDialog.selectBranch - localControl = globalref.mainControl.activeControl - exportControl = exports.ExportControl(localControl.model.root, - localControl.currentSelectionModel().selectedNodes(), - globalref.mainControl.defaultFilePath()) - try: - return exportControl.exportTextTables(filePath) - except IOError: - return False - - def exportXmlGeneric(self, filePath): - """Export selected branches to generic XML. - - Return True on successful export. - Arguments: - filePath -- the path and filename to export to - """ - exports.ExportDialog.exportWhat = exports.ExportDialog.selectBranch - localControl = globalref.mainControl.activeControl - exportControl = exports.ExportControl(localControl.model.root, - localControl.currentSelectionModel().selectedNodes(), - globalref.mainControl.defaultFilePath()) - try: - return exportControl.exportXmlGeneric(filePath) - except IOError: - return False - - def exportXmlSubtree(self, filePath): - """Export selected branches to a TreeLine subtree. - - Return True on successful export. - Arguments: - filePath -- the path and filename to export to - """ - exports.ExportDialog.exportWhat = exports.ExportDialog.selectBranch - localControl = globalref.mainControl.activeControl - exportControl = exports.ExportControl(localControl.model.root, - localControl.currentSelectionModel().selectedNodes(), - globalref.mainControl.defaultFilePath()) - try: - return exportControl.exportXmlSubtree(filePath) - except IOError: - return False - - def exportOdfText(self, filePath): - """Export selected branches to an ODF text file. - - Return True on successful export. - Arguments: - filePath -- the path and filename to export to - """ - exports.ExportDialog.exportWhat = exports.ExportDialog.selectBranch - localControl = globalref.mainControl.activeControl - exportControl = exports.ExportControl(localControl.model.root, - localControl.currentSelectionModel().selectedNodes(), - globalref.mainControl.defaultFilePath()) - try: - return exportControl.exportOdfText(filePath) - except IOError: - return False - - def exportBookmarksHtml(self, filePath): - """Export selected branches to HTML format bookmarks. - - Return True on successful export. - Arguments: - filePath -- the path and filename to export to - """ - exports.ExportDialog.exportWhat = exports.ExportDialog.selectBranch - localControl = globalref.mainControl.activeControl - exportControl = exports.ExportControl(localControl.model.root, - localControl.currentSelectionModel().selectedNodes(), - globalref.mainControl.defaultFilePath()) - try: - return exportControl.exportBookmarksHtml(filePath) - except IOError: - return False - - def exportBookmarksXbel(self, filePath): - """Export selected branches to XBEL format bookmarks. - - Return True on successful export. - Arguments: - filePath -- the path and filename to export to - """ - exports.ExportDialog.exportWhat = exports.ExportDialog.selectBranch - localControl = globalref.mainControl.activeControl - exportControl = exports.ExportControl(localControl.model.root, - localControl.currentSelectionModel().selectedNodes(), - globalref.mainControl.defaultFilePath()) - try: - return exportControl.exportBookmarksXbel(filePath) - except IOError: - return False - - def setFileModCallback(self, callbackFunc): - """Set callback function called when anything modifies file data. - - The callbackFunc must take no arguments. - Arguments: - callbackFunc -- the function to be called - """ - self.fileModCallbacks.append(callbackFunc) - - def setFileNewCallback(self, callbackFunc): - """Set callback function to be called after a new file is started. - - The callbackFunc must take no arguments. - Arguments: - callbackFunc -- the function to be called - """ - self.fileNewCallbacks.append(callbackFunc) - - def setFileOpenCallback(self, callbackFunc): - """Set callback function to be called after opening a TreeLine file. - - The callbackFunc must take no arguments. - Arguments: - callbackFunc -- the function to be called - """ - self.fileOpenCallbacks.append(callbackFunc) - - def setFileSaveCallback(self, callbackFunc): - """Set callback function to be called after a file is saved. - - The callbackFunc must take no arguments. - Arguments: - callbackFunc -- the function to be called - """ - self.fileSaveCallbacks.append(callbackFunc) - - - #************************************************************************** - # Menu Interfaces: - #************************************************************************** - - def execMenuAction(self, actionName): - """Execute the action associated with the given menu item. - - Names can be found in the keyboard option section of optiondefaults.py. - Raises an IndexError if the name is not valid. - Does nothing if the action is not enabled. - Arguments: - actionName -- the name of the menu action - """ - action = (globalref.mainControl.activeControl.activeWindow. - allActions[actionName]) - if action.isEnabled(): - action.trigger() - - def getMenuBar(self): - """Return the main window's top menu bar (QMenuBar). - """ - return globalref.mainControl.activeControl.activeWindow.menuBar() - - def getPulldownMenu(self, index): - """Return top pulldown menu (QMenu) at position index. - - Raise IndexError if index is not valid. - Arguments: - index -- the menu number - """ - return self.getMenuBar().actions()[index].menu() - - def addActionCustomize(self, action, menuName): - """Adds action to the custom key shortcuts and custom toolbar dialogs. - - This does not add it to a menu (use insertAction(...) on a QMenu). - The action should already have its default shortcut (if any) set. - The action must have an icon set for it to show in the toolbr dialog. - Arguments: - action -- the QAction being added - menuName -- top level menu (must be one of 'File', 'Edit', etc.) - """ - indexName = action.toolTip().replace(' ', '') - menuName = menuName + ' Menu' - try: - key = globalref.keyboardOptions.getValue(indexName) - except KeyError: - key = None - if key: - action.setShortcut(key) - options.KeyOptionItem(globalref.keyboardOptions, indexName, - action.shortcut().toString(), menuName) - globalref.mainControl.allActions[indexName] = action - for control in globalref.mainControl.localControls: - control.allActions[indexName] = action - for window in control.windowList: - window.allActions[indexName] = action - window.setupToolbars() - - - #************************************************************************** - # General Options: - #************************************************************************** - - def getOptionValue(self, name): - """Return the value of a general option item. - - See the setGenOptionDefaults function in the optiondefaults.py file - for available name values. - Raises a KeyError if the name is incorrect. - Arguments: - name -- the key name for the option - """ - return globalref.genOptions.getValue(name) - - def changeOptionValue(self, name, value): - """Set the name of an existing option to the given value. - - Returns True if changed, False if value is the same or not permitted. - See the setGenOptionDefaults function in the optiondefaults.py file - for available name values. - Raises a KeyError if the name is incorrect. - Arguments: - name -- the key name for the option - value -- a value or a string defining the value - """ - return globalref.genOptions.changeValue(name, value) - - - #************************************************************************** - # Internal methods (not for plugin use): - #************************************************************************** - - def execCallback(self, funcList, *args): - """Call functions in funcList with given args if any. - - Arguments: - funcList -- the stored callback function list - *args -- the callback argumants - """ - for func in funcList: - func(*args) diff -Nru treeline-2.0.2/source/printdata.py treeline-3.0.1/source/printdata.py --- treeline-2.0.2/source/printdata.py 2015-01-31 19:16:31.000000000 +0000 +++ treeline-3.0.1/source/printdata.py 2018-04-08 19:19:50.000000000 +0000 @@ -4,7 +4,7 @@ # printdata.py, provides a class for printing # # TreeLine, an information storage program -# Copyright (C) 2015, Douglas W. Bell +# Copyright (C) 2017, 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 @@ -13,12 +13,20 @@ #****************************************************************************** import os.path -from PyQt4 import QtCore, QtGui +import enum +from PyQt5.QtCore import QMarginsF, QSizeF, Qt +from PyQt5.QtGui import (QAbstractTextDocumentLayout, QFontMetrics, + QPageLayout, QPageSize, QPainter, QTextDocument) +from PyQt5.QtWidgets import QApplication, QDialog, QFileDialog, QMessageBox +from PyQt5.QtPrintSupport import QPrintDialog, QPrinter import treeoutput import printdialogs import globalref -entireTree, selectBranch, selectNode = range(3) +PrintScope = enum.IntEnum('PrintScope', 'entireTree selectBranch selectNode') +_defaultMargin = 0.5 +_defaultHeaderPos = 0.2 +_defaultColumnSpace = 0.5 class PrintData: @@ -34,10 +42,11 @@ """ self.localControl = localControl self.outputGroup = None - self.printWhat = entireTree + self.printWhat = PrintScope.entireTree self.includeRoot = True self.openOnly = False - self.printer = QtGui.QPrinter(QtGui.QPrinter.HighResolution) + self.printer = QPrinter(QPrinter.HighResolution) + self.pageLayout = self.printer.pageLayout() self.setDefaults() self.adjustSpacing() @@ -47,13 +56,14 @@ self.drawLines = True self.widowControl = True self.indentFactor = 2.0 - self.printer.setPaperSize(QtGui.QPrinter.Letter) - self.printer.setOrientation(QtGui.QPrinter.Portrait) - self.printer.setPageMargins(0.5, 0.5, 0.5, 0.5, QtGui.QPrinter.Inch) - self.headerMargin = 0.2 - self.footerMargin = 0.2 + self.pageLayout.setUnits(QPageLayout.Inch) + self.pageLayout.setPageSize(QPageSize(QPageSize.Letter)) + self.pageLayout.setOrientation(QPageLayout.Portrait) + self.pageLayout.setMargins(QMarginsF(*(_defaultMargin,) * 4)) + self.headerMargin = _defaultHeaderPos + self.footerMargin = _defaultHeaderPos self.numColumns = 1 - self.columnSpacing = 0.5 + self.columnSpacing = _defaultColumnSpace self.headerText = '' self.footerText = '' self.useDefaultFont = True @@ -62,8 +72,8 @@ def setDefaultFont(self): """Set the default font initially and based on an output font change. """ - self.defaultFont = QtGui.QTextDocument().defaultFont() - fontName = globalref.miscOptions.getValue('OutputFont') + self.defaultFont = QTextDocument().defaultFont() + fontName = globalref.miscOptions['OutputFont'] if fontName: self.defaultFont.fromString(fontName) if self.useDefaultFont: @@ -72,119 +82,135 @@ def adjustSpacing(self): """Adjust line spacing & indent size based on font & indent factor. """ - self.lineSpacing = QtGui.QFontMetrics(self.mainFont, - self.printer).lineSpacing() + self.lineSpacing = QFontMetrics(self.mainFont, + self.printer).lineSpacing() self.indentSize = self.indentFactor * self.lineSpacing - def xmlAttr(self): + def fileData(self): """Return a dictionary of non-default settings for storage. """ - attrs = {} + data = {} if not self.drawLines: - attrs['printlines'] = 'n' + data['printlines'] = False if not self.widowControl: - attrs['printwidowcontrol'] = 'n' + data['printwidowcontrol'] = False if self.indentFactor != 2.0: - attrs['printindentfactor'] = repr(self.indentFactor) - if self.printer.paperSize() == QtGui.QPrinter.Custom: + data['printindentfactor'] = self.indentFactor + pageSizeId = self.pageLayout.pageSize().id() + if pageSizeId == QPageSize.Custom: paperWidth, paperHeight = self.roundedPaperSize() - attrs['printpaperwidth'] = repr(paperWidth) - attrs['printpaperheight'] = repr(paperHeight) - elif self.printer.paperSize() != QtGui.QPrinter.Letter: - paperSizeDict = dict((num, attrib) for attrib, num in - vars(QtGui.QPrinter).items() - if isinstance(num, QtGui.QPrinter.PageSize)) - attrs['printpapersize'] = paperSizeDict[self.printer.paperSize()] - if self.printer.orientation() != QtGui.QPrinter.Portrait: - attrs['printportrait'] = 'n' - if self.roundedMargins() != [0.5] * 4: - attrs['printmargins'] = ' '.join([repr(margin) for margin in - self.roundedMargins()]) - if self.headerMargin != 0.2: - attrs['printheadermargin'] = repr(self.headerMargin) - if self.footerMargin != 0.2: - attrs['printfootermargin'] = repr(self.footerMargin) + data['printpaperwidth'] = paperWidth + data['printpaperheight'] = paperHeight + elif pageSizeId != QPageSize.Letter: + data['printpapersize'] = self.paperSizeName(pageSizeId) + if self.pageLayout.orientation() != QPageLayout.Portrait: + data['printportrait'] = False + if self.roundedMargins() != (_defaultMargin,) * 4: + data['printmargins'] = list(self.roundedMargins()) + if self.headerMargin != _defaultHeaderPos: + data['printheadermargin'] = self.headerMargin + if self.footerMargin != _defaultHeaderPos: + data['printfootermargin'] = self.footerMargin if self.numColumns > 1: - attrs['printnumcolumns'] = repr(self.numColumns) - if self.columnSpacing != 0.5: - attrs['printcolumnspace'] = repr(self.columnSpacing) + data['printnumcolumns'] = self.numColumns + if self.columnSpacing != _defaultColumnSpace: + data['printcolumnspace'] = self.columnSpacing if self.headerText: - attrs['printheadertext'] = self.headerText + data['printheadertext'] = self.headerText if self.footerText: - attrs['printfootertext'] = self.footerText + data['printfootertext'] = self.footerText if not self.useDefaultFont: - attrs['printfont'] = self.mainFont.toString() - return attrs + data['printfont'] = self.mainFont.toString() + return data - def restoreXmlAttrs(self, attrs): + def readData(self, data): """Restore saved settings from a dictionary. Arguments: - attrs -- a dictionary of stored non-default settings + data -- a dictionary of stored non-default settings """ self.setDefaults() # necessary for undo/redo - if attrs.get('printlines', '').startswith('n'): - self.drawLines = False - if attrs.get('printwidowcontrol', '').startswith('n'): - self.widowControl = False - if 'printindentfactor' in attrs: - self.indentFactor = float(attrs['printindentfactor']) - if 'printpapersize' in attrs: - self.printer.setPaperSize(getattr(QtGui.QPrinter, - attrs['printpapersize'])) - if 'printpaperwidth' in attrs and 'printpaperheight' in attrs: - width = float(attrs['printpaperwidth']) - height = float(attrs['printpaperheight']) - self.printer.setPaperSize(QtCore.QSizeF(width, height), - QtGui.QPrinter.Inch) - if attrs.get('printportrait', '').startswith('n'): - self.printer.setOrientation(QtGui.QPrinter.Landscape) - if 'printmargins' in attrs: - margins = [float(margin) for margin in - attrs['printmargins'].split()] - margins.append(QtGui.QPrinter.Inch) - self.printer.setPageMargins(*margins) - if 'printheadermargin' in attrs: - self.headerMargin = float(attrs['printheadermargin']) - if 'printfootermargin' in attrs: - self.footerMargin = float(attrs['printfootermargin']) - if 'printnumcolumns' in attrs: - self.numColumns = int(attrs['printnumcolumns']) - if 'printcolumnspace' in attrs: - self.columnSpacing = float(attrs['printcolumnspace']) - self.headerText = attrs.get('printheadertext', '') - self.footerText = attrs.get('printfootertext', '') - if 'printfont' in attrs: + self.drawLines = data.get('printlines', True) + self.widowControl = data.get('printwidowcontrol', True) + self.indentFactor = data.get('printindentfactor', 2.0) + if 'printpapersize' in data: + self.pageLayout.setPageSize(QPageSize(getattr(QPageSize, + data['printpapersize']))) + self.pageLayout.setMargins(QMarginsF(*(_defaultMargin,) * 4)) + if 'printpaperwidth' in data and 'printpaperheight' in data: + width = data['printpaperwidth'] + height = data['printpaperheight'] + self.pageLayout.setPageSize(QPageSize(QSizeF(width, height), + QPageSize.Inch)) + self.pageLayout.setMargins(QMarginsF(*(_defaultMargin,) * 4)) + if not data.get('printportrait', True): + self.pageLayout.setOrientation(QPageLayout.Landscape) + if 'printmargins' in data: + margins = data['printmargins'] + self.pageLayout.setMargins(QMarginsF(*margins)) + self.headerMargin = data.get('printheadermargin', _defaultHeaderPos) + self.footerMargin = data.get('printfootermargin', _defaultHeaderPos) + self.numColumns = data.get('printnumcolumns', 1) + self.columnSpacing = data.get('printcolumnspace', _defaultColumnSpace) + self.headerText = data.get('printheadertext', '') + self.footerText = data.get('printfootertext', '') + if 'printfont' in data: self.useDefaultFont = False - self.mainFont.fromString(attrs['printfont']) + self.mainFont.fromString(data['printfont']) self.adjustSpacing() def roundedMargins(self): - """Return a list of rounded page margins in inches. + """Return a tuple of rounded page margins in inches. Rounds to nearest .01" to avoid Qt unit conversion artifacts. """ - return [round(margin, 2) for margin in - self.printer.getPageMargins(QtGui.QPrinter.Inch)] + margins = self.pageLayout.margins(QPageLayout.Inch) + return tuple(round(margin, 2) for margin in + (margins.left(), margins.top(), margins.right(), + margins.bottom())) def roundedPaperSize(self): """Return a tuple of rounded paper width and height. Rounds to nearest .01" to avoid Qt unit conversion artifacts. """ - size = self.printer.paperSize(QtGui.QPrinter.Inch) + size = self.pageLayout.fullRect(QPageLayout.Inch) return (round(size.width(), 2), round(size.height(), 2)) + def paperSizeName(self, sizeId=None): + """Return a QPageSize attribute name matching the paper size ID. + + Arguments: + sizeId -- the Qt size ID, if None, use current size + """ + if sizeId == None: + sizeId = self.pageLayout.pageSize().id() + matches = [] + for name, num in vars(QPageSize).items(): + if num == sizeId: + matches.append(name) + if not matches: + return 'Custom' + if len(matches) > 1: + text = QPageSize(sizeId).name().split(None, 1)[0] + for name in matches: + if name == text: + return name + return matches[0] + def setupData(self): """Load data to be printed and set page info. """ - if self.printWhat == entireTree: - selNodes = [self.localControl.model.root] + if self.printWhat == PrintScope.entireTree: + selSpots = self.localControl.structure.rootSpots() else: - selNodes = (self.localControl.currentSelectionModel(). - selectedNodes()) - self.outputGroup = treeoutput.OutputGroup(selNodes, self.includeRoot, - self.printWhat != selectNode, + selSpots = (self.localControl.currentSelectionModel(). + selectedSpots()) + if not selSpots: + selSpots = self.localControl.structure.rootSpots() + self.outputGroup = treeoutput.OutputGroup(selSpots, self.includeRoot, + self.printWhat != + PrintScope.selectNode, self.openOnly) self.paginate() @@ -194,9 +220,12 @@ pageNum = 1 columnNum = 0 pagePos = 0 - heightAvail = self.printer.pageRect().height() + self.checkPageLayout() + heightAvail = (self.pageLayout.paintRect().height() * + self.printer.logicalDpiY()) columnSpacing = int(self.columnSpacing * self.printer.logicalDpiX()) - widthAvail = ((self.printer.pageRect().width() - + widthAvail = ((self.pageLayout.paintRect().width() * + self.printer.logicalDpiX() - columnSpacing * (self.numColumns - 1)) // self.numColumns) newGroup = treeoutput.OutputGroup([]) @@ -234,7 +263,7 @@ self.mainFont) if newItem: self.outputGroup.insert(0, newItem) - if item and pagePos + item.height <= heightAvail: + if item and (pagePos + item.height <= heightAvail or pagePos == 0): item.pageNum = pageNum item.columnNum = columnNum item.pagePos = pagePos @@ -270,23 +299,72 @@ self.outputGroup.loadFamilyRefs() self.printer.setFromTo(1, pageNum) + def checkPageLayout(self): + """Check and set the page layout on the current printer. + + Verify that the layout settings match the printer, adjust if required. + """ + if not self.printer.setPageLayout(self.pageLayout): + tempPrinter = QPrinter() + tempPageLayout = tempPrinter.pageLayout() + tempPageLayout.setUnits(QPageLayout.Inch) + pageSizeIssue = False + defaultPageSize = tempPageLayout.pageSize() + tempPageLayout.setPageSize(self.pageLayout.pageSize()) + if not tempPrinter.setPageLayout(tempPageLayout): + pageSizeIssue = True + tempPageLayout.setPageSize(defaultPageSize) + marginIssue = not (tempPageLayout.setMargins(self.pageLayout. + margins()) and + tempPrinter.setPageLayout(tempPageLayout)) + if marginIssue: + margin = 0.1 + while True: + if (tempPageLayout.setMargins(QMarginsF(*(margin,) * 4)) + and tempPrinter.setPageLayout(tempPageLayout)): + break + margin += 0.1 + newMargins = [] + for oldMargin in self.roundedMargins(): + newMargins.append(oldMargin if oldMargin >= margin else + margin) + tempPageLayout.setMargins(QMarginsF(*newMargins)) + tempPageLayout.setOrientation(self.pageLayout.orientation()) + self.printer.setPageLayout(tempPageLayout) + if not pageSizeIssue and not marginIssue: + return + if pageSizeIssue and marginIssue: + msg = _('Warning: Page size and margin settings unsupported ' + 'on current printer.\nSave page adjustments?') + elif pageSizeIssue: + msg = _('Warning: Page size setting unsupported ' + 'on current printer.\nSave adjustment?') + else: + msg = _('Warning: Margin settings unsupported ' + 'on current printer.\nSave adjustments?') + ans = QMessageBox.warning(QApplication.activeWindow(), 'TreeLine', + msg, QMessageBox.Yes | QMessageBox.No, + QMessageBox.Yes) + if ans == QMessageBox.Yes: + self.pageLayout = tempPageLayout + def paintData(self, printer): """Paint data to be printed to the printer. """ pageNum = 1 maxPageNum = self.outputGroup[-1].pageNum - if self.printer.printRange() != QtGui.QPrinter.AllPages: + if self.printer.printRange() != QPrinter.AllPages: pageNum = self.printer.fromPage() maxPageNum = self.printer.toPage() - painter = QtGui.QPainter() + painter = QPainter() if not painter.begin(self.printer): - QtGui.QMessageBox.warning(QtGui.QApplication.activeWindow(), - 'TreeLine', _('Error initializing printer')) - QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) + QMessageBox.warning(QApplication.activeWindow(), + 'TreeLine', _('Error initializing printer')) + QApplication.setOverrideCursor(Qt.WaitCursor) while True: self.paintPage(pageNum, painter) if pageNum == maxPageNum: - QtGui.QApplication.restoreOverrideCursor() + QApplication.restoreOverrideCursor() return pageNum += 1 self.printer.newPage() @@ -303,18 +381,20 @@ if headerDoc: layout = headerDoc.documentLayout() layout.setPaintDevice(self.printer) - headerDoc.setTextWidth(self.printer.pageRect().width()) + headerDoc.setTextWidth(self.pageLayout.paintRect().width() * + self.printer.logicalDpiX()) painter.save() - topMargin = self.printer.getPageMargins(QtGui.QPrinter.Inch)[1] + topMargin = self.pageLayout.margins(QPageLayout.Inch).top() headerDelta = ((self.headerMargin - topMargin) * self.printer.logicalDpiX()) painter.translate(0, int(headerDelta)) layout.draw(painter, - QtGui.QAbstractTextDocumentLayout.PaintContext()) + QAbstractTextDocumentLayout.PaintContext()) painter.restore() painter.save() columnSpacing = int(self.columnSpacing * self.printer.logicalDpiX()) - columnDelta = ((self.printer.pageRect().width() - + columnDelta = ((self.pageLayout.paintRect().width() * + self.printer.logicalDpiX() - columnSpacing * (self.numColumns - 1)) / self.numColumns) + columnSpacing for columnNum in range(self.numColumns): @@ -326,15 +406,17 @@ if footerDoc: layout = footerDoc.documentLayout() layout.setPaintDevice(self.printer) - footerDoc.setTextWidth(self.printer.pageRect().width()) + footerDoc.setTextWidth(self.pageLayout.paintRect().width() * + self.printer.logicalDpiX()) painter.save() - bottomMargin = self.printer.getPageMargins(QtGui.QPrinter.Inch)[3] + bottomMargin = self.pageLayout.margins(QPageLayout.Inch).bottom() footerDelta = ((bottomMargin - self.footerMargin) * self.printer.logicalDpiX()) - painter.translate(0, self.printer.pageRect().height() + - int(footerDelta) - self.lineSpacing) + painter.translate(0, self.pageLayout.paintRect().height() * + self.printer.logicalDpiX() + + int(footerDelta) - self.lineSpacing) layout.draw(painter, - QtGui.QAbstractTextDocumentLayout.PaintContext()) + QAbstractTextDocumentLayout.PaintContext()) painter.restore() def paintColumn(self, pageNum, columnNum, painter): @@ -352,7 +434,7 @@ painter.save() painter.translate(item.level * self.indentSize, item.pagePos) layout.draw(painter, - QtGui.QAbstractTextDocumentLayout.PaintContext()) + QAbstractTextDocumentLayout.PaintContext()) painter.restore() if self.drawLines: self.addPrintLines(pageNum, columnNum, columnItems, painter) @@ -369,7 +451,8 @@ parentsDrawn = set() horizOffset = self.indentSize // 2 vertOffset = self.lineSpacing // 2 - heightAvail = self.printer.pageRect().height() + heightAvail = (self.pageLayout.paintRect().height() * + self.printer.logicalDpiY()) for item in columnItems: if item.level > 0: indent = item.level * self.indentSize @@ -411,9 +494,10 @@ textParts = printdialogs.splitHeaderFooter(self.footerText) if not textParts: return '' - fileInfoFormat = self.localControl.model.formats.fileInfoFormat - fileInfoNode = self.localControl.model.fileInfoNode - fileInfoFormat.updateFileInfo(self.localControl.filePath, fileInfoNode) + fileInfoFormat = self.localControl.structure.treeFormats.fileInfoFormat + fileInfoNode = self.localControl.structure.fileInfoNode + fileInfoFormat.updateFileInfo(self.localControl.filePathObj, + fileInfoNode) fileInfoNode.data[fileInfoFormat.pageNumFieldName] = repr(pageNum) fileInfoNode.data[fileInfoFormat.numPagesFieldName] = repr(numPages) fileInfoFormat.changeOutputLines(textParts, keepBlanks=True) @@ -437,7 +521,7 @@ """ text = self.formatHeaderFooter(header, pageNum, numPages) if text: - doc = QtGui.QTextDocument() + doc = QTextDocument() doc.setHtml(text) doc.setDefaultFont(self.mainFont) frameFormat = doc.rootFrame().frameFormat() @@ -451,8 +535,7 @@ def printSetup(self): """Show a dialog to set margins, page size and other printing options. """ - setupDialog = printdialogs.PrintSetupDialog(self, True, - QtGui.QApplication. + setupDialog = printdialogs.PrintSetupDialog(self, True, QApplication. activeWindow()) setupDialog.exec_() @@ -460,21 +543,20 @@ """Show a preview of printing results. """ self.setupData() - previewDialog = printdialogs.PrintPreviewDialog(self, - QtGui.QApplication. + previewDialog = printdialogs.PrintPreviewDialog(self,QApplication. activeWindow()) previewDialog.previewWidget.paintRequested.connect(self.paintData) - if globalref.genOptions.getValue('SaveWindowGeom'): + if globalref.genOptions['SaveWindowGeom']: previewDialog.restoreDialogGeom() previewDialog.exec_() def filePrint(self): """Show dialog and print tree output based on current options. """ + self.printer.setOutputFormat(QPrinter.NativeFormat) self.setupData() - printDialog = QtGui.QPrintDialog(self.printer, - QtGui.QApplication.activeWindow()) - if printDialog.exec_() == QtGui.QDialog.Accepted: + printDialog = QPrintDialog(self.printer, QApplication.activeWindow()) + if printDialog.exec_() == QDialog.Accepted: self.paintData(self.printer) def filePrintPdf(self): @@ -482,11 +564,11 @@ """ filters = ';;'.join((globalref.fileFilters['pdf'], globalref.fileFilters['all'])) - defaultFilePath = globalref.mainControl.defaultFilePath() + defaultFilePath = str(globalref.mainControl.defaultPathObj()) defaultFilePath = os.path.splitext(defaultFilePath)[0] if os.path.basename(defaultFilePath): defaultFilePath = '{0}.{1}'.format(defaultFilePath, 'pdf') - filePath = QtGui.QFileDialog.getSaveFileName(QtGui.QApplication. + filePath, selectFilter = QFileDialog.getSaveFileName(QApplication. activeWindow(), _('TreeLine - Export PDF'), defaultFilePath, filters) @@ -494,11 +576,12 @@ return if not os.path.splitext(filePath)[1]: filePath = '{0}.{1}'.format(filePath, 'pdf') - self.printer.setOutputFormat(QtGui.QPrinter.PdfFormat) + origFormat = self.printer.outputFormat() + self.printer.setOutputFormat(QPrinter.PdfFormat) self.printer.setOutputFileName(filePath) self.adjustSpacing() self.setupData() self.paintData(self.printer) - self.printer.setOutputFormat(QtGui.QPrinter.NativeFormat) + self.printer.setOutputFormat(origFormat) self.printer.setOutputFileName('') self.adjustSpacing() diff -Nru treeline-2.0.2/source/printdialogs.py treeline-3.0.1/source/printdialogs.py --- treeline-2.0.2/source/printdialogs.py 2015-02-27 20:57:24.000000000 +0000 +++ treeline-3.0.1/source/printdialogs.py 2018-08-16 00:55:25.000000000 +0000 @@ -4,7 +4,7 @@ # printdialogs.py, provides print preview and print settings dialogs # # TreeLine, an information storage program -# Copyright (C) 2015, Douglas W. Bell +# Copyright (C) 2018, 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 @@ -14,7 +14,17 @@ import re import collections -from PyQt4 import QtCore, QtGui +from PyQt5.QtCore import (QMarginsF, QPoint, QRect, QSize, QSizeF, Qt, + pyqtSignal) +from PyQt5.QtGui import (QFontDatabase, QFontInfo, QFontMetrics, QIntValidator, + QPageLayout, QPageSize) +from PyQt5.QtWidgets import (QAbstractItemView, QAction, QButtonGroup, + QCheckBox, QComboBox, QDialog, QDoubleSpinBox, + QGridLayout, QGroupBox, QHBoxLayout, QLabel, + QLineEdit, QListWidget, QMenu, QMessageBox, + QPushButton, QRadioButton, QSpinBox, QTabWidget, + QToolBar, QVBoxLayout, QWidget) +from PyQt5.QtPrintSupport import (QPrintPreviewWidget, QPrinter, QPrinterInfo) import printdata import configdialog import treeformats @@ -22,7 +32,7 @@ import globalref -class PrintPreviewDialog(QtGui.QDialog): +class PrintPreviewDialog(QDialog): """Dialog for print previews. Similar to QPrintPreviewDialog but calls a custom page setup dialog. @@ -35,28 +45,28 @@ parent -- the parent window """ super().__init__(parent) - self.setWindowFlags(QtCore.Qt.Dialog | QtCore.Qt.WindowTitleHint | - QtCore.Qt.WindowSystemMenuHint) + self.setWindowFlags(Qt.Dialog | Qt.WindowTitleHint | + Qt.WindowCloseButtonHint) self.setWindowTitle(_('Print Preview')) self.printData = printData - topLayout = QtGui.QVBoxLayout(self) + topLayout = QVBoxLayout(self) self.setLayout(topLayout) - toolBar = QtGui.QToolBar(self) + toolBar = QToolBar(self) topLayout.addWidget(toolBar) - self.previewWidget = QtGui.QPrintPreviewWidget(printData.printer, self) + self.previewWidget = QPrintPreviewWidget(printData.printer, self) topLayout.addWidget(self.previewWidget) self.previewWidget.previewChanged.connect(self.updateControls) - self.zoomWidthAct = QtGui.QAction(_('Fit Width'), self, checkable=True) + self.zoomWidthAct = QAction(_('Fit Width'), self, checkable=True) icon = globalref.toolIcons.getIcon('printpreviewzoomwidth') if icon: self.zoomWidthAct.setIcon(icon) self.zoomWidthAct.triggered.connect(self.zoomWidth) toolBar.addAction(self.zoomWidthAct) - self.zoomAllAct = QtGui.QAction(_('Fit Page'), self, checkable=True) + self.zoomAllAct = QAction(_('Fit Page'), self, checkable=True) icon = globalref.toolIcons.getIcon('printpreviewzoomall') if icon: self.zoomAllAct.setIcon(icon) @@ -64,23 +74,23 @@ toolBar.addAction(self.zoomAllAct) toolBar.addSeparator() - self.zoomCombo = QtGui.QComboBox(self) + self.zoomCombo = QComboBox(self) self.zoomCombo.setEditable(True) - self.zoomCombo.setInsertPolicy(QtGui.QComboBox.NoInsert) + self.zoomCombo.setInsertPolicy(QComboBox.NoInsert) self.zoomCombo.addItems([' 12%', ' 25%', ' 50%', ' 75%', ' 100%', ' 125%', ' 150%', ' 200%', ' 400%', ' 800%']) self.zoomCombo.currentIndexChanged[str].connect(self.zoomToValue) self.zoomCombo.lineEdit().returnPressed.connect(self.zoomToValue) toolBar.addWidget(self.zoomCombo) - zoomInAct = QtGui.QAction(_('Zoom In'), self) + zoomInAct = QAction(_('Zoom In'), self) icon = globalref.toolIcons.getIcon('printpreviewzoomin') if icon: zoomInAct.setIcon(icon) zoomInAct.triggered.connect(self.zoomIn) toolBar.addAction(zoomInAct) - zoomOutAct = QtGui.QAction(_('Zoom Out'), self) + zoomOutAct = QAction(_('Zoom Out'), self) icon = globalref.toolIcons.getIcon('printpreviewzoomout') if icon: zoomOutAct.setIcon(icon) @@ -88,25 +98,25 @@ toolBar.addAction(zoomOutAct) toolBar.addSeparator() - self.previousAct = QtGui.QAction(_('Previous Page'), self) + self.previousAct = QAction(_('Previous Page'), self) icon = globalref.toolIcons.getIcon('printpreviewprevious') if icon: self.previousAct.setIcon(icon) self.previousAct.triggered.connect(self.previousPage) toolBar.addAction(self.previousAct) - self.pageNumEdit = QtGui.QLineEdit(self) - self.pageNumEdit.setAlignment(QtCore.Qt.AlignRight | - QtCore.Qt.AlignVCenter) - width = QtGui.QFontMetrics(self.pageNumEdit.font()).width('0000') + self.pageNumEdit = QLineEdit(self) + self.pageNumEdit.setAlignment(Qt.AlignRight | + Qt.AlignVCenter) + width = QFontMetrics(self.pageNumEdit.font()).width('0000') self.pageNumEdit.setMaximumWidth(width) self.pageNumEdit.returnPressed.connect(self.setPageNum) toolBar.addWidget(self.pageNumEdit) - self.maxPageLabel = QtGui.QLabel(' / 000 ', self) + self.maxPageLabel = QLabel(' / 000 ', self) toolBar.addWidget(self.maxPageLabel) - self.nextAct = QtGui.QAction(_('Next Page'), self) + self.nextAct = QAction(_('Next Page'), self) icon = globalref.toolIcons.getIcon('printpreviewnext') if icon: self.nextAct.setIcon(icon) @@ -114,7 +124,7 @@ toolBar.addAction(self.nextAct) toolBar.addSeparator() - self.onePageAct = QtGui.QAction(_('Single Page'), self, checkable=True) + self.onePageAct = QAction(_('Single Page'), self, checkable=True) icon = globalref.toolIcons.getIcon('printpreviewsingle') if icon: self.onePageAct.setIcon(icon) @@ -122,7 +132,7 @@ setSinglePageViewMode) toolBar.addAction(self.onePageAct) - self.twoPageAct = QtGui.QAction(_('Facing Pages'), self, + self.twoPageAct = QAction(_('Facing Pages'), self, checkable=True) icon = globalref.toolIcons.getIcon('printpreviewdouble') if icon: @@ -132,14 +142,14 @@ toolBar.addAction(self.twoPageAct) toolBar.addSeparator() - pageSetupAct = QtGui.QAction(_('Print Setup'), self) + pageSetupAct = QAction(_('Print Setup'), self) icon = globalref.toolIcons.getIcon('fileprintsetup') if icon: pageSetupAct.setIcon(icon) pageSetupAct.triggered.connect(self.printSetup) toolBar.addAction(pageSetupAct) - filePrintAct = QtGui.QAction(_('Print'), self) + filePrintAct = QAction(_('Print'), self) icon = globalref.toolIcons.getIcon('fileprint') if icon: filePrintAct.setIcon(icon) @@ -150,9 +160,9 @@ """Update control availability and status based on a change signal. """ self.zoomWidthAct.setChecked(self.previewWidget.zoomMode() == - QtGui.QPrintPreviewWidget.FitToWidth) + QPrintPreviewWidget.FitToWidth) self.zoomAllAct.setChecked(self.previewWidget.zoomMode() == - QtGui.QPrintPreviewWidget.FitInView) + QPrintPreviewWidget.FitInView) zoom = self.previewWidget.zoomFactor() * 100 self.zoomCombo.setEditText('{0:4.0f}%'.format(zoom)) self.previousAct.setEnabled(self.previewWidget.currentPage() > 1) @@ -162,9 +172,9 @@ self.maxPageLabel.setText(' / {0} '.format(self.previewWidget. pageCount())) self.onePageAct.setChecked(self.previewWidget.viewMode() == - QtGui.QPrintPreviewWidget.SinglePageView) + QPrintPreviewWidget.SinglePageView) self.twoPageAct.setChecked(self.previewWidget.viewMode() == - QtGui.QPrintPreviewWidget.FacingPagesView) + QPrintPreviewWidget.FacingPagesView) def zoomWidth(self, checked=True): """Set the fit to width zoom mode if checked. @@ -173,10 +183,10 @@ checked -- set this mode if True """ if checked: - self.previewWidget.setZoomMode(QtGui.QPrintPreviewWidget. + self.previewWidget.setZoomMode(QPrintPreviewWidget. FitToWidth) else: - self.previewWidget.setZoomMode(QtGui.QPrintPreviewWidget. + self.previewWidget.setZoomMode(QPrintPreviewWidget. CustomZoom) self.updateControls() @@ -187,9 +197,9 @@ checked -- set this mode if True """ if checked: - self.previewWidget.setZoomMode(QtGui.QPrintPreviewWidget.FitInView) + self.previewWidget.setZoomMode(QPrintPreviewWidget.FitInView) else: - self.previewWidget.setZoomMode(QtGui.QPrintPreviewWidget. + self.previewWidget.setZoomMode(QPrintPreviewWidget. CustomZoom) self.updateControls() @@ -245,7 +255,7 @@ """Show a dialog to set margins, page size and other printing options. """ setupDialog = PrintSetupDialog(self.printData, False, self) - if setupDialog.exec_() == QtGui.QDialog.Accepted: + if setupDialog.exec_() == QDialog.Accepted: self.printData.setupData() self.previewWidget.updatePreview() @@ -253,7 +263,10 @@ """Show dialog and print tree output based on current options. """ self.close() - self.printData.filePrint() + if self.printData.printer.outputFormat() == QPrinter.NativeFormat: + self.printData.filePrint() + else: + self.printData.filePrintPdf() def sizeHint(self): """Return a larger default height. @@ -265,10 +278,10 @@ def restoreDialogGeom(self): """Restore dialog window geometry from history options. """ - rect = QtCore.QRect(globalref.histOptions.getValue('PrintPrevXPos'), - globalref.histOptions.getValue('PrintPrevYPos'), - globalref.histOptions.getValue('PrintPrevXSize'), - globalref.histOptions.getValue('PrintPrevYSize')) + rect = QRect(globalref.histOptions['PrintPrevXPos'], + globalref.histOptions['PrintPrevYPos'], + globalref.histOptions['PrintPrevXSize'], + globalref.histOptions['PrintPrevYSize']) if rect.height() and rect.width(): self.setGeometry(rect) @@ -286,11 +299,11 @@ Arguments: event -- the close event """ - if globalref.genOptions.getValue('SaveWindowGeom'): + if globalref.genOptions['SaveWindowGeom']: self.saveDialogGeom() -class PrintSetupDialog(QtGui.QDialog): +class PrintSetupDialog(QDialog): """Base dialog for setting the print configuration. Pushes most options to the PrintData class. @@ -304,73 +317,90 @@ parent -- the parent window """ super().__init__(parent) - self.setWindowFlags(QtCore.Qt.Dialog | QtCore.Qt.WindowTitleHint | - QtCore.Qt.WindowSystemMenuHint) + self.setWindowFlags(Qt.Dialog | Qt.WindowTitleHint | + Qt.WindowCloseButtonHint) self.setWindowTitle(_('Printing Setup')) self.printData = printData - topLayout = QtGui.QVBoxLayout(self) + topLayout = QVBoxLayout(self) self.setLayout(topLayout) - tabs = QtGui.QTabWidget() + tabs = QTabWidget() topLayout.addWidget(tabs) generalPage = GeneralPage(self.printData) tabs.addTab(generalPage, _('&General Options')) - pageSetupPage = PageSetupPage(self.printData) + pageSetupPage = PageSetupPage(self.printData, + generalPage.currentPrinterName) tabs.addTab(pageSetupPage, _('Page &Setup')) fontPage = FontPage(self.printData) tabs.addTab(fontPage, _('&Font Selection')) headerPage = HeaderPage(self.printData) tabs.addTab(headerPage, _('&Header/Footer')) + generalPage.printerChanged.connect(pageSetupPage.changePrinter) self.tabPages = [generalPage, pageSetupPage, fontPage, headerPage] - ctrlLayout = QtGui.QHBoxLayout() + ctrlLayout = QHBoxLayout() topLayout.addLayout(ctrlLayout) ctrlLayout.addStretch() if showExtraButtons: - previewButton = QtGui.QPushButton(_('Print Pre&view...')) + previewButton = QPushButton(_('Print Pre&view...')) ctrlLayout.addWidget(previewButton) previewButton.clicked.connect(self.preview) - printButton = QtGui.QPushButton(_('&Print...')) + printButton = QPushButton(_('&Print...')) ctrlLayout.addWidget(printButton) printButton.clicked.connect(self.quickPrint) - okButton = QtGui.QPushButton(_('&OK')) + okButton = QPushButton(_('&OK')) ctrlLayout.addWidget(okButton) okButton.clicked.connect(self.accept) - cancelButton = QtGui.QPushButton(_('&Cancel')) + cancelButton = QPushButton(_('&Cancel')) ctrlLayout.addWidget(cancelButton) cancelButton.clicked.connect(self.reject) def quickPrint(self): - """Accept this dialog and go to print dialog""" + """Accept this dialog and go to print dialog. + """ self.accept() - self.printData.filePrint() + if self.printData.printer.outputFormat() == QPrinter.NativeFormat: + self.printData.filePrint() + else: + self.printData.filePrintPdf() def preview(self): - """Accept this dialog and go to print preview dialog""" + """Accept this dialog and go to print preview dialog. + """ self.accept() self.printData.printPreview() def accept(self): - """Store results before closing dialog""" + """Store results before closing dialog. + """ + if not self.tabPages[1].checkValid(): + QMessageBox.warning(self, 'TreeLine', + _('Error: Page size or margins are invalid')) + return changed = False - undoObj = undo.StateSettingUndo(self.printData.localControl.model. - undoList, self.printData.xmlAttr, - self.printData.restoreXmlAttrs) + control = self.printData.localControl + undoObj = undo.StateSettingUndo(control.structure.undoList, + self.printData.fileData, + self.printData.readData) for page in self.tabPages: if page.saveChanges(): changed = True if changed: self.printData.adjustSpacing() - self.printData.localControl.setModified() + control.setModified() else: - self.printData.localControl.model.undoList.removeLastUndo(undoObj) + control.structure.undoList.removeLastUndo(undoObj) super().accept() -class GeneralPage(QtGui.QWidget): +_pdfPrinterName = _('TreeLine PDF Printer') + + +class GeneralPage(QWidget): """Dialog page for misc. print options. """ + printerChanged = pyqtSignal(str) def __init__(self, printData, parent=None): """Create the general settings page. @@ -380,65 +410,88 @@ """ super().__init__(parent) self.printData = printData - topLayout = QtGui.QGridLayout(self) + self.printerList = QPrinterInfo.availablePrinterNames() + self.printerList.insert(0, _pdfPrinterName) + self.currentPrinterName = self.printData.printer.printerName() + if not self.currentPrinterName: + self.currentPrinterName = _pdfPrinterName + + topLayout = QHBoxLayout(self) self.setLayout(topLayout) + leftLayout = QVBoxLayout() + topLayout.addLayout(leftLayout) - whatGroupBox = QtGui.QGroupBox(_('What to print')) - topLayout.addWidget(whatGroupBox, 0, 0) - whatLayout = QtGui.QVBoxLayout(whatGroupBox) - self.whatButtons = QtGui.QButtonGroup(self) - treeButton = QtGui.QRadioButton(_('&Entire tree')) - self.whatButtons.addButton(treeButton, printdata.entireTree) + whatGroupBox = QGroupBox(_('What to print')) + leftLayout.addWidget(whatGroupBox) + whatLayout = QVBoxLayout(whatGroupBox) + self.whatButtons = QButtonGroup(self) + treeButton = QRadioButton(_('&Entire tree')) + self.whatButtons.addButton(treeButton, printdata.PrintScope.entireTree) whatLayout.addWidget(treeButton) - branchButton = QtGui.QRadioButton(_('Selected &branches')) - self.whatButtons.addButton(branchButton, printdata.selectBranch) + branchButton = QRadioButton(_('Selected &branches')) + self.whatButtons.addButton(branchButton, + printdata.PrintScope.selectBranch) whatLayout.addWidget(branchButton) - nodeButton = QtGui.QRadioButton(_('Selected &nodes')) - self.whatButtons.addButton(nodeButton, printdata.selectNode) + nodeButton = QRadioButton(_('Selected &nodes')) + self.whatButtons.addButton(nodeButton, printdata.PrintScope.selectNode) whatLayout.addWidget(nodeButton) self.whatButtons.button(self.printData.printWhat).setChecked(True) self.whatButtons.buttonClicked.connect(self.updateCmdAvail) - includeBox = QtGui.QGroupBox(_('Included Nodes')) - topLayout.addWidget(includeBox, 1, 0) - includeLayout = QtGui.QVBoxLayout(includeBox) - self.rootButton = QtGui.QCheckBox(_('&Include root node')) + includeBox = QGroupBox(_('Included Nodes')) + leftLayout.addWidget(includeBox) + includeLayout = QVBoxLayout(includeBox) + self.rootButton = QCheckBox(_('&Include root node')) includeLayout.addWidget(self.rootButton) self.rootButton.setChecked(self.printData.includeRoot) - self.openOnlyButton = QtGui.QCheckBox(_('Onl&y open node children')) + self.openOnlyButton = QCheckBox(_('Onl&y open node children')) includeLayout.addWidget(self.openOnlyButton) self.openOnlyButton.setChecked(self.printData.openOnly) + leftLayout.addStretch() + + rightLayout = QVBoxLayout() + topLayout.addLayout(rightLayout) - featureBox = QtGui.QGroupBox(_('Features')) - topLayout.addWidget(featureBox, 0, 1) - featureLayout = QtGui.QVBoxLayout(featureBox) - self.linesButton = QtGui.QCheckBox(_('&Draw lines to children')) + printerBox = QGroupBox(_('Select &Printer')) + rightLayout.addWidget(printerBox) + printerLayout = QVBoxLayout(printerBox) + printerCombo = QComboBox() + printerLayout.addWidget(printerCombo) + printerCombo.addItems(self.printerList) + printerCombo.setCurrentIndex(self.printerList.index(self. + currentPrinterName)) + printerCombo.currentIndexChanged.connect(self.changePrinter) + + featureBox = QGroupBox(_('Features')) + rightLayout.addWidget(featureBox) + featureLayout = QVBoxLayout(featureBox) + self.linesButton = QCheckBox(_('&Draw lines to children')) featureLayout.addWidget(self.linesButton) self.linesButton.setChecked(self.printData.drawLines) - self.widowButton = QtGui.QCheckBox(_('&Keep first child with parent')) + self.widowButton = QCheckBox(_('&Keep first child with parent')) featureLayout.addWidget(self.widowButton) self.widowButton.setChecked(self.printData.widowControl) - indentBox = QtGui.QGroupBox(_('Indent')) - topLayout.addWidget(indentBox, 1, 1) - indentLayout = QtGui.QHBoxLayout(indentBox) - indentLabel = QtGui.QLabel(_('Indent Offse&t\n(line height units)')) + indentBox = QGroupBox(_('Indent')) + rightLayout.addWidget(indentBox) + indentLayout = QHBoxLayout(indentBox) + indentLabel = QLabel(_('Indent Offse&t\n(line height units)')) indentLayout.addWidget(indentLabel) - self.indentSpin = QtGui.QDoubleSpinBox() + self.indentSpin = QDoubleSpinBox() indentLayout.addWidget(self.indentSpin) indentLabel.setBuddy(self.indentSpin) self.indentSpin.setMinimum(0.5) self.indentSpin.setSingleStep(0.5) self.indentSpin.setDecimals(1) self.indentSpin.setValue(self.printData.indentFactor) + rightLayout.addStretch() - topLayout.setRowStretch(2, 1) self.updateCmdAvail() def updateCmdAvail(self): """Update options available based on print what settings. """ - if self.whatButtons.checkedId() == printdata.selectNode: + if self.whatButtons.checkedId() == printdata.PrintScope.selectNode: self.rootButton.setChecked(True) self.rootButton.setEnabled(False) self.openOnlyButton.setChecked(False) @@ -447,6 +500,15 @@ self.rootButton.setEnabled(True) self.openOnlyButton.setEnabled(True) + def changePrinter(self, printerNum): + """Change the current printer based on a combo box signal. + + Arguments: + printerNum -- the printer number from the combo box + """ + self.currentPrinterName = self.printerList[printerNum] + self.printerChanged.emit(self.currentPrinterName) + def saveChanges(self): """Update print data with current dialog settings. @@ -455,6 +517,10 @@ self.printData.printWhat = self.whatButtons.checkedId() self.printData.includeRoot = self.rootButton.isChecked() self.printData.openOnly = self.openOnlyButton.isChecked() + if self.currentPrinterName != _pdfPrinterName: + self.printData.printer.setPrinterName(self.currentPrinterName) + else: + self.printData.printer.setPrinterName('') changed = False if self.printData.drawLines != self.linesButton.isChecked(): self.printData.drawLines = self.linesButton.isChecked() @@ -481,62 +547,61 @@ _unitValues = {'in': 1.0, 'cm': 2.54, 'mm': 25.4} _unitDecimals = {'in': 2, 'cm': 1, 'mm': 0} -class PageSetupPage(QtGui.QWidget): +class PageSetupPage(QWidget): """Dialog page for page setup options. """ - def __init__(self, printData, parent=None): + def __init__(self, printData, currentPrinterName, parent=None): """Create the page setup settings page. Arguments: printData -- a reference to the PrintData class + currentPrinterName -- the selected printer for validation parent -- the parent dialog """ super().__init__(parent) self.printData = printData - topLayout = QtGui.QHBoxLayout(self) + self.currentPrinterName = currentPrinterName + + topLayout = QHBoxLayout(self) self.setLayout(topLayout) - leftLayout = QtGui.QVBoxLayout() + leftLayout = QVBoxLayout() topLayout.addLayout(leftLayout) - unitsBox = QtGui.QGroupBox(_('&Units')) + unitsBox = QGroupBox(_('&Units')) leftLayout.addWidget(unitsBox) - unitsLayout = QtGui.QVBoxLayout(unitsBox) - unitsCombo = QtGui.QComboBox() + unitsLayout = QVBoxLayout(unitsBox) + unitsCombo = QComboBox() unitsLayout.addWidget(unitsCombo) unitsCombo.addItems(list(_units.values())) - self.currentUnit = globalref.miscOptions.getValue('PrintUnits') + self.currentUnit = globalref.miscOptions['PrintUnits'] if self.currentUnit not in _units: self.currentUnit = 'in' unitsCombo.setCurrentIndex(list(_units.keys()).index(self.currentUnit)) unitsCombo.currentIndexChanged.connect(self.changeUnits) - paperSizeBox = QtGui.QGroupBox(_('Paper &Size')) + paperSizeBox = QGroupBox(_('Paper &Size')) leftLayout.addWidget(paperSizeBox) - paperSizeLayout = QtGui.QGridLayout(paperSizeBox) + paperSizeLayout = QGridLayout(paperSizeBox) spacing = paperSizeLayout.spacing() paperSizeLayout.setVerticalSpacing(0) paperSizeLayout.setRowMinimumHeight(1, spacing) - paperSizeCombo = QtGui.QComboBox() + paperSizeCombo = QComboBox() paperSizeLayout.addWidget(paperSizeCombo, 0, 0, 1, 2) paperSizeCombo.addItems(list(_paperSizes.values())) - paperSizeDict = dict((num, attrib) for attrib, num in - vars(QtGui.QPrinter).items() - if isinstance(num, QtGui.QPrinter.PageSize)) - self.currentPaperSize = paperSizeDict[self.printData.printer. - paperSize()] + self.currentPaperSize = self.printData.paperSizeName() if self.currentPaperSize not in _paperSizes: self.currentPaperSize = 'Custom' paperSizeCombo.setCurrentIndex(list(_paperSizes.keys()). index(self.currentPaperSize)) paperSizeCombo.currentIndexChanged.connect(self.changePaper) - widthLabel = QtGui.QLabel(_('&Width:')) + widthLabel = QLabel(_('&Width:')) paperSizeLayout.addWidget(widthLabel, 2, 0) self.paperWidthSpin = UnitSpinBox(self.currentUnit) paperSizeLayout.addWidget(self.paperWidthSpin, 3, 0) widthLabel.setBuddy(self.paperWidthSpin) paperWidth, paperHeight = self.printData.roundedPaperSize() self.paperWidthSpin.setInchValue(paperWidth) - heightlabel = QtGui.QLabel(_('Height:')) + heightlabel = QLabel(_('Height:')) paperSizeLayout.addWidget(heightlabel, 2, 1) self.paperHeightSpin = UnitSpinBox(self.currentUnit) paperSizeLayout.addWidget(self.paperHeightSpin, 3, 1) @@ -546,47 +611,47 @@ self.paperWidthSpin.setEnabled(False) self.paperHeightSpin.setEnabled(False) - orientbox = QtGui.QGroupBox(_('Orientation')) + orientbox = QGroupBox(_('Orientation')) leftLayout.addWidget(orientbox) - orientLayout = QtGui.QVBoxLayout(orientbox) - portraitButton = QtGui.QRadioButton(_('Portra&it')) + orientLayout = QVBoxLayout(orientbox) + portraitButton = QRadioButton(_('Portra&it')) orientLayout.addWidget(portraitButton) - landscapeButton = QtGui.QRadioButton(_('Lan&dscape')) + landscapeButton = QRadioButton(_('Lan&dscape')) orientLayout.addWidget(landscapeButton) - self.portraitOrient = (self.printData.printer.orientation() == - QtGui.QPrinter.Portrait) + self.portraitOrient = (self.printData.pageLayout.orientation() == + QPageLayout.Portrait) if self.portraitOrient: portraitButton.setChecked(True) else: landscapeButton.setChecked(True) portraitButton.toggled.connect(self.changeOrient) - rightLayout = QtGui.QVBoxLayout() + rightLayout = QVBoxLayout() topLayout.addLayout(rightLayout) - marginsBox = QtGui.QGroupBox(_('Margins')) + marginsBox = QGroupBox(_('Margins')) rightLayout.addWidget(marginsBox) - marginsLayout = QtGui.QGridLayout(marginsBox) + marginsLayout = QGridLayout(marginsBox) spacing = marginsLayout.spacing() marginsLayout.setVerticalSpacing(0) marginsLayout.setRowMinimumHeight(2, spacing) marginsLayout.setRowMinimumHeight(5, spacing) - leftLabel = QtGui.QLabel(_('&Left:')) + leftLabel = QLabel(_('&Left:')) marginsLayout.addWidget(leftLabel, 3, 0) leftMarginSpin = UnitSpinBox(self.currentUnit) marginsLayout.addWidget(leftMarginSpin, 4, 0) leftLabel.setBuddy(leftMarginSpin) - topLabel = QtGui.QLabel(_('&Top:')) + topLabel = QLabel(_('&Top:')) marginsLayout.addWidget(topLabel, 0, 1) topMarginSpin = UnitSpinBox(self.currentUnit) marginsLayout.addWidget(topMarginSpin, 1, 1) topLabel.setBuddy(topMarginSpin) - rightLabel = QtGui.QLabel(_('&Right:')) + rightLabel = QLabel(_('&Right:')) marginsLayout.addWidget(rightLabel, 3, 2) rightMarginSpin = UnitSpinBox(self.currentUnit) marginsLayout.addWidget(rightMarginSpin, 4, 2) rightLabel.setBuddy(rightMarginSpin) - bottomLabel = QtGui.QLabel(_('&Bottom:')) + bottomLabel = QLabel(_('&Bottom:')) marginsLayout.addWidget(bottomLabel, 6, 1) bottomMarginSpin = UnitSpinBox(self.currentUnit) marginsLayout.addWidget(bottomMarginSpin, 7, 1) @@ -596,37 +661,45 @@ for control, value in zip(self.marginControls, self.printData.roundedMargins()): control.setInchValue(value) - headerLabel = QtGui.QLabel(_('He&ader:')) + headerLabel = QLabel(_('He&ader:')) marginsLayout.addWidget(headerLabel, 0, 2) self.headerMarginSpin = UnitSpinBox(self.currentUnit) marginsLayout.addWidget(self.headerMarginSpin, 1, 2) headerLabel.setBuddy(self.headerMarginSpin) self.headerMarginSpin.setInchValue(self.printData.headerMargin) - footerLabel = QtGui.QLabel(_('Foot&er:')) + footerLabel = QLabel(_('Foot&er:')) marginsLayout.addWidget(footerLabel, 6, 2) self.footerMarginSpin = UnitSpinBox(self.currentUnit) marginsLayout.addWidget(self.footerMarginSpin, 7, 2) footerLabel.setBuddy(self.footerMarginSpin) self.footerMarginSpin.setInchValue(self.printData.footerMargin) - columnsBox = QtGui.QGroupBox(_('Columns')) + columnsBox = QGroupBox(_('Columns')) rightLayout.addWidget(columnsBox) - columnLayout = QtGui.QGridLayout(columnsBox) - numLabel = QtGui.QLabel(_('&Number of columns')) + columnLayout = QGridLayout(columnsBox) + numLabel = QLabel(_('&Number of columns')) columnLayout.addWidget(numLabel, 0, 0) - self.columnSpin = QtGui.QSpinBox() + self.columnSpin = QSpinBox() columnLayout.addWidget(self.columnSpin, 0, 1) numLabel.setBuddy(self.columnSpin) self.columnSpin.setMinimum(1) self.columnSpin.setMaximum(9) self.columnSpin.setValue(self.printData.numColumns) - spaceLabel = QtGui.QLabel(_('Space between colu&mns')) + spaceLabel = QLabel(_('Space between colu&mns')) columnLayout.addWidget(spaceLabel, 1, 0) self.columnSpaceSpin = UnitSpinBox(self.currentUnit) columnLayout.addWidget(self.columnSpaceSpin, 1, 1) spaceLabel.setBuddy(self.columnSpaceSpin) self.columnSpaceSpin.setInchValue(self.printData.columnSpacing) + def changePrinter(self, newPrinterName): + """Change the currently selected printer. + + Arguments: + newPrinterName -- new printer selection + """ + self.currentPrinterName = newPrinterName + def changeUnits(self, unitNum): """Change the current unit and update conversions based on a signal. @@ -651,12 +724,13 @@ """ self.currentPaperSize = list(_paperSizes.keys())[paperNum] if self.currentPaperSize != 'Custom': - tempPrinter = QtGui.QPrinter() - tempPrinter.setPaperSize(getattr(QtGui.QPrinter, - self.currentPaperSize)) + tempPrinter = QPrinter() + pageLayout = tempPrinter.pageLayout() + pageLayout.setPageSize(QPageSize(getattr(QPageSize, + self.currentPaperSize))) if not self.portraitOrient: - tempPrinter.setOrientation(QtGui.QPrinter.Landscape) - paperSize = tempPrinter.paperSize(QtGui.QPrinter.Inch) + pageLayout.setOrientation(QPageLayout.Landscape) + paperSize = pageLayout.fullRect(QPageLayout.Inch) self.paperWidthSpin.setInchValue(round(paperSize.width(), 2)) self.paperHeightSpin.setInchValue(round(paperSize.height(), 2)) self.paperWidthSpin.setEnabled(self.currentPaperSize == 'Custom') @@ -676,36 +750,49 @@ self.paperWidthSpin.setInchValue(height) self.paperHeightSpin.setInchValue(width) + def checkValid(self): + """Return True if the current page size and margins appear to be valid. + """ + pageWidth = self.paperWidthSpin.inchValue + pageHeight = self.paperHeightSpin.inchValue + if pageWidth <= 0 or pageHeight <= 0: + return False + margins = tuple(control.inchValue for control in self.marginControls) + if (margins[0] + margins[2] >= pageWidth or + margins[1] + margins[3] >= pageHeight): + return False + return True + def saveChanges(self): """Update print data with current dialog settings. Return True if saved settings have changed, False otherwise. """ - if self.currentUnit != globalref.miscOptions.getValue('PrintUnits'): + if self.currentUnit != globalref.miscOptions['PrintUnits']: globalref.miscOptions.changeValue('PrintUnits', self.currentUnit) globalref.miscOptions.writeFile() changed = False + pageLayout = self.printData.pageLayout if self.currentPaperSize != 'Custom': - size = getattr(QtGui.QPrinter, self.currentPaperSize) - if size != self.printData.printer.paperSize(): - self.printData.printer.setPaperSize(size) + size = getattr(QPageSize, self.currentPaperSize) + if size != pageLayout.pageSize().id(): + pageLayout.setPageSize(QPageSize(size)) changed = True else: size = (self.paperWidthSpin.inchValue, self.paperHeightSpin.inchValue) if size != self.printData.roundedPaperSize(): - self.printData.printer.setPaperSize(QtCore.QSizeF(*size), - QtGui.QPrinter.Inch) + pageLayout.setPageSize(QPageSize(QSizeF(*size), + QPageSize.Inch)) changed = True - orient = (QtGui.QPrinter.Portrait if self.portraitOrient else - QtGui.QPrinter.Landscape) - if orient != self.printData.printer.orientation(): - self.printData.printer.setOrientation(orient) + orient = (QPageLayout.Portrait if self.portraitOrient else + QPageLayout.Landscape) + if orient != pageLayout.orientation(): + pageLayout.setOrientation(orient) changed = True - margins = [control.inchValue for control in self.marginControls] + margins = tuple(control.inchValue for control in self.marginControls) if margins != self.printData.roundedMargins(): - margins.append(QtGui.QPrinter.Inch) - self.printData.printer.setPageMargins(*margins) + pageLayout.setMargins(QMarginsF(*margins)) changed = True if self.printData.headerMargin != self.headerMarginSpin.inchValue: self.printData.headerMargin = self.headerMarginSpin.inchValue @@ -722,7 +809,7 @@ return changed -class UnitSpinBox(QtGui.QDoubleSpinBox): +class UnitSpinBox(QDoubleSpinBox): """Spin box with unit suffix that can convert the units of its contents. Stores the value at full precision to avoid round-trip rounding errors. @@ -785,54 +872,61 @@ self.inchValue = round(self.value() / _unitValues[self.unit], 2) -class SmallListWidget(QtGui.QListWidget): - """ListWidget with a smaller size hint""" +class SmallListWidget(QListWidget): + """ListWidget with a smaller size hint. + """ def __init__(self, parent=None): - QtGui.QListWidget.__init__(self, parent) + """Initialize the widget. + + Arguments: + parent -- the parent, if given + """ + super().__init__(parent) def sizeHint(self): - """Return smaller width""" + """Return smaller width. + """ itemHeight = self.visualItemRect(self.item(0)).height() - return QtCore.QSize(100, itemHeight * 3) + return QSize(100, itemHeight * 3) -class FontPage(QtGui.QWidget): +class FontPage(QWidget): """Font selection print option dialog page. """ - def __init__(self, printData, useSysDfltLabel=False, parent=None): + def __init__(self, printData, defaultLabel='', parent=None): """Create the font settings page. Arguments: printData -- a reference to the PrintData class - useSysDfltLabel -- default is system if True, o/w TreeLine output + defaultLabel -- default font label if given, o/w TreeLine output parent -- the parent dialog """ super().__init__(parent) self.printData = printData self.currentFont = self.printData.mainFont - topLayout = QtGui.QVBoxLayout(self) + topLayout = QVBoxLayout(self) self.setLayout(topLayout) - defaultBox = QtGui.QGroupBox(_('Default Font')) + defaultBox = QGroupBox(_('Default Font')) topLayout.addWidget(defaultBox) - defaultLayout = QtGui.QVBoxLayout(defaultBox) - defaultText = (_('&Use system default font') if useSysDfltLabel else - _('&Use TreeLine output view font')) - self.defaultCheck = QtGui.QCheckBox(defaultText) + defaultLayout = QVBoxLayout(defaultBox) + if not defaultLabel: + defaultLabel = _('&Use TreeLine output view font') + self.defaultCheck = QCheckBox(defaultLabel) defaultLayout.addWidget(self.defaultCheck) self.defaultCheck.setChecked(self.printData.useDefaultFont) self.defaultCheck.clicked.connect(self.setFontSelectAvail) - self.fontBox = QtGui.QGroupBox(_('Select Font')) + self.fontBox = QGroupBox(_('Select Font')) topLayout.addWidget(self.fontBox) - fontLayout = QtGui.QGridLayout(self.fontBox) + fontLayout = QGridLayout(self.fontBox) spacing = fontLayout.spacing() fontLayout.setSpacing(0) - label = QtGui.QLabel(_('&Font')) + label = QLabel(_('&Font')) fontLayout.addWidget(label, 0, 0) label.setIndent(2) - self.familyEdit = QtGui.QLineEdit() + self.familyEdit = QLineEdit() fontLayout.addWidget(self.familyEdit, 1, 0) self.familyEdit.setReadOnly(True) self.familyList = SmallListWidget() @@ -840,15 +934,15 @@ label.setBuddy(self.familyList) self.familyEdit.setFocusProxy(self.familyList) fontLayout.setColumnMinimumWidth(1, spacing) - families = [family for family in QtGui.QFontDatabase().families()] + families = [family for family in QFontDatabase().families()] families.sort(key=str.lower) self.familyList.addItems(families) self.familyList.currentItemChanged.connect(self.updateFamily) - label = QtGui.QLabel(_('Font st&yle')) + label = QLabel(_('Font st&yle')) fontLayout.addWidget(label, 0, 2) label.setIndent(2) - self.styleEdit = QtGui.QLineEdit() + self.styleEdit = QLineEdit() fontLayout.addWidget(self.styleEdit, 1, 2) self.styleEdit.setReadOnly(True) self.styleList = SmallListWidget() @@ -858,13 +952,13 @@ fontLayout.setColumnMinimumWidth(3, spacing) self.styleList.currentItemChanged.connect(self.updateStyle) - label = QtGui.QLabel(_('Si&ze')) + label = QLabel(_('Si&ze')) fontLayout.addWidget(label, 0, 4) label.setIndent(2) - self.sizeEdit = QtGui.QLineEdit() + self.sizeEdit = QLineEdit() fontLayout.addWidget(self.sizeEdit, 1, 4) - self.sizeEdit.setFocusPolicy(QtCore.Qt.ClickFocus) - validator = QtGui.QIntValidator(1, 512, self) + self.sizeEdit.setFocusPolicy(Qt.ClickFocus) + validator = QIntValidator(1, 512, self) self.sizeEdit.setValidator(validator) self.sizeList = SmallListWidget() fontLayout.addWidget(self.sizeList, 2, 4) @@ -875,12 +969,12 @@ fontLayout.setColumnStretch(2, 25) fontLayout.setColumnStretch(4, 10) - sampleBox = QtGui.QGroupBox(_('Sample')) + sampleBox = QGroupBox(_('Sample')) topLayout.addWidget(sampleBox) - sampleLayout = QtGui.QVBoxLayout(sampleBox) - self.sampleEdit = QtGui.QLineEdit() + sampleLayout = QVBoxLayout(sampleBox) + self.sampleEdit = QLineEdit() sampleLayout.addWidget(self.sampleEdit) - self.sampleEdit.setAlignment(QtCore.Qt.AlignCenter) + self.sampleEdit.setAlignment(Qt.AlignCenter) self.sampleEdit.setText(_('AaBbCcDdEeFfGg...TtUuVvWvXxYyZz')) self.sampleEdit.setFixedHeight(self.sampleEdit.sizeHint().height() * 2) @@ -907,20 +1001,23 @@ Arguments: font -- the QFont to set. """ - fontInfo = QtGui.QFontInfo(font) + fontInfo = QFontInfo(font) family = fontInfo.family() - matches = self.familyList.findItems(family, QtCore.Qt.MatchExactly) + matches = self.familyList.findItems(family, Qt.MatchExactly) if matches: self.familyList.setCurrentItem(matches[0]) self.familyList.scrollToItem(matches[0], - QtGui.QAbstractItemView.PositionAtTop) - style = QtGui.QFontDatabase().styleString(fontInfo) - matches = self.styleList.findItems(style, QtCore.Qt.MatchExactly) + QAbstractItemView.PositionAtTop) + style = QFontDatabase().styleString(fontInfo) + matches = self.styleList.findItems(style, Qt.MatchExactly) if matches: self.styleList.setCurrentItem(matches[0]) self.styleList.scrollToItem(matches[0]) + else: + self.styleList.setCurrentRow(0) + self.styleList.scrollToItem(self.styleList.currentItem()) size = repr(fontInfo.pointSize()) - matches = self.sizeList.findItems(size, QtCore.Qt.MatchExactly) + matches = self.sizeList.findItems(size, Qt.MatchExactly) if matches: self.sizeList.setCurrentItem(matches[0]) self.sizeList.scrollToItem(matches[0]) @@ -938,7 +1035,7 @@ self.familyEdit.selectAll() prevStyle = self.styleEdit.text() prevSize = self.sizeEdit.text() - fontDb = QtGui.QFontDatabase() + fontDb = QFontDatabase() styles = [style for style in fontDb.styles(family)] self.styleList.clear() self.styleList.addItems(styles) @@ -1003,7 +1100,7 @@ style = self.styleEdit.text() size = self.sizeEdit.text() if family and style and size: - return QtGui.QFontDatabase().font(family, style, int(size)) + return QFontDatabase().font(family, style, int(size)) return None def saveChanges(self): @@ -1029,7 +1126,7 @@ _headerNames = (_('&Header Left'), _('Header C&enter'), _('Header &Right')) _footerNames = (_('Footer &Left'), _('Footer Ce&nter'), _('Footer Righ&t')) -class HeaderPage(QtGui.QWidget): +class HeaderPage(QWidget): """Header/footer print option dialog page. """ def __init__(self, printData, parent=None): @@ -1043,31 +1140,31 @@ self.printData = printData self.focusedEditor = None - topLayout = QtGui.QGridLayout(self) - fieldBox = QtGui.QGroupBox(_('Fiel&ds')) + topLayout = QGridLayout(self) + fieldBox = QGroupBox(_('Fiel&ds')) topLayout.addWidget(fieldBox, 0, 0, 3, 1) - fieldLayout = QtGui.QVBoxLayout(fieldBox) + fieldLayout = QVBoxLayout(fieldBox) self.fieldListWidget = FieldListWidget() fieldLayout.addWidget(self.fieldListWidget) - fieldFormatButton = QtGui.QPushButton(_('Field For&mat')) + fieldFormatButton = QPushButton(_('Field For&mat')) fieldLayout.addWidget(fieldFormatButton) fieldFormatButton.clicked.connect(self.showFieldFormatDialog) - self.addFieldButton = QtGui.QPushButton('>>') + self.addFieldButton = QPushButton('>>') topLayout.addWidget(self.addFieldButton, 0, 1) self.addFieldButton.setMaximumWidth(self.addFieldButton.sizeHint(). height()) self.addFieldButton.clicked.connect(self.addField) - self.delFieldButton = QtGui.QPushButton('<<') + self.delFieldButton = QPushButton('<<') topLayout.addWidget(self.delFieldButton, 1, 1) self.delFieldButton.setMaximumWidth(self.delFieldButton.sizeHint(). height()) self.delFieldButton.clicked.connect(self.delField) - headerFooterBox = QtGui.QGroupBox(_('Header and Footer')) + headerFooterBox = QGroupBox(_('Header and Footer')) topLayout.addWidget(headerFooterBox, 0, 2, 2, 1) - headerFooterLayout = QtGui.QGridLayout(headerFooterBox) + headerFooterLayout = QGridLayout(headerFooterBox) spacing = headerFooterLayout.spacing() headerFooterLayout.setVerticalSpacing(0) headerFooterLayout.setRowMinimumHeight(2, spacing) @@ -1089,7 +1186,7 @@ """ lineEdits = [] for num, name in enumerate(names): - label = QtGui.QLabel(name) + label = QLabel(name) layout.addWidget(label, startRow, num) lineEdit = configdialog.TitleEdit() layout.addWidget(lineEdit, startRow + 1, num) @@ -1102,8 +1199,8 @@ def loadContent(self): """Load field names and header/footer text into the controls. """ - self.fieldListWidget.addItems(self.printData.localControl.model. - formats.fileInfoFormat.fieldNames()) + self.fieldListWidget.addItems(self.printData.localControl.structure. + treeFormats.fileInfoFormat.fieldNames()) self.fieldListWidget.setCurrentRow(0) for text, lineEdit in zip(splitHeaderFooter(self.printData.headerText), self.headerEdits): @@ -1180,7 +1277,7 @@ def showFieldFormatDialog(self): """Show thw dialog used to set file info field formats. """ - fileInfoFormat = (self.printData.localControl.model.formats. + fileInfoFormat = (self.printData.localControl.structure.treeFormats. fileInfoFormat) fieldName = self.fieldListWidget.currentItem().text() field = fileInfoFormat.fieldDict[fieldName] @@ -1213,7 +1310,7 @@ return changed -class FieldListWidget(QtGui.QListWidget): +class FieldListWidget(QListWidget): """List widget for fields with smaller width size hint. """ def __init__(self, parent=None): @@ -1227,10 +1324,10 @@ def sizeHint(self): """Return a size with a smaller width. """ - return QtCore.QSize(120, 100) + return QSize(120, 100) -class HeaderFieldFormatDialog(QtGui.QDialog): +class HeaderFieldFormatDialog(QDialog): """Dialog to modify file info field formats used in headers and footers. """ def __init__(self, field, localControl, parent=None): @@ -1244,45 +1341,45 @@ self.field = field self.localControl = localControl - self.setWindowFlags(QtCore.Qt.Dialog | QtCore.Qt.WindowTitleHint | - QtCore.Qt.WindowSystemMenuHint) + self.setWindowFlags(Qt.Dialog | Qt.WindowTitleHint | + Qt.WindowCloseButtonHint) self.setWindowTitle(_('Field Format for "{0}"').format(field.name)) - topLayout = QtGui.QVBoxLayout(self) + topLayout = QVBoxLayout(self) self.setLayout(topLayout) - self.formatBox = QtGui.QGroupBox(_('Output &Format')) + self.formatBox = QGroupBox(_('Output &Format')) topLayout.addWidget(self.formatBox) - formatLayout = QtGui.QHBoxLayout(self.formatBox) - self.formatEdit = QtGui.QLineEdit() + formatLayout = QHBoxLayout(self.formatBox) + self.formatEdit = QLineEdit() formatLayout.addWidget(self.formatEdit) - self.helpButton = QtGui.QPushButton(_('Format &Help')) + self.helpButton = QPushButton(_('Format &Help')) formatLayout.addWidget(self.helpButton) self.helpButton.clicked.connect(self.formatHelp) - extraBox = QtGui.QGroupBox(_('Extra Text')) + extraBox = QGroupBox(_('Extra Text')) topLayout.addWidget(extraBox) - extraLayout = QtGui.QVBoxLayout(extraBox) + extraLayout = QVBoxLayout(extraBox) spacing = extraLayout.spacing() extraLayout.setSpacing(0) - prefixLabel = QtGui.QLabel(_('&Prefix')) + prefixLabel = QLabel(_('&Prefix')) extraLayout.addWidget(prefixLabel) - self.prefixEdit = QtGui.QLineEdit() + self.prefixEdit = QLineEdit() extraLayout.addWidget(self.prefixEdit) prefixLabel.setBuddy(self.prefixEdit) extraLayout.addSpacing(spacing) - suffixLabel = QtGui.QLabel(_('&Suffix')) + suffixLabel = QLabel(_('&Suffix')) extraLayout.addWidget(suffixLabel) - self.suffixEdit = QtGui.QLineEdit() + self.suffixEdit = QLineEdit() extraLayout.addWidget(self.suffixEdit) suffixLabel.setBuddy(self.suffixEdit) - ctrlLayout = QtGui.QHBoxLayout() + ctrlLayout = QHBoxLayout() topLayout.addLayout(ctrlLayout) ctrlLayout.addStretch() - okButton = QtGui.QPushButton(_('&OK')) + okButton = QPushButton(_('&OK')) ctrlLayout.addWidget(okButton) okButton.clicked.connect(self.accept) - cancelButton = QtGui.QPushButton(_('&Cancel')) + cancelButton = QPushButton(_('&Cancel')) ctrlLayout.addWidget(cancelButton) cancelButton.clicked.connect(self.reject) @@ -1295,7 +1392,7 @@ def formatHelp(self): """Provide a format help menu based on a button signal. """ - menu = QtGui.QMenu(self) + menu = QMenu(self) self.formatHelpDict = {} for descript, key in self.field.getFormatHelpMenuList(): if descript: @@ -1304,7 +1401,7 @@ else: menu.addSeparator() menu.popup(self.helpButton. - mapToGlobal(QtCore.QPoint(0, self.helpButton.height()))) + mapToGlobal(QPoint(0, self.helpButton.height()))) menu.triggered.connect(self.insertFormat) def insertFormat(self, action): @@ -1322,8 +1419,8 @@ format = self.formatEdit.text() if (self.field.prefix != prefix or self.field.suffix != suffix or self.field.format != format): - undo.FormatUndo(self.localControl.model.undoList, - self.localControl.model.formats, + undo.FormatUndo(self.localControl.structure.undoList, + self.localControl.structure.treeFormats, treeformats.TreeFormats()) self.field.prefix = prefix self.field.suffix = suffix diff -Nru treeline-2.0.2/source/recentfiles.py treeline-3.0.1/source/recentfiles.py --- treeline-2.0.2/source/recentfiles.py 2014-12-20 19:15:45.000000000 +0000 +++ treeline-3.0.1/source/recentfiles.py 2018-04-08 19:19:50.000000000 +0000 @@ -4,7 +4,7 @@ # recentfiles.py, classes to save recent file lists, states and actions # # TreeLine, an information storage program -# Copyright (C) 2014, Douglas W. Bell +# Copyright (C) 2018, 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 @@ -12,11 +12,11 @@ # but WITTHOUT ANY WARRANTY. See the included LICENSE file for details. #****************************************************************************** -import os.path +import pathlib +import os import time -from PyQt4 import QtCore, QtGui +from PyQt5.QtWidgets import QAction import globalref -import options _maxActionPathLength = 30 _maxOpenNodesStored = 100 @@ -25,113 +25,62 @@ class RecentFileItem: """Class containing path, state and action info for a single recent file. """ - def __init__(self, posNum=1, path=''): - """Initialize a RecentFileItem. + def __init__(self, pathObj=None, dataDict=None): + """Initialize with either a pathObject or a stored data dict. Arguments: - posNum -- the position number for use in the action text - path -- the path to the file (can be set later) + pathObj -- a path object for the file + dataDict -- dict of staore data """ - self.posNum = posNum - self.path = '' - self.abbrevPath = '' - self.stateTime = 0 - self.scrollPos = '' - self.selectNodes = '' - self.openNodes = '' - self.action = QtGui.QAction(globalref.mainControl) - self.action.triggered.connect(self.openFile) - if path: - self.setPath(path) - - def setPath(self, path): - """Set the path and abbreviated path for this item and action. - - Arguments: - path - the new path name to set - """ - self.path = os.path.abspath(path) - self.abbrevPath = self.path - if len(self.path) > _maxActionPathLength: - truncLength = _maxActionPathLength - 3 - pos = self.path.find(os.sep, len(self.path) - truncLength) - if pos < 0: - pos = len(self.path) - truncLength - self.abbrevPath = '...' + self.path[pos:] - self.renameAction() - - def setPosNum(self, posNum): - """Set a new position number and rename the action if necessary. - - Arguments: - posNum -- the new position number - """ - if posNum != self.posNum: - self.posNum = posNum - self.renameAction() - - def renameAction(self): - """Rename the action based on the current path and position number. - """ - self.action.setText('&{0:d} {1}'.format(self.posNum, self.abbrevPath)) - self.action.setStatusTip = self.path + if not dataDict: + dataDict = {} + self.pathObj = pathObj + path = dataDict.get('path', '') + if not self.pathObj and path: + self.pathObj = pathlib.Path(path) + self.stateTime = dataDict.get('time', 0) + self.scrollPos = dataDict.get('scroll', '') + self.selectSpots = dataDict.get('select', []) + self.openSpots = dataDict.get('open', []) + + def dataDict(self): + """Return the data dict for storing this recent file. + """ + return {'path': str(self.pathObj), 'time': self.stateTime, + 'scroll': self.scrollPos, 'select': self.selectSpots, + 'open': self.openSpots} def pathIsValid(self): - """Return True if the current path exists. - """ - if self.path: - return os.access(self.path, os.R_OK) - return False - - def readOptionParams(self): - """Read path names and state info from the option storage. + """Return True if the current path points to an actual file. """ try: - path = globalref.histOptions.getValue('RecentPath{:d}'. - format(self.posNum)) - if path: - self.setPath(path) - self.stateTime = globalref.histOptions.getValue('RecentTime{:d}'. - format(self.posNum)) - self.scrollPos = globalref.histOptions.getValue('RecentScroll{:d}'. - format(self.posNum)) - self.selectNodes = (globalref.histOptions. - getValue('RecentSelect{:d}'. - format(self.posNum))) - self.openNodes = globalref.histOptions.getValue('RecentOpen{:d}'. - format(self.posNum)) - except KeyError: - return + return self.pathObj.is_file() + except OSError: + return False - def writeOptionParams(self): - """Write path names and state info to the option storage. + def itemAction(self, posNum): + """Return a menu action for this recent file. + + Arguments: + posNum -- the position number in the menu """ - key = 'RecentPath{:d}'.format(self.posNum) - while True: - try: - globalref.histOptions.changeValue('RecentPath{:d}'. - format(self.posNum), - self.path) - globalref.histOptions.changeValue('RecentTime{:d}'. - format(self.posNum), - self.stateTime) - globalref.histOptions.changeValue('RecentScroll{:d}'. - format(self.posNum), - self.scrollPos) - globalref.histOptions.changeValue('RecentSelect{:d}'. - format(self.posNum), - self.selectNodes) - globalref.histOptions.changeValue('RecentOpen{:d}'. - format(self.posNum), - self.openNodes) - return - except KeyError: - setRecentOptionDefault(self.posNum) + abbrevPath = str(self.pathObj) + if len(abbrevPath) > _maxActionPathLength: + truncLength = _maxActionPathLength - 3 + pos = abbrevPath.find(os.sep, len(abbrevPath) - truncLength) + if pos < 0: + pos = len(abbrevPath) - truncLength + abbrevPath = '...' + abbrevPath[pos:] + text = '&{0:d} {1}'.format(posNum, abbrevPath) + action = QAction(text, globalref.mainControl, + statusTip=str(self.pathObj)) + action.triggered.connect(self.openFile) + return action def openFile(self): """Open this path using the main control method. """ - globalref.mainControl.openFile(self.path, True) + globalref.mainControl.openFile(self.pathObj, checkModified=True) def recordTreeState(self, localControl): """Save the tree state of this item. @@ -140,53 +89,41 @@ localControl -- the control to store """ self.stateTime = int(time.time()) - treeView = localControl.currentTreeView() - topNode = treeView.nodeAtTop() - if topNode.parent: # not root, so view is scrolled - self.scrollPos = topNode.uniqueId - else: - self.scrollPos = '' - self.selectNodes = ','.join([node.uniqueId for node in - treeView.selectionModel(). - selectedNodes()]) - openNodes = [node.uniqueId for node in - localControl.model.root.openNodes()] - if len(openNodes) < _maxOpenNodesStored: - self.openNodes = ','.join(openNodes) - else: - self.openNodes = '' + treeView = localControl.activeWindow.treeView + topSpot = treeView.spotAtTop() + self.scrollPos = topSpot.spotId() if topSpot else '' + self.selectSpots = [spot.spotId() for spot in + treeView.selectionModel().selectedSpots()] + self.openSpots = [spot.spotId() for spot in localControl.structure. + structSpot().expandedSpotDescendantGen(treeView)] + self.openSpots = self.openSpots[:_maxOpenNodesStored] def restoreTreeState(self, localControl): """Restore the tree state of this item. + Return True if the state was restored. Arguments: localControl -- the control to set state """ - fileModTime = os.stat(localControl.filePath).st_mtime + fileModTime = self.pathObj.stat().st_mtime if self.stateTime == 0 or fileModTime > self.stateTime: - return # file modified externally - treeView = localControl.currentTreeView() - if self.openNodes: - nodes = [localControl.model.nodeIdDict.get(nodeId, None) for - nodeId in self.openNodes.split(',')] - rootNode = localControl.model.root - # should be faster if root is temporarily closed - treeView.collapse(rootNode.index()) - for node in nodes: - if node: - treeView.expand(node.index()) - treeView.expand(rootNode.index()) - if self.scrollPos: - topNode = localControl.model.nodeIdDict.get(self.scrollPos, None) - if topNode: - treeView.scrollTo(topNode.index(), - QtGui.QAbstractItemView.PositionAtTop) - if self.selectNodes: - nodes = [localControl.model.nodeIdDict.get(nodeId, None) for - nodeId in self.selectNodes.split(',')] - nodes = [node for node in nodes if node] - if nodes: - treeView.selectionModel().selectNodes(nodes) + return False # file modified externally + treeView = localControl.activeWindow.treeView + try: + for spotId in self.openSpots: + treeView.expandSpot(localControl.structure.spotById(spotId)) + if self.scrollPos: + treeView.scrollToSpot(localControl.structure. + spotById(self.scrollPos)) + if self.selectSpots: + treeView.selectionModel().selectSpots([localControl.structure. + spotById(spotId) for + spotId in + self.selectSpots]) + return True + except (KeyError, IndexError): + # for old TreeLine import (spotIds don't match) + return False def __eq__(self, other): """Test for equality between RecentFileItems and paths. @@ -195,10 +132,10 @@ other -- either a RecentFileItem or a path string """ try: - otherPath = other.path + otherPath = other.pathObj except AttributeError: otherPath = other - return os.path.normcase(self.path) == os.path.normcase(otherPath) + return self.pathObj.resolve() == otherPath.resolve() def __ne__(self, other): """Test for inequality between RecentFileItems and paths. @@ -207,10 +144,10 @@ other -- either a RecentFileItem or a path string """ try: - otherPath = other.path + otherPath = other.pathObj except AttributeError: otherPath = other - return os.path.normcase(self.path) != os.path.normcase(otherPath) + return self.pathObj.resolve() != otherPath.resolve() class RecentFileList(list): @@ -220,60 +157,74 @@ """Load the initial list from the options file. """ super().__init__() - self.numEntries = globalref.genOptions.getValue('RecentFiles') - self.readItems() + self.updateOptions() + for data in globalref.histOptions['RecentFiles']: + item = RecentFileItem(dataDict=data) + if not self.purge or item.pathIsValid(): + self.append(item) - def readItems(self): - """Read the recent items from the options file. + def updateOptions(self): + """Get number of entries and check exists from general options. """ - self[:] = [] - for num in range(self.numEntries): - item = RecentFileItem(num + 1) - item.readOptionParams() - if item.pathIsValid(): - self.append(item) - self.updatePosNumbers() + self.numEntries = globalref.genOptions['RecentFiles'] + self.purge = globalref.genOptions['PurgeRecentFiles'] def writeItems(self): """Write the recent items to the options file. """ - for entry in self: - entry.writeOptionParams() - for num in range(len(self), self.numEntries): - try: - globalref.histOptions.changeValue('RecentPath{:d}'. - format(num + 1), '') - except KeyError: - pass - globalref.histOptions.writeFile() + data = [item.dataDict() for item in self[:self.numEntries]] + globalref.histOptions.changeValue('RecentFiles', data) - def addItem(self, path): + def addItem(self, pathObj): """Add the given path at the start of the list. If the path is in the list, move it to the start, otherwise create a new item. Arguments: - path -- the new path to search and/or create + pathObj -- the new path object to search and/or create """ - item = RecentFileItem(1, path) + item = RecentFileItem(pathObj) try: item = self.pop(self.index(item)) except ValueError: pass self.insert(0, item) - self.updatePosNumbers() - def removeItem(self, path): + def removeItem(self, pathObj): """Remove the given path name if found. Arguments: - path -- the path to be removed + pathObj -- the path to be removed """ try: - self.remove(RecentFileItem(1, path)) + self.remove(RecentFileItem(pathObj)) except ValueError: pass - self.updatePosNumbers() + + def getActions(self): + """Return a list of actions for ech recent item. + """ + return [item.itemAction(i) for i, item in + enumerate(self[:self.numEntries], 1)] + + def firstDir(self): + """Return a path object of the first valid directory from recent items. + """ + for item in self: + pathObj = item.pathObj.parent + try: + if pathObj.is_dir(): + return pathObj + except OSError: + pass + return None + + def firstPath(self): + """Return the first full path from the recent items if valid. + """ + if self and self[0].pathIsValid(): + return self[0].pathObj + return None def saveTreeState(self, localControl): """Save the tree state of the item matching the localControl. @@ -281,98 +232,21 @@ Arguments: localControl -- the control to store """ - item = RecentFileItem(1, localControl.filePath) try: - item = self[self.index(item)] - except ValueError: + item = self[self.index(localControl.filePathObj)] + except (ValueError, TypeError, AttributeError, OSError): return item.recordTreeState(localControl) def retrieveTreeState(self, localControl): """Restore the saved tree state of the item matching the localControl. + Return True if the state was restored. Arguments: localControl -- the control to restore state """ - item = RecentFileItem(1, localControl.filePath) try: - item = self[self.index(item)] + item = self[self.index(localControl.filePathObj)] except ValueError: - return - item.restoreTreeState(localControl) - - def getActions(self): - """Return a list of actions for ech recent item. - """ - return [item.action for item in self] - - def firstDir(self): - """Return the first valid path from the recent items. - """ - for item in self: - path = os.path.dirname(item.path) - if os.path.exists(path): - return path + os.sep - return '' - - def firstPath(self): - """Return the first full path from the recent items. - """ - if self: - return self[0].path - return '' - - def updatePosNumbers(self): - """Update the item position numbers and truncate the list if req'd. - """ - self[:] = self[:self.numEntries] - for num, item in enumerate(self): - item.setPosNum(num + 1) - - def updateNumEntries(self): - """Update the maximum number of entries based on the option. - """ - oldNumEntries = self.numEntries - self.numEntries = globalref.genOptions.getValue('RecentFiles') - if self.numEntries > oldNumEntries: - for i in range(oldNumEntries, self.numEntries): - setRecentOptionDefault(i + 1) - elif self.numEntries < oldNumEntries: - for i in range(self.numEntries, oldNumEntries): - for name in ('Path', 'Time', 'Scroll', 'Select', 'Open'): - globalref.histOptions.removeValue('Recent{0}{1:d}'. - format(name, i + 1)) - self[:] = self[:self.numEntries] - - -def setRecentOptionDefaults(): - """Load correct number of default option entries. - - Must be called after general options are read but before reading history - options or initializing the RecentFileList class. - """ - for i in range(globalref.genOptions.getValue('RecentFiles')): - setRecentOptionDefault(i + 1) - - -def setRecentOptionDefault(num): - """Load a single default option entry. - - Arguments: - num -- the recent number to load - """ - options.StringOptionItem(globalref.histOptions, - 'RecentPath{:d}'.format(num), '', True, - _('Recent Files')) - options.IntOptionItem(globalref.histOptions, 'RecentTime{:d}'.format(num), - 0, 0, None, _('Recent Files')) - options.StringOptionItem(globalref.histOptions, - 'RecentScroll{:d}'.format(num), '', True, - _('Recent Files')) - options.StringOptionItem(globalref.histOptions, - 'RecentSelect{:d}'.format(num), '', True, - _('Recent Files')) - options.StringOptionItem(globalref.histOptions, - 'RecentOpen{:d}'.format(num), '', True, - _('Recent Files')) - + return False + return item.restoreTreeState(localControl) diff -Nru treeline-2.0.2/source/setup.py treeline-3.0.1/source/setup.py --- treeline-2.0.2/source/setup.py 2015-05-17 19:54:20.000000000 +0000 +++ treeline-3.0.1/source/setup.py 2018-08-17 01:19:44.000000000 +0000 @@ -9,7 +9,7 @@ # subdirectory that contains the files needed to run the application # # TreeLine, an information storage program -# Copyright (C) 2015, Douglas W. Bell +# Copyright (C) 2018, 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 @@ -33,10 +33,12 @@ setup(name = 'treeline', version = __version__, description = 'TreeLine info storage program', - options = {'build_exe': {'includes': ['atexit', 'urllib'], + options = {'build_exe': {'includes': ['atexit', 'PyQt5.sip'], 'include_files': extraFiles, 'excludes': ['*.pyc'], - 'icon': '../win/treeline.ico', + 'zip_include_packages': ['*'], + 'zip_exclude_packages': [], 'include_msvcr': True, - 'build_exe': '../../TreeLine-2.0'}}, - executables = [Executable('treeline.py', base=base)]) + 'build_exe': '../../TreeLine-3.0'}}, + executables = [Executable('treeline.py', base=base, + icon='../win/treeline.ico')]) diff -Nru treeline-2.0.2/source/spellcheck.py treeline-3.0.1/source/spellcheck.py --- treeline-2.0.2/source/spellcheck.py 2014-12-20 19:15:45.000000000 +0000 +++ treeline-3.0.1/source/spellcheck.py 2018-04-08 19:19:50.000000000 +0000 @@ -1,22 +1,27 @@ #!/usr/bin/env python3 -#**************************************************************************** +#****************************************************************************** # spellcheck.py, provides classes for spell check interfaces and dialogs, # including interfaces to aspell, ispell, hunspell. # -# Copyright (C) 2014, Douglas W. Bell +# TreeLine, an information storage program +# Copyright (C) 2017, 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 # version. This program is distributed in the hope that it will be useful, # but WITTHOUT ANY WARRANTY. See the included LICENSE file for details. -#***************************************************************************** +#****************************************************************************** import re import sys import subprocess import collections -from PyQt4 import QtCore, QtGui +from PyQt5.QtCore import QSize, Qt, pyqtSignal +from PyQt5.QtGui import QFontMetrics, QTextCursor +from PyQt5.QtWidgets import (QApplication, QDialog, QFileDialog, QGroupBox, + QHBoxLayout, QLabel, QLineEdit, QListWidget, + QMessageBox, QPushButton, QTextEdit, QVBoxLayout) import undo import globalref @@ -175,12 +180,12 @@ """ self.controlRef = controlRef self.selectModel = controlRef.currentSelectionModel() - self.currentNode = None + self.currentSpot = None self.currentField = '' self.lineNum = 0 self.textLine = '' - parentWidget = QtGui.QApplication.activeWindow() - path = globalref.miscOptions.getValue('SpellCheckPath') + parentWidget = QApplication.activeWindow() + path = globalref.miscOptions['SpellCheckPath'] while True: try: self.spellCheckInterface = SpellCheckInterface(path, @@ -195,17 +200,17 @@ prompt = (_('Could not find either aspell.exe, ' 'ispell.exe or hunspell.exe\n' 'Browse for location?')) - ans = QtGui.QMessageBox.warning(parentWidget, + ans = QMessageBox.warning(parentWidget, _('Spell Check Error'), prompt, - QtGui.QMessageBox.Yes | - QtGui.QMessageBox.Cancel, - QtGui.QMessageBox.Yes) - if ans == QtGui.QMessageBox.Cancel: + QMessageBox.Yes | + QMessageBox.Cancel, + QMessageBox.Yes) + if ans == QMessageBox.Cancel: raise title = _('Locate aspell.exe, ipsell.exe or ' 'hunspell.exe') - path = QtGui.QFileDialog.getOpenFileName(parentWidget, + path, fltr = QFileDialog.getOpenFileName(parentWidget, title, '', _('Program (*.exe)')) if path: @@ -218,47 +223,44 @@ else: prompt = (_('TreeLine Spell Check Error\nMake sure ' 'aspell, ispell or hunspell is installed')) - QtGui.QMessageBox.warning(parentWidget, 'TreeLine', - prompt) + QMessageBox.warning(parentWidget, 'TreeLine', prompt) raise def spellCheck(self): """Spell check starting with the selected branches. """ - parentWidget = QtGui.QApplication.activeWindow() + parentWidget = QApplication.activeWindow() spellCheckDialog = SpellCheckDialog(self.spellCheckInterface, parentWidget) spellCheckDialog.misspellFound.connect(self.updateSelection) spellCheckDialog.changeRequest.connect(self.changeNode) - origBranches = self.selectModel.uniqueBranches() + origBranches = self.selectModel.selectedBranchSpots() + if not origBranches: + origBranches = self.controlRef.structure.rootSpots() result = (spellCheckDialog. startSpellCheck(self.textLineGenerator(origBranches))) - self.selectModel.selectNodes(origBranches, expandParents = True) - if result and origBranches[0].parent: - prompt = (_('Finished checking the branch\n' - 'Continue from the root branch?')) - ans = QtGui.QMessageBox.information(parentWidget, - _('TreeLine Spell Check'), - prompt, - QtGui.QMessageBox.Yes | - QtGui.QMessageBox.No) - if ans == QtGui.QMessageBox.Yes: - generator = self.textLineGenerator([self.controlRef.model. - root]) + self.selectModel.selectSpots(origBranches, expandParents = True) + if result and origBranches[0].parentSpot.parentSpot: + prompt = _('Finished checking the branch\nContinue from the top?') + ans = QMessageBox.information(parentWidget, + _('TreeLine Spell Check'), prompt, + QMessageBox.Yes | QMessageBox.No) + if ans == QMessageBox.Yes: + generator = self.textLineGenerator(self.controlRef.structure. + rootSpots()) result = spellCheckDialog.startSpellCheck(generator) - self.selectModel.selectNodes(origBranches, + self.selectModel.selectSpots(origBranches, expandParents = True) else: result = False if result: - QtGui.QMessageBox.information(parentWidget, - _('TreeLine Spell Check'), - _('Finished spell checking')) + QMessageBox.information(parentWidget, _('TreeLine Spell Check'), + _('Finished spell checking')) def updateSelection(self): """Change the tree selection to the node with a misspelled word. """ - self.selectModel.selectNode(self.currentNode, expandParents = True) + self.selectModel.selectSpots([self.currentSpot], expandParents = True) def changeNode(self, newTextLine): """Replace the current text line in the current node. @@ -266,12 +268,12 @@ Arguments: newTextLine -- the new text to use """ - undo.DataUndo(self.currentNode.modelRef.undoList, self.currentNode) - textLines = (self.currentNode.data.get(self.currentField, ''). - split('\n')) + node = self.currentSpot.nodeRef + undo.DataUndo(self.controlRef.structure.undoList, node) + textLines = node.data.get(self.currentField, '').split('\n') textLines[self.lineNum] = newTextLine - self.currentNode.data[self.currentField] = '\n'.join(textLines) - self.controlRef.updateTreeNode(self.currentNode) + node.data[self.currentField] = '\n'.join(textLines) + self.controlRef.updateTreeNode(node) def textLineGenerator(self, branches): """Yield next line to be checked. @@ -280,21 +282,21 @@ branches -- a list of branch parent nodes to check. """ for parent in branches: - for self.currentNode in parent.descendantGen(): - for self.currentField in (self.currentNode.nodeFormat(). - fieldNames()): - text = self.currentNode.data.get(self.currentField, '') + for self.currentSpot in parent.spotDescendantGen(): + node = self.currentSpot.nodeRef + for self.currentField in node.formatRef.fieldNames(): + text = node.data.get(self.currentField, '') if text: for self.lineNum, self.textLine in \ enumerate(text.split('\n')): yield self.textLine -class SpellCheckDialog(QtGui.QDialog): +class SpellCheckDialog(QDialog): """Dialog to perform and control the spell check operation. """ - misspellFound = QtCore.pyqtSignal() - changeRequest = QtCore.pyqtSignal(str) + misspellFound = pyqtSignal() + changeRequest = pyqtSignal(str) def __init__(self, spellCheckInterface, parent=None): """Create the dialog. @@ -303,8 +305,8 @@ parent -- the parent dialog """ super().__init__(parent) - self.setWindowFlags(QtCore.Qt.Dialog | QtCore.Qt.WindowTitleHint | - QtCore.Qt.WindowSystemMenuHint) + self.setWindowFlags(Qt.Dialog | Qt.WindowTitleHint | + Qt.WindowCloseButtonHint) self.setWindowTitle(_('Spell Check')) self.spellCheckInterface = spellCheckInterface self.textLineIter = None @@ -314,55 +316,55 @@ self.word = '' self.postion = 0 - topLayout = QtGui.QHBoxLayout(self) - leftLayout = QtGui.QVBoxLayout() + topLayout = QHBoxLayout(self) + leftLayout = QVBoxLayout() topLayout.addLayout(leftLayout) - wordBox = QtGui.QGroupBox(_('Not in Dictionary')) + wordBox = QGroupBox(_('Not in Dictionary')) leftLayout.addWidget(wordBox) - wordLayout = QtGui.QVBoxLayout(wordBox) - label = QtGui.QLabel(_('Word:')) + wordLayout = QVBoxLayout(wordBox) + label = QLabel(_('Word:')) wordLayout.addWidget(label) - self.wordEdit = QtGui.QLineEdit() + self.wordEdit = QLineEdit() wordLayout.addWidget(self.wordEdit) self.wordEdit.textChanged.connect(self.updateFromWord) wordLayout.addSpacing(5) - label = QtGui.QLabel(_('Context:')) + label = QLabel(_('Context:')) wordLayout.addWidget(label) self.contextEdit = SpellContextEdit() wordLayout.addWidget(self.contextEdit) self.contextEdit.textChanged.connect(self.updateFromContext) - suggestBox = QtGui.QGroupBox(_('Suggestions')) + suggestBox = QGroupBox(_('Suggestions')) leftLayout.addWidget(suggestBox) - suggestLayout = QtGui.QVBoxLayout(suggestBox) - self.suggestList = QtGui.QListWidget() + suggestLayout = QVBoxLayout(suggestBox) + self.suggestList = QListWidget() suggestLayout.addWidget(self.suggestList) self.suggestList.itemDoubleClicked.connect(self.replace) - rightLayout = QtGui.QVBoxLayout() + rightLayout = QVBoxLayout() topLayout.addLayout(rightLayout) - ignoreButton = QtGui.QPushButton(_('Ignor&e')) + ignoreButton = QPushButton(_('Ignor&e')) rightLayout.addWidget(ignoreButton) ignoreButton.clicked.connect(self.ignore) - ignoreAllButton = QtGui.QPushButton(_('&Ignore All')) + ignoreAllButton = QPushButton(_('&Ignore All')) rightLayout.addWidget(ignoreAllButton) ignoreAllButton.clicked.connect(self.ignoreAll) rightLayout.addStretch() - addButton = QtGui.QPushButton(_('&Add')) + addButton = QPushButton(_('&Add')) rightLayout.addWidget(addButton) addButton.clicked.connect(self.add) - addLowerButton = QtGui.QPushButton(_('Add &Lowercase')) + addLowerButton = QPushButton(_('Add &Lowercase')) rightLayout.addWidget(addLowerButton) addLowerButton.clicked.connect(self.addLower) rightLayout.addStretch() - replaceButton = QtGui.QPushButton(_('&Replace')) + replaceButton = QPushButton(_('&Replace')) rightLayout.addWidget(replaceButton) replaceButton.clicked.connect(self.replace) - self.replaceAllButton = QtGui.QPushButton(_('Re&place All')) + self.replaceAllButton = QPushButton(_('Re&place All')) rightLayout.addWidget(self.replaceAllButton) self.replaceAllButton.clicked.connect(self.replaceAll) rightLayout.addStretch() - cancelButton = QtGui.QPushButton(_('&Cancel')) + cancelButton = QPushButton(_('&Cancel')) rightLayout.addWidget(cancelButton) cancelButton.clicked.connect(self.reject) self.widgetDisableList = [ignoreButton, ignoreAllButton, addButton, @@ -384,7 +386,7 @@ except StopIteration: return True if self.spellCheck(): - if self.exec_() == QtGui.QDialog.Rejected: + if self.exec_() == QDialog.Rejected: return False return True @@ -525,7 +527,7 @@ self.suggestList.clearSelection() -class SpellContextEdit(QtGui.QTextEdit): +class SpellContextEdit(QTextEdit): """Editor for spell check word context. Sets the size hint to 3 lines and simplifies selction. @@ -542,8 +544,8 @@ def sizeHint(self): """Set prefered size of 3 lines long. """ - fontHeight = QtGui.QFontMetrics(self.currentFont()).lineSpacing() - return QtCore.QSize(QtGui.QTextEdit.sizeHint(self).width(), + fontHeight = QFontMetrics(self.currentFont()).lineSpacing() + return QSize(QTextEdit.sizeHint(self).width(), fontHeight * 3) def setSelection(self, fromPos, toPos): @@ -555,6 +557,6 @@ """ cursor = self.textCursor() cursor.setPosition(fromPos) - cursor.setPosition(toPos, QtGui.QTextCursor.KeepAnchor) + cursor.setPosition(toPos, QTextCursor.KeepAnchor) self.setTextCursor(cursor) self.ensureCursorVisible() diff -Nru treeline-2.0.2/source/titlelistview.py treeline-3.0.1/source/titlelistview.py --- treeline-2.0.2/source/titlelistview.py 2015-01-24 19:07:54.000000000 +0000 +++ treeline-3.0.1/source/titlelistview.py 2018-06-23 18:03:42.000000000 +0000 @@ -4,7 +4,7 @@ # titlelistview.py, provides a class for the title list view # # TreeLine, an information storage program -# Copyright (C) 2015, Douglas W. Bell +# Copyright (C) 2018, 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 @@ -12,34 +12,36 @@ # but WITTHOUT ANY WARRANTY. See the included LICENSE file for details. #****************************************************************************** -from PyQt4 import QtCore, QtGui +from PyQt5.QtCore import Qt, pyqtSignal +from PyQt5.QtGui import QKeySequence, QPalette, QTextCursor +from PyQt5.QtWidgets import QTextEdit import treenode import undo import globalref -class TitleListView(QtGui.QTextEdit): +class TitleListView(QTextEdit): """Class override for the title list view. Sets view defaults and updates the content. """ - nodeModified = QtCore.pyqtSignal(treenode.TreeNode) - treeModified = QtCore.pyqtSignal() - shortcutEntered = QtCore.pyqtSignal(QtGui.QKeySequence) - def __init__(self, selectModel, isChildView=True, parent=None): + nodeModified = pyqtSignal(treenode.TreeNode) + treeModified = pyqtSignal() + shortcutEntered = pyqtSignal(QKeySequence) + def __init__(self, treeView, isChildView=True, parent=None): """Initialize the title list view. Arguments: - selectModel - the tree view's selection model + treeView - the tree view, needed for the current selection model isChildView -- shows selected nodes if false, child nodes if true parent -- the parent main window """ super().__init__(parent) - self.selectModel = selectModel + self.treeView = treeView self.isChildView = isChildView - self.hideChildView = not globalref.genOptions.getValue('ShowChildPane') + self.hideChildView = not globalref.genOptions['InitShowChildPane'] self.setAcceptRichText(False) - self.setLineWrapMode(QtGui.QTextEdit.NoWrap) + self.setLineWrapMode(QTextEdit.NoWrap) self.setTabChangesFocus(True) self.setUndoRedoEnabled(False) self.textChanged.connect(self.readChange) @@ -49,18 +51,27 @@ Avoids update if view is not visible or has zero height or width. """ - selNodes = self.selectModel.selectedNodes() - if self.isChildView and (len(selNodes) != 1 or self.hideChildView): + selSpots = self.treeView.selectionModel().selectedSpots() + if self.isChildView: + if len(selSpots) > 1 or self.hideChildView: + self.hide() + return + if not selSpots: + # use top node childList from tree structure + selSpots = [globalref.mainControl.activeControl.structure. + structSpot()] + elif not selSpots: self.hide() - else: - self.show() + return + self.show() if not self.isVisible() or self.height() == 0 or self.width() == 0: return if self.isChildView: - selNodes = selNodes[0].childList + selSpots = selSpots[0].childSpots() self.blockSignals(True) - if selNodes: - self.setPlainText('\n'.join(node.title() for node in selNodes)) + if selSpots: + self.setPlainText('\n'.join(spot.nodeRef.title(spot) for spot in + selSpots)) else: self.clear() self.blockSignals(False) @@ -70,21 +81,47 @@ """ textList = [' '.join(text.split()) for text in self.toPlainText(). split('\n') if text.strip()] - selNodes = self.selectModel.selectedNodes() + selSpots = self.treeView.selectionModel().selectedSpots() + treeStructure = globalref.mainControl.activeControl.structure if self.isChildView: - parent = selNodes[0] - selNodes = parent.childList - if len(selNodes) == len(textList): - for node, text in zip(selNodes, textList): - if node.title() != text: - undoObj = undo.DataUndo(node.modelRef.undoList, node, True) - if node.setTitle(text): - self.nodeModified.emit(node) - else: - node.modelRef.undoList.removeLastUndo(undoObj) - elif self.isChildView: - undo.BranchUndo(parent.modelRef.undoList, parent) - parent.replaceChildren(textList) + if not selSpots: + selSpots = [treeStructure.structSpot()] + parentSpot = selSpots[0] + parent = parentSpot.nodeRef + selSpots = parentSpot.childSpots() + if len(selSpots) == len(textList): + # collect changes first to skip false clone changes + changes = [(spot.nodeRef, text) for spot, text in + zip(selSpots, textList) + if spot.nodeRef.title(spot) != text] + for node, text in changes: + undoObj = undo.DataUndo(treeStructure.undoList, node, + skipSame=True) + if node.setTitle(text): + self.nodeModified.emit(node) + else: + treeStructure.undoList.removeLastUndo(undoObj) + elif self.isChildView and (textList or parent != treeStructure): + undo.ChildDataUndo(treeStructure.undoList, parent) + # clear hover to avoid crash if deleted child item was hovered over + self.treeView.clearHover() + isDeleting = len(selSpots) > len(textList) + control = globalref.mainControl.activeControl + if isDeleting and len(control.windowList) > 1: + # clear other window selections that are about to be deleted + for window in control.windowList: + if window != control.activeWindow: + selectModel = window.treeView.selectionModel() + ancestors = set() + for spot in selectModel.selectedBranchSpots(): + ancestors.update(set(spot.spotChain())) + if ancestors & set(selSpots): + selectModel.selectSpots([], False) + expandState = self.treeView.savedExpandState(selSpots) + parent.replaceChildren(textList, treeStructure) + self.treeView.restoreExpandState(expandState) + if self.treeView.selectionModel().selectedSpots(): + self.treeView.expandSpot(parentSpot) self.treeModified.emit() else: self.updateContents() # remove illegal changes @@ -101,10 +138,10 @@ wordList -- list of words to highlight regExpList -- a list of regular expression objects to highlight """ - backColor = self.palette().brush(QtGui.QPalette.Active, - QtGui.QPalette.Highlight) - foreColor = self.palette().brush(QtGui.QPalette.Active, - QtGui.QPalette.HighlightedText) + backColor = self.palette().brush(QPalette.Active, + QPalette.Highlight) + foreColor = self.palette().brush(QPalette.Active, + QPalette.HighlightedText) if wordList is None: wordList = [] if regExpList is None: @@ -117,12 +154,12 @@ selections = [] for word in wordList: while self.find(word): - extraSel = QtGui.QTextEdit.ExtraSelection() + extraSel = QTextEdit.ExtraSelection() extraSel.cursor = self.textCursor() extraSel.format.setBackground(backColor) extraSel.format.setForeground(foreColor) selections.append(extraSel) - cursor = QtGui.QTextCursor(self.document()) + cursor = QTextCursor(self.document()) self.setTextCursor(cursor) # reset main cursor/selection self.setExtraSelections(selections) @@ -132,9 +169,9 @@ Arguments: event -- the focus in event """ - if event.reason() in (QtCore.Qt.TabFocusReason, - QtCore.Qt.BacktabFocusReason): - self.moveCursor(QtGui.QTextCursor.End) + if event.reason() in (Qt.TabFocusReason, + Qt.BacktabFocusReason): + self.moveCursor(QTextCursor.End) super().focusInEvent(event) def contextMenuEvent(self, event): @@ -156,13 +193,13 @@ Arguments: event -- the key press event """ - if (event.modifiers() == QtCore.Qt.ControlModifier and - QtCore.Qt.Key_A <= event.key() <= QtCore.Qt.Key_Z): - key = QtGui.QKeySequence(event.modifiers() | event.key()) + if (event.modifiers() == Qt.ControlModifier and + Qt.Key_A <= event.key() <= Qt.Key_Z): + key = QKeySequence(event.modifiers() | event.key()) self.shortcutEntered.emit(key) return - if self.isChildView or event.key() not in (QtCore.Qt.Key_Enter, - QtCore.Qt.Key_Return): + if self.isChildView or event.key() not in (Qt.Key_Enter, + Qt.Key_Return): super().keyPressEvent(event) def resizeEvent(self, event): diff -Nru treeline-2.0.2/source/treeformats.py treeline-3.0.1/source/treeformats.py --- treeline-2.0.2/source/treeformats.py 2015-01-31 19:13:12.000000000 +0000 +++ treeline-3.0.1/source/treeformats.py 2018-09-27 00:53:52.000000000 +0000 @@ -4,7 +4,7 @@ # treeformats.py, provides a class to store node format types and info # # TreeLine, an information storage program -# Copyright (C) 2015, Douglas W. Bell +# Copyright (C) 2018, 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 @@ -12,25 +12,26 @@ # but WITTHOUT ANY WARRANTY. See the included LICENSE file for details. #****************************************************************************** +import operator import copy import nodeformat -import conditional import matheval +import conditional defaultTypeName = _('DEFAULT') -dummyRootTypeName = '_DUMMY__ROOT_' class TreeFormats(dict): """Class to store node format types and info. - + Stores node formats by format name in a dictionary. Provides methods to change and update format data. """ - def __init__(self, setDefault=False): + def __init__(self, formatList=None, setDefault=False): """Initialize the format storage. Arguments: + formatList -- the list of formats' file info setDefault - if true, initializes with a default format """ super().__init__() @@ -38,30 +39,95 @@ self.typeRenameDict = {} # nested dict for fields renamed, keys are type name then orig field self.fieldRenameDict = {} - # list of format types with unique ID ref field changes - self.changedIdFieldTypes = set() + self.conditionalTypes = set() # set of math field names with deleted equations, keys are type names self.emptiedMathDict = {} - self.conditionalTypes = set() self.mathFieldRefDict = {} # list of math eval levels, each is a dict by type name with lists of # equation fields self.mathLevelList = [] # for saving all-type find/filter conditionals self.savedConditionText = {} - self.configModified = False self.fileInfoFormat = nodeformat.FileInfoFormat(self) + if formatList: + for formatData in formatList: + name = formatData['formatname'] + self[name] = nodeformat.NodeFormat(name, self, formatData) + self.updateDerivedRefs() + self.updateMathFieldRefs() + if nodeformat.FileInfoFormat.typeName in self: + self.fileInfoFormat.duplicateFileInfo(self[nodeformat. + FileInfoFormat. + typeName]) + del self[nodeformat.FileInfoFormat.typeName] if setDefault: self[defaultTypeName] = nodeformat.NodeFormat(defaultTypeName, - self, {}, True) - self.updateLineParsing() + self, + addDefaultField=True) + + def storeFormats(self): + """Return a list of formats stored in JSON data. + """ + formats = list(self.values()) + if self.fileInfoFormat.fieldFormatModified: + formats.append(self.fileInfoFormat) + return sorted([nodeFormat.storeFormat() for nodeFormat in formats], + key=operator.itemgetter('formatname')) + + def loadGlobalSavedConditions(self, propertyDict): + """Load all-type saved conditionals from property dict. + + Arguments: + propertyDict -- a JSON property dict + """ + for key in propertyDict.keys(): + if key.startswith('glob-cond-'): + self.savedConditionText[key[10:]] = propertyDict[key] + + def storeGlobalSavedConditions(self, propertyDict): + """Save all-type saved conditionals to property dict. + + Arguments: + propertyDict -- a JSON property dict + """ + for key, text in self.savedConditionText.items(): + propertyDict['glob-cond-' + key] = text + return propertyDict + + def copySettings(self, sourceFormats): + """Copy all settings from other type formats to these formats. + + Copy any new formats and delete any missing formats. + Arguments: + sourceFormats -- the type formats to copy + """ + if sourceFormats.typeRenameDict: + for oldName, newName in sourceFormats.typeRenameDict.items(): + try: + self[oldName].name = newName + except KeyError: + pass # skip if new type is renamed + formats = list(self.values()) + self.clear() + for nodeFormat in formats: + self[nodeFormat.name] = nodeFormat + sourceFormats.typeRenameDict = {} + for name in list(self.keys()): + if name in sourceFormats: + self[name].copySettings(sourceFormats[name]) + else: + del self[name] + for name in sourceFormats.keys(): + if name not in self: + self[name] = copy.deepcopy(sourceFormats[name]) + if (sourceFormats.fileInfoFormat.fieldFormatModified or + self.fileInfoFormat.fieldFormatModified): + self.fileInfoFormat.duplicateFileInfo(sourceFormats.fileInfoFormat) def typeNames(self): """Return a sorted list of type names. """ - names = list(self.keys()) - names.sort() - return names + return sorted(list(self.keys())) def updateLineParsing(self): """Update the fields parsed in the output lines for each format type. @@ -77,22 +143,13 @@ """ self.setdefault(typeFormat.name, typeFormat) - def addDummyRootType(self): - """Add temporary dummy root format and return it. - - Used as a root item for copying a multiple selection. + def fieldNameDict(self): + """Return a dictionary of field name sets using type names as keys. """ - typeFormat = nodeformat.NodeFormat(dummyRootTypeName, self) - self[dummyRootTypeName] = typeFormat - return typeFormat - - def removeDummyRootType(self): - """Remove the temporary dummy root format if present. - """ - try: - del self[dummyRootTypeName] - except KeyError: - pass + result = {} + for typeFormat in self.values(): + result[typeFormat.name] = set(typeFormat.fieldNames()) + return result def updateDerivedRefs(self): """Update derived type lists (in generics) & the conditional type set. @@ -112,7 +169,7 @@ self.conditionalTypes.add(typeFormat) for typeFormat in self.values(): if not typeFormat.genericType and not typeFormat.derivedTypes: - typeFormat.conditional = conditional.Conditional() + typeFormat.conditional = None self.conditionalTypes.discard(typeFormat) def updateMathFieldRefs(self): @@ -144,9 +201,9 @@ [allRecursiveRefs[0]]}] for prevRef, currRef in zip(allRecursiveRefs, allRecursiveRefs[1:]): if currRef.evalSequence == prevRef.evalSequence: - if prevRef.evalDirection == matheval.optional: + if prevRef.evalDirection == matheval.EvalDir.optional: prevRef.evalDirection = currRef.evalDirection - elif currRef.evalDirection == matheval.optional: + elif currRef.evalDirection == matheval.EvalDir.optional: currRef.evalDirection = prevRef.evalDirection if currRef.evalDirection != prevRef.evalDirection: self.mathLevelList.append({}) @@ -155,41 +212,6 @@ self.mathLevelList[-1].setdefault(currRef.eqnTypeName, []).append(currRef) - def loadAttr(self, attrs): - """Restore attributes from the stored file. - - Arguments: - attrs -- a dict of attributes to load - """ - for key in attrs.keys(): - if key.startswith('glob-cond-'): - self.savedConditionText[key[10:]] = attrs[key] - - def xmlAttr(self): - """Return a dictionary of the formats' attributes. - """ - attrs = {} - for key, text in self.savedConditionText.items(): - attrs['glob-cond-' + key] = text - return attrs - - def copyTypes(self, sourceFormats, modelRef): - """Copy type formats from another TreeFormats instance. - - New formats are added, ones with the same name are overwritten. - Arguments: - sourceFormats -- the TreeFormats instance to copy from - modelRef -- a ref to the current model - """ - newFormats = copy.deepcopy(self) - for sourceFormat in sourceFormats.values(): - if sourceFormat.name in newFormats: - newFormats.changedIdFieldTypes.add(sourceFormat) - newFormats[sourceFormat.name] = sourceFormat - newFormats.updateDerivedRefs() - modelRef.configDialogFormats = newFormats - modelRef.applyConfigDialogFormats() - def numberingFieldDict(self): """Return a dict of numbering field names by node format name. """ @@ -209,7 +231,7 @@ """ formats = set() for node in nodes: - formats.add(node.formatName) + formats.add(node.formatRef.name) firstFields = self[formats.pop()].fieldNames() commonFields = set(firstFields) for formatName in formats: diff -Nru treeline-2.0.2/source/treeline.pro treeline-3.0.1/source/treeline.pro --- treeline-2.0.2/source/treeline.pro 2015-03-27 01:50:50.000000000 +0000 +++ treeline-3.0.1/source/treeline.pro 2018-04-08 19:19:50.000000000 +0000 @@ -1,4 +1,5 @@ -SOURCES = conditional.py \ +SOURCES = breadcrumbview.py \ + conditional.py \ configdialog.py \ dataeditors.py \ dataeditview.py \ @@ -10,7 +11,6 @@ helpview.py \ icondict.py \ imports.py \ - linkref.py \ matheval.py \ miscdialogs.py \ nodeformat.py \ @@ -19,7 +19,6 @@ options.py \ outputview.py \ p3.py \ - plugininterface.py \ printdata.py \ printdialogs.py \ recentfiles.py \ @@ -31,10 +30,11 @@ treemaincontrol.py \ treemodel.py \ treenode.py \ - treenodelist.py \ - treeopener.py \ treeoutput.py \ treeselection.py \ + treespotlist.py \ + treespot.py \ + treestructure.py \ treeview.py \ treewindow.py \ undo.py \ diff -Nru treeline-2.0.2/source/treeline.py treeline-3.0.1/source/treeline.py --- treeline-2.0.2/source/treeline.py 2015-10-03 18:20:31.000000000 +0000 +++ treeline-3.0.1/source/treeline.py 2018-09-14 01:59:15.000000000 +0000 @@ -4,7 +4,7 @@ # treeline.py, the main program file # # TreeLine, an information storage program -# Copyright (C) 2015, Douglas W. Bell +# Copyright (C) 2018, 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 @@ -13,7 +13,7 @@ #****************************************************************************** __progname__ = 'TreeLine' -__version__ = '2.0.2' +__version__ = '3.0.1' __author__ = 'Doug Bell' docPath = None # modified by install script if required @@ -24,11 +24,13 @@ import sys +import pathlib import os.path import argparse import locale import builtins -from PyQt4 import QtCore, QtGui +from PyQt5.QtCore import QCoreApplication, QTranslator +from PyQt5.QtWidgets import QApplication, qApp def loadTranslator(fileName, app): @@ -38,20 +40,20 @@ fileName -- the translator file to load app -- the main QApplication """ - translator = QtCore.QTranslator(app) - modPath = os.path.abspath(sys.path[0]) - if modPath.endswith('.zip'): # for py2exe - modPath = os.path.dirname(modPath) - path = os.path.join(modPath, translationPath) - result = translator.load(fileName, path) + translator = QTranslator(app) + modPath = pathlib.Path(sys.path[0]).resolve() + if modPath.is_file(): + modPath = modPath.parent + path = modPath / translationPath + result = translator.load(fileName, str(path)) if not result: - path = os.path.join(modPath, '..', translationPath) - result = translator.load(fileName, path) + path = modPath.parent / translationPath + result = translator.load(fileName, str(path)) if not result: - path = os.path.join(modPath, '..', 'i18n', translationPath) - result = translator.load(fileName, path) + path = modPath.parent / 'i18n' / translationPath + result = translator.load(fileName, str(path)) if result: - QtCore.QCoreApplication.installTranslator(translator) + QCoreApplication.installTranslator(translator) return True else: print('Warning: translation file "{0}" could not be loaded'. @@ -99,8 +101,8 @@ fileName = frame.f_code.co_filename finally: del frame - context = os.path.basename(os.path.splitext(fileName)[0]) - return QtCore.QCoreApplication.translate(context, text, comment) + context = pathlib.Path(fileName).stem + return QCoreApplication.translate(context, text, comment) def markNoTranslate(text, comment=''): """Dummy translation function, only used to mark text. @@ -119,25 +121,46 @@ return lang -def main(): - """Main event loop function for TreeLine +exceptDialog = None + +def handleException(excType, value, tb): + """Handle uncaught exceptions, show debug info to the user. + + Called from sys.excepthook. + Arguments: + excType -- execption class + value -- execption error text + tb -- the traceback object + """ + import miscdialogs + global exceptDialog + exceptDialog = miscdialogs.ExceptionDialog(excType, value, tb) + exceptDialog.show() + if not QApplication.activeWindow(): + qApp.exec_() # start event loop in case it's not running yet + + +if __name__ == '__main__': + """Main event loop for TreeLine """ - app = QtGui.QApplication(sys.argv) + app = QApplication(sys.argv) parser = argparse.ArgumentParser() parser.add_argument('--lang', help='language code for GUI translation') parser.add_argument('fileList', nargs='*', metavar='filename', help='input filename(s) to load') args = parser.parse_args() + # use abspath() - pathlib's resolve() replaces drive letters with map names + pathObjects = [pathlib.Path(os.path.abspath(path)) for path in + args.fileList] + # must setup translator before any treeline module imports lang = setupTranslator(app, args.lang) + import globalref - globalref.lang = lang globalref.localTextEncoding = locale.getpreferredencoding() + sys.excepthook = handleException + import treemaincontrol - treeMainControl = treemaincontrol.TreeMainControl(args.fileList) + treeMainControl = treemaincontrol.TreeMainControl(pathObjects) app.exec_() - - -if __name__ == '__main__': - main() diff -Nru treeline-2.0.2/source/treelocalcontrol.py treeline-3.0.1/source/treelocalcontrol.py --- treeline-2.0.2/source/treelocalcontrol.py 2015-03-22 19:44:44.000000000 +0000 +++ treeline-3.0.1/source/treelocalcontrol.py 2018-09-28 01:36:05.000000000 +0000 @@ -4,7 +4,7 @@ # treelocalcontrol.py, provides a class for the main tree commands # # TreeLine, an information storage program -# Copyright (C) 2015, Douglas W. Bell +# Copyright (C) 2018, 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 @@ -12,44 +12,50 @@ # but WITTHOUT ANY WARRANTY. See the included LICENSE file for details. #****************************************************************************** -import os.path -import io +import pathlib +import json +import os import sys import gzip -import zlib -from xml.etree import ElementTree -from PyQt4 import QtCore, QtGui +import operator +from itertools import chain +from PyQt5.QtCore import QObject, QTimer, Qt, pyqtSignal +from PyQt5.QtWidgets import (QAction, QActionGroup, QApplication, QDialog, + QFileDialog, QMenu, QMessageBox) import treemaincontrol +import treestructure import treemodel +import treeformats +import treenode import treewindow -import treeopener -import printdata +import exports import miscdialogs -import configdialog +import printdata import matheval +import spellcheck import undo import p3 -import exports -import spellcheck import globalref -class TreeLocalControl(QtCore.QObject): +class TreeLocalControl(QObject): """Class to handle controls local to a model/view combination. - + Provides methods for all local controls and stores a model & windows. """ - controlActivated = QtCore.pyqtSignal(QtCore.QObject) - controlClosed = QtCore.pyqtSignal(QtCore.QObject) - def __init__(self, allActions, filePath='', model=None, parent=None): + controlActivated = pyqtSignal(QObject) + controlClosed = pyqtSignal(QObject) + def __init__(self, allActions, fileObj=None, treeStruct=None, + forceNewWindow=False, parent=None): """Initialize the local tree controls. - - Use an imported model if given or open the file if path is given. + + Use an imported structure if given or open the file if path is given. Always creates a new window. Arguments: allActions -- a dict containing the upper level actions - filePath -- the file path or file object to open, if given - model -- an imported model file, if given + fileObj -- the path object or file object to open, if given + treeStruct -- an imported tree structure file, if given + forceNewWindow -- if True, use a new window regardless of option parent -- a parent object if given """ super().__init__(parent) @@ -57,102 +63,141 @@ self.spellCheckLang = '' self.allActions = allActions.copy() self.setupActions() - if hasattr(filePath, 'name'): - self.filePath = filePath.name - else: - self.filePath = filePath - if model: - self.model = model - elif filePath: - opener = treeopener.TreeOpener() - self.model = opener.readFile(filePath) - self.printData.restoreXmlAttrs(opener.rootAttr) - self.spellCheckLang = opener.rootAttr.get('spellchk', '') - self.model.mathZeroBlanks = (opener.rootAttr. - get('zeroblanks', 'y'). - startswith('y')) - if opener.duplicateIdList: - msg = _('Warning: duplicate Unique IDs found.\n') - if len(opener.duplicateIdList) > 10: - msg += _('Many Unique IDs were re-assigned.\n') - else: - msg += _('The following IDs were re-assigned:\n\t') - msg += '\n\t'.join(opener.duplicateIdList) - msg += _('\nInternal link targets could be affected.') - QtGui.QMessageBox.warning(None, 'TreeLine', msg) + self.filePathObj = (pathlib.Path(fileObj.name) if + hasattr(fileObj, 'read') else fileObj) + if treeStruct: + self.structure = treeStruct + elif fileObj: + if hasattr(fileObj, 'read'): + fileData = json.load(fileObj) + else: + with fileObj.open('r', encoding='utf-8') as f: + fileData = json.load(f) + self.structure = treestructure.TreeStructure(fileData) + self.printData.readData(fileData['properties']) + self.spellCheckLang = fileData['properties'].get('spellchk', '') else: - self.model = treemodel.TreeModel(True) - self.model.allModified.connect(self.updateAll) - self.model.nodeTitleModified.connect(self.updateRightViews) - self.model.formats.fileInfoFormat.updateFileInfo(self.filePath, - self.model.fileInfoNode) + self.structure = treestructure.TreeStructure(addDefaults=True) + fileInfoFormat = self.structure.treeFormats.fileInfoFormat + fileInfoFormat.updateFileInfo(self.filePathObj, + self.structure.fileInfoNode) + self.model = treemodel.TreeModel(self.structure) + self.model.treeModified.connect(self.updateRightViews) + self.modified = False self.imported = False self.compressed = False self.encrypted = False self.windowList = [] self.activeWindow = None - self.findReplaceNodeRef = (None, 0) - QtGui.QApplication.clipboard().dataChanged.connect(self. - updatePasteAvail) - self.updatePasteAvail() - self.model.undoList = undo.UndoRedoList(self.allActions['EditUndo'], - self) - self.model.redoList = undo.UndoRedoList(self.allActions['EditRedo'], - self) - self.model.undoList.altListRef = self.model.redoList - self.model.redoList.altListRef = self.model.undoList - self.autoSaveTimer = QtCore.QTimer(self) + self.findReplaceSpotRef = (None, 0) + QApplication.clipboard().dataChanged.connect(self.updateCommandsAvail) + self.structure.undoList = undo.UndoRedoList(self. + allActions['EditUndo'], + self) + self.structure.redoList = undo.UndoRedoList(self. + allActions['EditRedo'], + self) + self.structure.undoList.altListRef = self.structure.redoList + self.structure.redoList.altListRef = self.structure.undoList + self.autoSaveTimer = QTimer(self) self.autoSaveTimer.timeout.connect(self.autoSave) - self.windowNew() + if not globalref.mainControl.activeControl: + self.windowNew(offset=0) + elif forceNewWindow or globalref.genOptions['OpenNewWindow']: + self.windowNew() + else: + oldControl = globalref.mainControl.activeControl + window = oldControl.activeWindow + if len(oldControl.windowList) > 1: + oldControl.windowList.remove(window) + else: + oldControl.controlClosed.emit(oldControl) + window.resetTreeModel(self.model) + # set first spot current to match cases with a new window + window.treeView.selectionModel().setCurrentSpot(self.structure. + rootSpots()[0]) + self.setWindowSignals(window, True) + window.updateActions(self.allActions) + self.windowList.append(window) + window.setCaption(self.filePathObj) + self.activeWindow = window + if fileObj and self.structure.childRefErrorNodes: + msg = _('Warning - file corruption!\n' + 'Skipped bad child references in the following nodes:') + for node in self.structure.childRefErrorNodes: + msg += '\n "{}"'.format(node.title()) + QMessageBox.warning(self.activeWindow, 'TreeLine', msg) + self.structure.childRefErrorNodes = [] + + def setWindowSignals(self, window, removeOld=False): + """Setup signals between the window and this controller. + + Arguments: + window -- the window to link + removeOld -- if True, remove old signals + """ + if removeOld: + window.selectChanged.disconnect() + window.nodeModified.disconnect() + window.treeModified.disconnect() + window.winActivated.disconnect() + window.winClosing.disconnect() + window.selectChanged.connect(self.updateCommandsAvail) + window.nodeModified.connect(self.updateTreeNode) + window.treeModified.connect(self.updateTree) + window.winActivated.connect(self.setActiveWin) + window.winClosing.connect(self.checkWindowClose) + window.setExternalSignals() def updateTreeNode(self, node, setModified=True): - """Update the given node in all tree views. + """Update the full tree in all windows. + Also update right views in secondary windows. Arguments: node -- the node to be updated setModified -- if True, set the modified flag for this file """ - if node.setConditionalType(): + if node.setConditionalType(self.structure): self.activeWindow.updateRightViews(outputOnly=True) - if node.updateNodeMathFields(): + if (self.structure.treeFormats.mathFieldRefDict and + node.updateNodeMathFields(self.structure.treeFormats)): self.activeWindow.updateRightViews(outputOnly=True) - if globalref.genOptions.getValue('ShowMath'): + if globalref.genOptions['ShowMath']: self.activeWindow.refreshDataEditViews() for window in self.windowList: window.updateTreeNode(node) - if window != self.activeWindow: - window.updateRightViews() - if window.isFiltering(): + if window.treeFilterView: window.treeFilterView.updateItem(node) - pluginInterface = globalref.mainControl.pluginInterface - if pluginInterface: - pluginInterface.execCallback(pluginInterface.dataChangeCallbacks, - node) if setModified: self.setModified() def updateTree(self, setModified=True): - """Update the full tree in all tree views and set the modified flag. + """Update the full tree in all windows. + Also update right views in secondary windows. Arguments: setModified -- if True, set the modified flag for this file """ - QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) - typeChanges = self.model.root.setDescendantConditionalTypes() + QApplication.setOverrideCursor(Qt.WaitCursor) + typeChanges = 0 + if self.structure.treeFormats.conditionalTypes: + for node in self.structure.childList: + typeChanges += node.setDescendantConditionalTypes(self. + structure) self.updateAllMathFields() for window in self.windowList: window.updateTree() if window != self.activeWindow or typeChanges: window.updateRightViews() - if window.isFiltering(): + if window.treeFilterView: window.treeFilterView.updateContents() if setModified: self.setModified() - QtGui.QApplication.restoreOverrideCursor() + QApplication.restoreOverrideCursor() def updateRightViews(self, setModified=False): - """Update the right-hand view in all windows. + """Update the right-hand views in all windows. Arguments: setModified -- if True, set the modified flag for this file @@ -163,61 +208,89 @@ self.setModified() def updateAll(self, setModified=True): - """Update the full tree, right-hand views and set the modified flag. + """Update the full tree and right-hand views in all windows. Arguments: setModified -- if True, set the modified flag for this file """ - QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) - self.model.root.setDescendantConditionalTypes() + QApplication.setOverrideCursor(Qt.WaitCursor) + if self.structure.treeFormats.conditionalTypes: + for node in self.structure.childList: + node.setDescendantConditionalTypes(self.structure) self.updateAllMathFields() - if (globalref.mainControl.findConditionDialog and - globalref.mainControl.findConditionDialog.isVisible()): - globalref.mainControl.findConditionDialog.loadTypeNames() - if (globalref.mainControl.findReplaceDialog and - globalref.mainControl.findReplaceDialog.isVisible()): - globalref.mainControl.findReplaceDialog.loadTypeNames() - if (globalref.mainControl.filterConditionDialog and - globalref.mainControl.filterConditionDialog.isVisible()): - globalref.mainControl.filterConditionDialog.loadTypeNames() for window in self.windowList: window.updateTree() - if window.isFiltering(): + if window.treeFilterView: window.treeFilterView.updateContents() window.updateRightViews() self.updateCommandsAvail() if setModified: self.setModified() - QtGui.QApplication.restoreOverrideCursor() + # self.structure.debugCheck() + QApplication.restoreOverrideCursor() + + def updateAllMathFields(self): + """Recalculate all math fields in the entire tree. + """ + for eqnRefDict in self.structure.treeFormats.mathLevelList: + if list(eqnRefDict.values())[0][0].evalDirection != (matheval. + EvalDir. + upward): + for node in self.structure.descendantGen(): + for eqnRef in eqnRefDict.get(node.formatRef.name, []): + node.data[eqnRef.eqnField.name] = (eqnRef.eqnField. + equationValue(node)) + else: + spot = self.structure.structSpot().lastDescendantSpot() + while spot: + node = spot.nodeRef + for eqnRef in eqnRefDict.get(node.formatRef.name, []): + node.data[eqnRef.eqnField.name] = (eqnRef.eqnField. + equationValue(node)) + spot = spot.prevTreeSpot() def updateCommandsAvail(self): """Set commands available based on node selections. """ - selNodes = self.currentSelectionModel().selectedNodes() - notRoot = len(selNodes) and self.model.root not in selNodes - hasGrandParent = (notRoot and None not in - [node.parent.parent for node in selNodes]) - hasPrevSibling = (len(selNodes) and None not in - [node.prevSibling() for node in selNodes]) - hasNextSibling = (len(selNodes) and None not in - [node.nextSibling() for node in selNodes]) - hasChildren = (len(selNodes) and 0 not in - [len(node.childList) for node in selNodes]) - self.allActions['NodeRename'].setEnabled(len(selNodes) == 1) - self.allActions['NodeInsertBefore'].setEnabled(notRoot) - self.allActions['NodeInsertAfter'].setEnabled(notRoot) - self.allActions['NodeAddChild'].setEnabled(len(selNodes)) - self.allActions['NodeDelete'].setEnabled(notRoot) + selSpots = self.currentSelectionModel().selectedSpots() + hasSelect = len(selSpots) > 0 + rootSpots = [spot for spot in selSpots if not + spot.parentSpot.parentSpot] + hasPrevSibling = (len(selSpots) and None not in + [spot.prevSiblingSpot() for spot in selSpots]) + hasNextSibling = (len(selSpots) and None not in + [spot.nextSiblingSpot() for spot in selSpots]) + hasChildren = (sum([len(spot.nodeRef.childList) for spot in selSpots]) + > 0) + mime = QApplication.clipboard().mimeData() + hasData = len(mime.data('application/json')) > 0 + hasText = len(mime.data('text/plain')) > 0 + self.allActions['EditPaste'].setEnabled(hasData or hasText) + self.allActions['EditPasteChild'].setEnabled(hasData) + self.allActions['EditPasteBefore'].setEnabled(hasData and hasSelect) + self.allActions['EditPasteAfter'].setEnabled(hasData and hasSelect) + self.allActions['EditPasteCloneChild'].setEnabled(hasData) + self.allActions['EditPasteCloneBefore'].setEnabled(hasData and + hasSelect) + self.allActions['EditPasteCloneAfter'].setEnabled(hasData and + hasSelect) + self.allActions['NodeRename'].setEnabled(len(selSpots) == 1) + self.allActions['NodeInsertBefore'].setEnabled(hasSelect) + self.allActions['NodeInsertAfter'].setEnabled(hasSelect) + self.allActions['NodeDelete'].setEnabled(hasSelect and len(rootSpots) < + len(self.structure.childList)) self.allActions['NodeIndent'].setEnabled(hasPrevSibling) - self.allActions['NodeUnindent'].setEnabled(hasGrandParent) + self.allActions['NodeUnindent'].setEnabled(hasSelect and + len(rootSpots) == 0) self.allActions['NodeMoveUp'].setEnabled(hasPrevSibling) self.allActions['NodeMoveDown'].setEnabled(hasNextSibling) self.allActions['NodeMoveFirst'].setEnabled(hasPrevSibling) self.allActions['NodeMoveLast'].setEnabled(hasNextSibling) - self.allActions['DataNodeType'].setEnabled(len(selNodes)) - self.allActions['DataAddCategory'].setEnabled(hasChildren) + self.allActions['DataNodeType'].parent().setEnabled(hasSelect) self.allActions['DataFlatCategory'].setEnabled(hasChildren) - if self.activeWindow.isFiltering(): + self.allActions['DataAddCategory'].setEnabled(hasChildren) + self.allActions['DataSwapCategory'].setEnabled(hasChildren) + if self.activeWindow.treeFilterView: self.allActions['NodeInsertBefore'].setEnabled(False) self.allActions['NodeInsertAfter'].setEnabled(False) self.allActions['NodeAddChild'].setEnabled(False) @@ -227,65 +300,41 @@ self.allActions['NodeMoveDown'].setEnabled(False) self.allActions['NodeMoveFirst'].setEnabled(False) self.allActions['NodeMoveLast'].setEnabled(False) - if (globalref.mainControl.sortDialog and - globalref.mainControl.sortDialog.isVisible()): - globalref.mainControl.sortDialog.updateCommandsAvail() - if (globalref.mainControl.numberingDialog and - globalref.mainControl.numberingDialog.isVisible()): - globalref.mainControl.numberingDialog.updateCommandsAvail() - if (globalref.mainControl.findReplaceDialog and - globalref.mainControl.findReplaceDialog.isVisible()): - globalref.mainControl.findReplaceDialog.updateAvail() self.activeWindow.updateCommandsAvail() - pluginInterface = globalref.mainControl.pluginInterface - if pluginInterface: - pluginInterface.execCallback(pluginInterface.selectChangeCallbacks) - - def updatePasteAvail(self): - """Set paste available based on a signal. - """ - mime = QtGui.QApplication.clipboard().mimeData() - self.allActions['EditPaste'].setEnabled(len(mime.data('text/xml') or - mime.data('text/plain')) - > 0) - focusWidget = QtGui.QApplication.focusWidget() - if hasattr(focusWidget, 'pastePlain'): - focusWidget.updateActions() def updateWindowCaptions(self): """Update the caption for all windows. """ for window in self.windowList: - window.setCaption(self.filePath) + window.setCaption(self.filePathObj) - def updateAllMathFields(self): - """Recalculate all math fields in the entire tree. + def setModified(self, modified=True): + """Set the modified flag on this file and update commands available. + + Arguments: + modified -- the modified state to set """ - for eqnRefDict in self.model.formats.mathLevelList: - if list(eqnRefDict.values())[0][0].evalDirection != (matheval. - upward): - for node in self.model.root.descendantGen(): - for eqnRef in eqnRefDict.get(node.formatName, []): - node.data[eqnRef.eqnField.name] = (eqnRef.eqnField. - equationValue(node)) - else: - node = self.model.root.lastDescendant() - while node: - for eqnRef in eqnRefDict.get(node.formatName, []): - node.data[eqnRef.eqnField.name] = (eqnRef.eqnField. - equationValue(node)) - node = node.prevTreeNode() + if modified != self.modified: + self.modified = modified + self.allActions['FileSave'].setEnabled(modified) + self.resetAutoSave() + + def expandRootNodes(self, maxNum=5): + """Expand root node if there are fewer than the maximum. + + Arguments: + maxNum -- only expand if there are fewer root nodes than this. + """ + if len(self.structure.childList) < maxNum: + treeView = self.activeWindow.treeView + for spot in self.structure.rootSpots(): + treeView.expandSpot(spot) def currentSelectionModel(self): """Return the current tree's selection model. """ return self.activeWindow.treeView.selectionModel() - def currentTreeView(self): - """Return the current left-hand tree view. - """ - return self.activeWindow.treeView - def setActiveWin(self, window): """When a window is activated, stores it and emits a signal. @@ -295,46 +344,59 @@ self.activeWindow = window self.controlActivated.emit(self) self.updateCommandsAvail() - filterTextDialog = globalref.mainControl.filterTextDialog - if filterTextDialog and filterTextDialog.isVisible(): - filterTextDialog.updateAvail('', True) - filterConditionDialog = globalref.mainControl.filterConditionDialog - if filterConditionDialog and filterConditionDialog.isVisible(): - filterConditionDialog.updateFilterControls() - def windowActions(self, startNum=1, active=False): - """Return a list of window menu actions to select this file's windows. + def checkWindowClose(self, window): + """Check for modified files and delete ref when a window is closing. Arguments: - startNum -- where to start numbering the action names - active -- if True, activate the current active window + window -- the window being closed + """ + if len(self.windowList) > 1: + self.windowList.remove(window) + window.allowCloseFlag = True + # # keep ref until Qt window can fully close + # self.oldWindow = window + elif self.checkSaveChanges(): + window.allowCloseFlag = True + self.controlClosed.emit(self) + else: + window.allowCloseFlag = False + + def checkSaveChanges(self): + """Ask for save if doc modified, return True if OK to continue. + + Save this doc if directed. + Return True if not modified, if saved or if discarded. + Return False on cancel. + """ + if not self.modified or len(self.windowList) > 1: + return True + promptText = (_('Save changes to {}?').format(self.filePathObj) + if self.filePathObj else _('Save changes?')) + ans = QMessageBox.information(self.activeWindow, 'TreeLine', + promptText, + QMessageBox.Save | QMessageBox.Discard | + QMessageBox.Cancel, QMessageBox.Save) + if ans == QMessageBox.Save: + self.fileSave() + elif ans == QMessageBox.Cancel: + return False + else: + self.deleteAutoSaveFile() + return True + + def closeWindows(self): + """Close this control's windows prior to quiting the application. """ - actions = [] - maxActionPathLength = 30 - abbrevPath = self.filePath - if len(self.filePath) > maxActionPathLength: - truncLength = maxActionPathLength - 3 - pos = self.filePath.find(os.sep, len(self.filePath) - truncLength) - if pos < 0: - pos = len(self.filePath) - truncLength - abbrevPath = '...' + self.filePath[pos:] for window in self.windowList: - action = QtGui.QAction('&{0:d} {1}'.format(startNum, abbrevPath), - self, statusTip=self.filePath, - checkable=True) - action.triggered.connect(window.activateAndRaise) - if active and window == self.activeWindow: - action.setChecked(True) - actions.append(action) - startNum += 1 - return actions + window.close() def autoSave(self): """Save a backup file if appropriate. Called from the timer. """ - if self.filePath and not self.imported: + if self.filePathObj and not self.imported: self.fileSave(True) def resetAutoSave(self): @@ -343,7 +405,7 @@ Also delete old autosave files if file becomes unmodified. """ self.autoSaveTimer.stop() - minutes = globalref.genOptions.getValue('AutoSaveMinutes') + minutes = globalref.genOptions['AutoSaveMinutes'] if minutes and self.modified: self.autoSaveTimer.start(60000 * minutes) else: @@ -352,73 +414,40 @@ def deleteAutoSaveFile(self): """Delete an auto save file if it exists. """ - filePath = self.filePath + '~' - if self.filePath and os.path.exists(filePath): + filePath = pathlib.Path(str(self.filePathObj) + '~') + if self.filePathObj and filePath.is_file(): try: - os.remove(filePath) + filePath.unlink() except OSError: - QtGui.QMessageBox.warning(self.activeWindow, 'TreeLine', + QMessageBox.warning(self.activeWindow, 'TreeLine', _('Error - could not delete backup file {}'). format(filePath)) - def checkWindowClose(self, window): - """Check for modified files and delete ref when a window is closing. + def windowActions(self, startNum=1, active=False): + """Return a list of window menu actions to select this file's windows. Arguments: - window -- the window being closed - """ - if len(self.windowList) > 1: - self.windowList.remove(window) - window.allowCloseFlag = True - # keep ref until Qt window can fully close - self.oldWindow = window - elif self.promptModifiedOk(): - window.allowCloseFlag = True - self.controlClosed.emit(self) - else: - window.allowCloseFlag = False - - def promptModifiedOk(self): - """Ask for save if doc modified, return True if OK to continue. - - Save this doc if directed. - Return True if not modified or if saved or if discarded. - Return False on cancel. - """ - if not self.modified or len(self.windowList) > 1: - return True - promptText = (_('Save changes to {}?').format(self.filePath) if - self.filePath else _('Save changes?')) - ans = QtGui.QMessageBox.information(self.activeWindow, 'TreeLine', - promptText, - QtGui.QMessageBox.Save | - QtGui.QMessageBox.Discard | - QtGui.QMessageBox.Cancel, - QtGui.QMessageBox.Save) - if ans == QtGui.QMessageBox.Save: - self.fileSave() - elif ans == QtGui.QMessageBox.Cancel: - return False - else: # discard - self.setModified(False) - return True - - def closeWindows(self): - """Close this control's windows prior to quiting the application. + startNum -- where to start numbering the action names + active -- if True, activate the current active window """ + actions = [] + maxActionPathLength = 30 + abbrevPath = str(self.filePathObj) + if len(abbrevPath) > maxActionPathLength: + truncLength = maxActionPathLength - 3 + pos = abbrevPath.find(os.sep, len(abbrevPath) - truncLength) + if pos < 0: + pos = len(abbrevPath) - truncLength + abbrevPath = '...' + abbrevPath[pos:] for window in self.windowList: - window.close() - - def setModified(self, modified=True): - """Set the modified flag on this file and update commands available. - """ - if modified != self.modified: - self.modified = modified - self.allActions['FileSave'].setEnabled(modified) - self.resetAutoSave() - pluginInterface = globalref.mainControl.pluginInterface - if pluginInterface and modified: - pluginInterface.execCallback(pluginInterface.fileModCallbacks) + action = QAction('&{0:d} {1}'.format(startNum, abbrevPath), self, + statusTip=str(self.filePathObj), checkable=True) + action.triggered.connect(window.activateAndRaise) + if active and window == self.activeWindow: + action.setChecked(True) + actions.append(action) + startNum += 1 + return actions def setupActions(self): """Add the actions for contols at the local level. @@ -427,230 +456,265 @@ """ localActions = {} - fileSaveAct = QtGui.QAction(_('&Save'), self, toolTip=_('Save File'), - statusTip=_('Save the current file')) + fileSaveAct = QAction(_('&Save'), self, toolTip=_('Save File'), + statusTip=_('Save the current file')) fileSaveAct.setEnabled(False) fileSaveAct.triggered.connect(self.fileSave) localActions['FileSave'] = fileSaveAct - fileSaveAsAct = QtGui.QAction(_('Save &As...'), self, - statusTip=_('Save the file with a new name')) + fileSaveAsAct = QAction(_('Save &As...'), self, + statusTip=_('Save the file with a new name')) fileSaveAsAct.triggered.connect(self.fileSaveAs) localActions['FileSaveAs'] = fileSaveAsAct - fileExportAct = QtGui.QAction(_('&Export...'), self, + fileExportAct = QAction(_('&Export...'), self, statusTip=_('Export the file in various other formats')) fileExportAct.triggered.connect(self.fileExport) localActions['FileExport'] = fileExportAct - filePropertiesAct = QtGui.QAction(_('Prop&erties...'), self, + filePropertiesAct = QAction(_('Prop&erties...'), self, statusTip=_('Set file parameters like compression and encryption')) filePropertiesAct.triggered.connect(self.fileProperties) localActions['FileProperties'] = filePropertiesAct - filePrintSetupAct = QtGui.QAction(_('P&rint Setup...'), self, + filePrintSetupAct = QAction(_('P&rint Setup...'), self, statusTip=_('Set margins, page size and other printing options')) filePrintSetupAct.triggered.connect(self.printData.printSetup) localActions['FilePrintSetup'] = filePrintSetupAct - filePrintPreviewAct = QtGui.QAction(_('Print Pre&view...'), self, + filePrintPreviewAct = QAction(_('Print Pre&view...'), self, statusTip=_('Show a preview of printing results')) filePrintPreviewAct.triggered.connect(self.printData.printPreview) localActions['FilePrintPreview'] = filePrintPreviewAct - filePrintAct = QtGui.QAction(_('&Print...'), self, + filePrintAct = QAction(_('&Print...'), self, statusTip=_('Print tree output based on current options')) filePrintAct.triggered.connect(self.printData.filePrint) localActions['FilePrint'] = filePrintAct - filePrintPdfAct = QtGui.QAction(_('Print &to PDF...'), self, + filePrintPdfAct = QAction(_('Print &to PDF...'), self, statusTip=_('Export to PDF with current printing options')) filePrintPdfAct.triggered.connect(self.printData.filePrintPdf) localActions['FilePrintPdf'] = filePrintPdfAct - editUndoAct = QtGui.QAction(_('&Undo'), self, - statusTip=_('Undo the previous action')) + editUndoAct = QAction(_('&Undo'), self, + statusTip=_('Undo the previous action')) editUndoAct.triggered.connect(self.editUndo) localActions['EditUndo'] = editUndoAct - editRedoAct = QtGui.QAction(_('&Redo'), self, - statusTip=_('Redo the previous undo')) + editRedoAct = QAction(_('&Redo'), self, + statusTip=_('Redo the previous undo')) editRedoAct.triggered.connect(self.editRedo) localActions['EditRedo'] = editRedoAct - editCutAct = QtGui.QAction(_('Cu&t'), self, + editCutAct = QAction(_('Cu&t'), self, statusTip=_('Cut the branch or text to the clipboard')) editCutAct.triggered.connect(self.editCut) localActions['EditCut'] = editCutAct - editCopyAct = QtGui.QAction(_('&Copy'), self, + editCopyAct = QAction(_('&Copy'), self, statusTip=_('Copy the branch or text to the clipboard')) editCopyAct.triggered.connect(self.editCopy) localActions['EditCopy'] = editCopyAct - editPasteAct = QtGui.QAction(_('&Paste'), self, + editPasteAct = QAction(_('&Paste'), self, statusTip=_('Paste nodes or text from the clipboard')) editPasteAct.triggered.connect(self.editPaste) localActions['EditPaste'] = editPasteAct - editPastePlainAct = QtGui.QAction(_('P&aste Plain Text'), self, + editPastePlainAct = QAction(_('Pa&ste Plain Text'), self, statusTip=_('Paste non-formatted text from the clipboard')) editPastePlainAct.setEnabled(False) localActions['EditPastePlain'] = editPastePlainAct - editBoldAct = QtGui.QAction(_('&Bold Font'), self, - statusTip=_('Set the current or selected font to bold'), - checkable=True) - editBoldAct.setEnabled(False) - localActions['EditBoldFont'] = editBoldAct - - editItalicAct = QtGui.QAction(_('&Italic Font'), self, - statusTip=_('Set the current or selected font to italic'), - checkable=True) - editItalicAct.setEnabled(False) - localActions['EditItalicFont'] = editItalicAct - - editUnderlineAct = QtGui.QAction(_('U&nderline Font'), self, - statusTip=_('Set the current or selected font to underline'), - checkable=True) - editUnderlineAct.setEnabled(False) - localActions['EditUnderlineFont'] = editUnderlineAct - - title = _('&Font Size') - key = globalref.keyboardOptions.getValue('EditFontSize') - if not key.isEmpty(): - title = '{0} ({1})'.format(title, key.toString()) - self.fontSizeSubMenu = QtGui.QMenu(title, - statusTip=_('Set size of the current or selected text')) - sizeActions = QtGui.QActionGroup(self) - for size in (_('Small'), _('Default'), _('Large'), _('Larger'), - _('Largest')): - action = QtGui.QAction(size, sizeActions) - action.setCheckable(True) - self.fontSizeSubMenu.addActions(sizeActions.actions()) - self.fontSizeSubMenu.setEnabled(False) - fontSizeContextMenuAct = QtGui.QAction(_('Set Font Size'), - self.fontSizeSubMenu) - localActions['EditFontSize'] = fontSizeContextMenuAct - - editColorAct = QtGui.QAction(_('Font C&olor...'), self, - statusTip=_('Set the color of the current or selected text')) - editColorAct.setEnabled(False) - localActions['EditFontColor'] = editColorAct - - editExtLinkAct = QtGui.QAction(_('&External Link...'), self, - statusTip=_('Add or modify an extrnal web link')) - editExtLinkAct.setEnabled(False) - localActions['EditExtLink'] = editExtLinkAct - - editIntLinkAct = QtGui.QAction(_('Internal &Link...'), self, - statusTip=_('Add or modify an internal node link')) - editIntLinkAct.setEnabled(False) - localActions['EditIntLink'] = editIntLinkAct + editPasteChildAct = QAction(_('Paste C&hild'), self, + statusTip=_('Paste a child node from the clipboard')) + editPasteChildAct.triggered.connect(self.editPasteChild) + localActions['EditPasteChild'] = editPasteChildAct + + editPasteBeforeAct = QAction(_('Paste Sibling &Before'), self, + statusTip=_('Paste a sibling before selection')) + editPasteBeforeAct.triggered.connect(self.editPasteBefore) + localActions['EditPasteBefore'] = editPasteBeforeAct + + editPasteAfterAct = QAction(_('Paste Sibling &After'), self, + statusTip=_('Paste a sibling after selection')) + editPasteAfterAct.triggered.connect(self.editPasteAfter) + localActions['EditPasteAfter'] = editPasteAfterAct + + editPasteCloneChildAct = QAction(_('Paste Cl&oned Child'), self, + statusTip=_('Paste a child clone from the clipboard')) + editPasteCloneChildAct.triggered.connect(self.editPasteCloneChild) + localActions['EditPasteCloneChild'] = editPasteCloneChildAct + + editPasteCloneBeforeAct = QAction(_('Paste Clo&ned Sibling Before'), + self, statusTip=_('Paste a sibling clone before selection')) + editPasteCloneBeforeAct.triggered.connect(self.editPasteCloneBefore) + localActions['EditPasteCloneBefore'] = editPasteCloneBeforeAct + + editPasteCloneAfterAct = QAction(_('Paste Clone&d Sibling After'), + self, statusTip=_('Paste a sibling clone after selection')) + editPasteCloneAfterAct.triggered.connect(self.editPasteCloneAfter) + localActions['EditPasteCloneAfter'] = editPasteCloneAfterAct - editClearFormatAct = QtGui.QAction(_('Clear For&matting'), self, - statusTip=_('Clear current or selected text formatting')) - editClearFormatAct.setEnabled(False) - localActions['EditClearFormat'] = editClearFormatAct - - nodeRenameAct = QtGui.QAction(_('&Rename'), self, + nodeRenameAct = QAction(_('&Rename'), self, statusTip=_('Rename the current tree entry title')) nodeRenameAct.triggered.connect(self.nodeRename) localActions['NodeRename'] = nodeRenameAct - nodeInBeforeAct = QtGui.QAction(_('Insert Sibling &Before'), self, + nodeAddChildAct = QAction(_('Add &Child'), self, + statusTip=_('Add new child to selected parent')) + nodeAddChildAct.triggered.connect(self.nodeAddChild) + localActions['NodeAddChild'] = nodeAddChildAct + + nodeInBeforeAct = QAction(_('Insert Sibling &Before'), self, statusTip=_('Insert new sibling before selection')) nodeInBeforeAct.triggered.connect(self.nodeInBefore) localActions['NodeInsertBefore'] = nodeInBeforeAct - nodeInAfterAct = QtGui.QAction(_('Insert Sibling &After'), self, + nodeInAfterAct = QAction(_('Insert Sibling &After'), self, statusTip=_('Insert new sibling after selection')) nodeInAfterAct.triggered.connect(self.nodeInAfter) localActions['NodeInsertAfter'] = nodeInAfterAct - nodeAddChildAct = QtGui.QAction(_('Add &Child'), self, - statusTip=_('Add new child to selected parent')) - nodeAddChildAct.triggered.connect(self.nodeAddChild) - localActions['NodeAddChild'] = nodeAddChildAct - - nodeDeleteAct = QtGui.QAction(_('&Delete Node'), self, - statusTip=_('Delete the selected nodes')) + nodeDeleteAct = QAction(_('&Delete Node'), self, + statusTip=_('Delete the selected nodes')) nodeDeleteAct.triggered.connect(self.nodeDelete) localActions['NodeDelete'] = nodeDeleteAct - nodeIndentAct = QtGui.QAction(_('&Indent Node'), self, + nodeIndentAct = QAction(_('&Indent Node'), self, statusTip=_('Indent the selected nodes')) nodeIndentAct.triggered.connect(self.nodeIndent) localActions['NodeIndent'] = nodeIndentAct - nodeUnindentAct = QtGui.QAction(_('&Unindent Node'), self, + nodeUnindentAct = QAction(_('&Unindent Node'), self, statusTip=_('Unindent the selected nodes')) nodeUnindentAct.triggered.connect(self.nodeUnindent) localActions['NodeUnindent'] = nodeUnindentAct - nodeMoveUpAct = QtGui.QAction(_('&Move Up'), self, + nodeMoveUpAct = QAction(_('&Move Up'), self, statusTip=_('Move the selected nodes up')) nodeMoveUpAct.triggered.connect(self.nodeMoveUp) localActions['NodeMoveUp'] = nodeMoveUpAct - nodeMoveDownAct = QtGui.QAction(_('M&ove Down'), self, + nodeMoveDownAct = QAction(_('M&ove Down'), self, statusTip=_('Move the selected nodes down')) nodeMoveDownAct.triggered.connect(self.nodeMoveDown) localActions['NodeMoveDown'] = nodeMoveDownAct - nodeMoveFirstAct = QtGui.QAction(_('Move &First'), self, + nodeMoveFirstAct = QAction(_('Move &First'), self, statusTip=_('Move the selected nodes to be the first children')) nodeMoveFirstAct.triggered.connect(self.nodeMoveFirst) localActions['NodeMoveFirst'] = nodeMoveFirstAct - nodeMoveLastAct = QtGui.QAction(_('Move &Last'), self, + nodeMoveLastAct = QAction(_('Move &Last'), self, statusTip=_('Move the selected nodes to be the last children')) nodeMoveLastAct.triggered.connect(self.nodeMoveLast) localActions['NodeMoveLast'] = nodeMoveLastAct title = _('&Set Node Type') - key = globalref.keyboardOptions.getValue('DataNodeType') + key = globalref.keyboardOptions['DataNodeType'] if not key.isEmpty(): title = '{0} ({1})'.format(title, key.toString()) - self.typeSubMenu = QtGui.QMenu(title, + self.typeSubMenu = QMenu(title, statusTip=_('Set the node type for selected nodes')) self.typeSubMenu.aboutToShow.connect(self.loadTypeSubMenu) self.typeSubMenu.triggered.connect(self.dataSetType) - typeContextMenuAct = QtGui.QAction(_('Set Node Type'), self.typeSubMenu) + typeContextMenuAct = QAction(_('Set Node Type'), self.typeSubMenu) typeContextMenuAct.triggered.connect(self.showTypeContextMenu) localActions['DataNodeType'] = typeContextMenuAct - dataCopyTypeAct = QtGui.QAction(_('Copy Types from &File...'), self, + dataCopyTypeAct = QAction(_('Copy Types from &File...'), self, statusTip=_('Copy the configuration from another TreeLine file')) dataCopyTypeAct.triggered.connect(self.dataCopyType) localActions['DataCopyType'] = dataCopyTypeAct - dataFlatCatAct = QtGui.QAction(_('Flatten &by Category'), self, + dataCloneMatchesAct = QAction(_('Clone All &Matched Nodes'), self, + statusTip=_('Convert all matching nodes into clones')) + dataCloneMatchesAct.triggered.connect(self.dataCloneMatches) + localActions['DataCloneMatches'] = dataCloneMatchesAct + + dataDetachClonesAct = QAction(_('&Detach Clones'), self, + statusTip=_('Detach all cloned nodes in current branches')) + dataDetachClonesAct.triggered.connect(self.dataDetachClones) + localActions['DataDetachClones'] = dataDetachClonesAct + + dataFlatCatAct = QAction(_('Flatten &by Category'), self, statusTip=_('Collapse descendants by merging fields')) dataFlatCatAct.triggered.connect(self.dataFlatCategory) localActions['DataFlatCategory'] = dataFlatCatAct - dataAddCatAct = QtGui.QAction(_('Add Category &Level...'), self, + dataAddCatAct = QAction(_('Add Category &Level...'), self, statusTip=_('Insert category nodes above children')) dataAddCatAct.triggered.connect(self.dataAddCategory) localActions['DataAddCategory'] = dataAddCatAct - dataFlatLinkAct = QtGui.QAction(_('Flatten b&y Link...'), self, - statusTip=_('Collapse descendants and insert parent links')) - dataFlatLinkAct.triggered.connect(self.dataFlatLink) - localActions['DataFlatLink'] = dataFlatLinkAct - - dataArrangeLinkAct = QtGui.QAction(_('&Arrange by Link...'), self, - statusTip=_('Arrange descendants using parent links')) - dataArrangeLinkAct.triggered.connect(self.dataArrangeLink) - localActions['DataArrangeLink'] = dataArrangeLinkAct + dataSwapCatAct = QAction(_('S&wap Category Levels'), self, + statusTip=_('Swap child and grandchild category nodes')) + dataSwapCatAct.triggered.connect(self.dataSwapCategory) + localActions['DataSwapCategory'] = dataSwapCatAct - toolsSpellCheckAct = QtGui.QAction(_('&Spell Check...'), self, - statusTip=_('Spell check the tree\')s text data')) + toolsSpellCheckAct = QAction(_('&Spell Check...'), self, + statusTip=_('Spell check the tree\'s text data')) toolsSpellCheckAct.triggered.connect(self.toolsSpellCheck) localActions['ToolsSpellCheck'] = toolsSpellCheckAct - winNewAct = QtGui.QAction(_('&New Window'), self, + formatBoldAct = QAction(_('&Bold Font'), self, + statusTip=_('Set the current or selected font to bold'), + checkable=True) + formatBoldAct.setEnabled(False) + localActions['FormatBoldFont'] = formatBoldAct + + formatItalicAct = QAction(_('&Italic Font'), self, + statusTip=_('Set the current or selected font to italic'), + checkable=True) + formatItalicAct.setEnabled(False) + localActions['FormatItalicFont'] = formatItalicAct + + formatUnderlineAct = QAction(_('U&nderline Font'), self, + statusTip=_('Set the current or selected font to underline'), + checkable=True) + formatUnderlineAct.setEnabled(False) + localActions['FormatUnderlineFont'] = formatUnderlineAct + + title = _('&Font Size') + key = globalref.keyboardOptions['FormatFontSize'] + if not key.isEmpty(): + title = '{0} ({1})'.format(title, key.toString()) + self.fontSizeSubMenu = QMenu(title, + statusTip=_('Set size of the current or selected text')) + sizeActions = QActionGroup(self) + for size in (_('Small'), _('Default'), _('Large'), _('Larger'), + _('Largest')): + action = QAction(size, sizeActions) + action.setCheckable(True) + self.fontSizeSubMenu.addActions(sizeActions.actions()) + self.fontSizeSubMenu.setEnabled(False) + fontSizeContextMenuAct = QAction(_('Set Font Size'), + self.fontSizeSubMenu) + localActions['FormatFontSize'] = fontSizeContextMenuAct + + formatColorAct = QAction(_('Font C&olor...'), self, + statusTip=_('Set the color of the current or selected text')) + formatColorAct.setEnabled(False) + localActions['FormatFontColor'] = formatColorAct + + formatExtLinkAct = QAction(_('&External Link...'), self, + statusTip=_('Add or modify an extrnal web link')) + formatExtLinkAct.setEnabled(False) + localActions['FormatExtLink'] = formatExtLinkAct + + formatIntLinkAct = QAction(_('Internal &Link...'), self, + statusTip=_('Add or modify an internal node link')) + formatIntLinkAct.setEnabled(False) + localActions['FormatIntLink'] = formatIntLinkAct + + formatClearFormatAct = QAction(_('Clear For&matting'), self, + statusTip=_('Clear current or selected text formatting')) + formatClearFormatAct.setEnabled(False) + localActions['FormatClearFormat'] = formatClearFormatAct + + winNewAct = QAction(_('&New Window'), self, statusTip=_('Open a new window for the same file')) winNewAct.triggered.connect(self.windowNew) localActions['WinNewWindow'] = winNewAct @@ -659,9 +723,15 @@ icon = globalref.toolIcons.getIcon(name.lower()) if icon: action.setIcon(icon) - key = globalref.keyboardOptions.getValue(name) + key = globalref.keyboardOptions[name] if not key.isEmpty(): action.setShortcut(key) + typeIcon = globalref.toolIcons.getIcon('DataNodeType'.lower()) + if typeIcon: + self.typeSubMenu.setIcon(typeIcon) + fontIcon = globalref.toolIcons.getIcon('FormatFontSize'.lower()) + if fontIcon: + self.fontSizeSubMenu.setIcon(fontIcon) self.allActions.update(localActions) def fileSave(self, backupFile=False): @@ -670,157 +740,157 @@ Arguments: backupFile -- if True, write auto-save backup file instead """ - if not self.filePath or self.imported: + if not self.filePathObj or self.imported: self.fileSaveAs() return - QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) - saveFilePath = self.filePath + QApplication.setOverrideCursor(Qt.WaitCursor) + savePathObj = self.filePathObj if backupFile: - saveFilePath += '~' - rootElement = self.model.root.elementXml() - rootElement.attrib.update(self.model.formats.xmlAttr()) - rootElement.attrib.update(self.printData.xmlAttr()) + savePathObj = pathlib.Path(str(savePathObj) + '~') + else: + self.structure.purgeOldFieldData() + fileData = self.structure.fileData() + fileData['properties'].update(self.printData.fileData()) if self.spellCheckLang: - rootElement.set('spellchk', self.spellCheckLang) - if not self.model.mathZeroBlanks: - rootElement.set('zeroblanks', 'n') - elementTree = ElementTree.ElementTree(rootElement) - try: - # use binary for regular files to avoid newline translation - fileIO = io.BytesIO() - elementTree.write(fileIO, 'utf-8', True) - data = fileIO.getvalue() - fileIO.close() + fileData['properties']['spellchk'] = self.spellCheckLang + if not self.compressed and not self.encrypted: + try: + with savePathObj.open('w', encoding='utf-8', + newline='\n') as f: + json.dump(fileData, f, indent=0, sort_keys=True) + except IOError: + QApplication.restoreOverrideCursor() + QMessageBox.warning(self.activeWindow, 'TreeLine', + _('Error - could not write to {}'). + format(savePathObj)) + return + else: + data = json.dumps(fileData, indent=0, sort_keys=True).encode() if self.compressed: data = gzip.compress(data) if self.encrypted: password = (globalref.mainControl.passwords. - get(self.filePath, '')) + get(self.filePathObj, '')) if not password: - QtGui.QApplication.restoreOverrideCursor() + QApplication.restoreOverrideCursor() dialog = miscdialogs.PasswordDialog(True, '', self.activeWindow) - if dialog.exec_() != QtGui.QDialog.Accepted: + if dialog.exec_() != QDialog.Accepted: return - QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) + QApplication.setOverrideCursor(Qt.WaitCursor) password = dialog.password if miscdialogs.PasswordDialog.remember: globalref.mainControl.passwords[self. - filePath] = password + filePathObj] = password data = (treemaincontrol.encryptPrefix + p3.p3_encrypt(data, password.encode())) - with open(saveFilePath, 'wb') as f: - f.write(data) - except IOError: - QtGui.QApplication.restoreOverrideCursor() - QtGui.QMessageBox.warning(self.activeWindow, 'TreeLine', - _('Error - could not write to {}'). - format(saveFilePath)) - else: - QtGui.QApplication.restoreOverrideCursor() - if not backupFile: - self.model.formats.fileInfoFormat.updateFileInfo(self.filePath, - self.model.fileInfoNode) - self.setModified(False) - self.imported = False - self.activeWindow.statusBar().showMessage(_('File saved'), - 3000) - pluginInterface = globalref.mainControl.pluginInterface - if pluginInterface: - pluginInterface.execCallback(pluginInterface. - fileSaveCallbacks) + try: + with savePathObj.open('wb') as f: + f.write(data) + except IOError: + QApplication.restoreOverrideCursor() + QMessageBox.warning(self.activeWindow, 'TreeLine', + _('Error - could not write to {}'). + format(savePathObj)) + return + QApplication.restoreOverrideCursor() + if not backupFile: + fileInfoFormat = self.structure.treeFormats.fileInfoFormat + fileInfoFormat.updateFileInfo(self.filePathObj, + self.structure.fileInfoNode) + self.setModified(False) + self.imported = False + self.activeWindow.statusBar().showMessage(_('File saved'), 3000) def fileSaveAs(self): """Prompt for a new file name and save the file. """ - oldFilePath = self.filePath + oldPathObj = self.filePathObj oldModifiedFlag = self.modified oldImportFlag = self.imported self.modified = True self.imported = False - filters = ';;'.join((globalref.fileFilters['trl'], - globalref.fileFilters['trlgz'], - globalref.fileFilters['trlenc'])) - if self.encrypted: - initFilter = globalref.fileFilters['trlenc'] - elif self.compressed: - initFilter = globalref.fileFilters['trlgz'] - else: - initFilter = globalref.fileFilters['trl'] - defaultFilePath = globalref.mainControl.defaultFilePath() - defaultFilePath = os.path.splitext(defaultFilePath)[0] + '.trl' - self.filePath, selectFilter = (QtGui.QFileDialog. - getSaveFileNameAndFilter(self.activeWindow, - _('TreeLine - Save As'), - defaultFilePath, - filters, initFilter)) - if self.filePath: - if not os.path.splitext(self.filePath)[1]: - self.filePath += '.trl' + filters = ';;'.join((globalref.fileFilters['trlnsave'], + globalref.fileFilters['trlngz'], + globalref.fileFilters['trlnenc'])) + initFilter = globalref.fileFilters['trlnsave'] + defaultPathObj = globalref.mainControl.defaultPathObj() + defaultPathObj = defaultPathObj.with_suffix('.trln') + newPath, selectFilter = (QFileDialog. + getSaveFileName(self.activeWindow, + _('TreeLine - Save As'), + str(defaultPathObj), + filters, initFilter)) + if newPath: + self.filePathObj = pathlib.Path(newPath) + if not self.filePathObj.suffix: + self.filePathObj = self.filePathObj.with_suffix('.trln') if selectFilter != initFilter: self.compressed = (selectFilter == - globalref.fileFilters['trlgz']) + globalref.fileFilters['trlngz']) self.encrypted = (selectFilter == - globalref.fileFilters['trlenc']) + globalref.fileFilters['trlnenc']) self.fileSave() if not self.modified: - globalref.mainControl.recentFiles.addItem(self.filePath) + globalref.mainControl.recentFiles.addItem(self.filePathObj) self.updateWindowCaptions() return - self.filePath = oldFilePath + self.filePathObj = oldPathObj self.modified = oldModifiedFlag self.imported = oldImportFlag def fileExport(self): """Export the file in various other formats. """ - self.currentSelectionModel().sortSelection() - exportControl = exports.ExportControl(self.model.root, - self.currentSelectionModel(). - selectedNodes(), + exportControl = exports.ExportControl(self.structure, + self.currentSelectionModel(), globalref.mainControl. - defaultFilePath()) + defaultPathObj(), self.printData) try: exportControl.interactiveExport() except IOError: - QtGui.QApplication.restoreOverrideCursor() - QtGui.QMessageBox.warning(self.activeWindow, 'TreeLine', - _('Error - could not write to file')) + QApplication.restoreOverrideCursor() + QMessageBox.warning(self.activeWindow, 'TreeLine', + _('Error - could not write to file')) def fileProperties(self): """Show dialog to set file parameters like compression and encryption. """ - origZeroBlanks = self.model.mathZeroBlanks + origZeroBlanks = self.structure.mathZeroBlanks dialog = miscdialogs.FilePropertiesDialog(self, self.activeWindow) - if dialog.exec_() == QtGui.QDialog.Accepted: + if dialog.exec_() == QDialog.Accepted: self.setModified() - if self.model.mathZeroBlanks != origZeroBlanks: + if self.structure.mathZeroBlanks != origZeroBlanks: self.updateAll(False) def editUndo(self): """Undo the previous action and update the views. """ - self.model.undoList.undo() + self.structure.undoList.undo() self.updateAll(False) def editRedo(self): """Redo the previous undo and update the views. """ - self.model.redoList.undo() + self.structure.redoList.undo() self.updateAll(False) def editCut(self): """Cut the branch or text to the clipboard. """ - widget = QtGui.QApplication.focusWidget() + widget = QApplication.focusWidget() try: if widget.hasSelectedText(): widget.cut() return except AttributeError: pass - self.currentSelectionModel().selectedNodes().copyTree() - self.nodeDelete() + self.currentSelectionModel().copySelectedNodes() + selSpots = self.currentSelectionModel().selectedSpots() + rootSpots = [spot for spot in selSpots if not + spot.parentSpot.parentSpot] + if selSpots and len(rootSpots) < len(self.structure.childList): + self.nodeDelete() def editCopy(self): """Copy the branch or text to the clipboard. @@ -828,44 +898,94 @@ Copy from any selection in non-focused output view, or copy from any focused editor, or copy from tree. """ + widgets = [QApplication.focusWidget()] splitter = self.activeWindow.rightTabs.currentWidget() if splitter == self.activeWindow.outputSplitter: - for view in splitter.children(): - try: - if view.hasSelectedText(): - view.copy() - return - except AttributeError: - pass - widget = QtGui.QApplication.focusWidget() - try: - if widget.hasSelectedText(): - widget.copy() - return - except AttributeError: - pass - self.currentSelectionModel().selectedNodes().copyTree() + widgets[0:0] = [splitter.widget(0), splitter.widget(1)] + for widget in widgets: + try: + if widget.hasSelectedText(): + widget.copy() + return + except AttributeError: + pass + self.currentSelectionModel().copySelectedNodes() def editPaste(self): """Paste nodes or text from the clipboard. """ if self.activeWindow.treeView.hasFocus(): - if (self.currentSelectionModel().selectedNodes(). - pasteMimeData(QtGui.QApplication.clipboard().mimeData())): - for node in self.currentSelectionModel().selectedNodes(): - node.expandInView() - self.updateAll() + self.editPasteChild() else: - widget = QtGui.QApplication.focusWidget() + widget = QApplication.focusWidget() try: widget.paste() except AttributeError: pass + def editPasteChild(self): + """Paste a child node from the clipboard. + """ + if (self.currentSelectionModel().selectedSpots(). + pasteChild(self.structure, self.activeWindow.treeView)): + self.updateAll() + globalref.mainControl.updateConfigDialog() + + def editPasteBefore(self): + """Paste a sibling before selection. + """ + treeView = self.activeWindow.treeView + selSpots = self.currentSelectionModel().selectedSpots() + saveSpots = chain.from_iterable([spot.parentSpot.childSpots() for + spot in selSpots]) + expandState = treeView.savedExpandState(saveSpots) + if selSpots.pasteSibling(self.structure): + treeView.restoreExpandState(expandState) + self.currentSelectionModel().selectSpots(selSpots, False) + self.updateAll() + globalref.mainControl.updateConfigDialog() + + def editPasteAfter(self): + """Paste a sibling after selection. + """ + treeView = self.activeWindow.treeView + selSpots = self.currentSelectionModel().selectedSpots() + saveSpots = chain.from_iterable([spot.parentSpot.childSpots() for + spot in selSpots]) + expandState = treeView.savedExpandState(saveSpots) + if selSpots.pasteSibling(self.structure, False): + treeView.restoreExpandState(expandState) + self.currentSelectionModel().selectSpots(selSpots, False) + self.updateAll() + globalref.mainControl.updateConfigDialog() + + def editPasteCloneChild(self): + """Paste a child clone from the clipboard. + """ + if (self.currentSelectionModel().selectedSpots(). + pasteCloneChild(self.structure, self.activeWindow.treeView)): + self.updateAll() + + def editPasteCloneBefore(self): + """Paste a sibling clone before selection. + """ + selSpots = self.currentSelectionModel().selectedSpots() + if selSpots.pasteCloneSibling(self.structure): + self.currentSelectionModel().selectSpots(selSpots, False) + self.updateAll() + + def editPasteCloneAfter(self): + """Paste a sibling clone after selection. + """ + selSpots = self.currentSelectionModel().selectedSpots() + if selSpots.pasteCloneSibling(self.structure, False): + self.currentSelectionModel().selectSpots(selSpots, False) + self.updateAll() + def nodeRename(self): """Start the rename editor in the selected tree node. """ - if self.activeWindow.isFiltering(): + if self.activeWindow.treeFilterView: self.activeWindow.treeFilterView.editItem(self.activeWindow. treeFilterView. currentItem()) @@ -874,158 +994,169 @@ self.activeWindow.treeView.edit(self.currentSelectionModel(). currentIndex()) + def nodeAddChild(self): + """Add new child to selected parent. + """ + self.activeWindow.treeView.endEditing() + selSpots = self.currentSelectionModel().selectedSpots() + newSpots = selSpots.addChild(self.structure, + self.activeWindow.treeView) + self.updateAll() + if globalref.genOptions['RenameNewNodes']: + self.currentSelectionModel().selectSpots(newSpots) + if len(newSpots) == 1: + self.activeWindow.treeView.edit(newSpots[0].index(self.model)) + def nodeInBefore(self): """Insert new sibling before selection. """ - self.activeWindow.treeView.endEditing() - undo.ChildListUndo(self.model.undoList, [node.parent for node in - self.currentSelectionModel(). - selectedNodes()]) - newNodes = [] - for node in self.currentSelectionModel().selectedNodes(): - newNodes.append(node.parent.addNewChild(node, True)) - if globalref.genOptions.getValue('RenameNewNodes'): - self.currentSelectionModel().selectNodes(newNodes, False) - if len(newNodes) == 1: - self.updateAll() - self.activeWindow.treeView.edit(newNodes[0].index()) - return + treeView = self.activeWindow.treeView + treeView.endEditing() + selSpots = self.currentSelectionModel().selectedSpots() + saveSpots = chain.from_iterable([spot.parentSpot.childSpots() for + spot in selSpots]) + expandState = treeView.savedExpandState(saveSpots) + newSpots = selSpots.insertSibling(self.structure) + treeView.restoreExpandState(expandState) self.updateAll() + if globalref.genOptions['RenameNewNodes']: + self.currentSelectionModel().selectSpots(newSpots) + if len(newSpots) == 1: + treeView.edit(newSpots[0].index(self.model)) def nodeInAfter(self): """Insert new sibling after selection. """ - self.activeWindow.treeView.endEditing() - undo.ChildListUndo(self.model.undoList, [node.parent for node in - self.currentSelectionModel(). - selectedNodes()]) - newNodes = [] - for node in self.currentSelectionModel().selectedNodes(): - newNodes.append(node.parent.addNewChild(node, False)) - if globalref.genOptions.getValue('RenameNewNodes'): - self.currentSelectionModel().selectNodes(newNodes, False) - if len(newNodes) == 1: - self.updateAll() - self.activeWindow.treeView.edit(newNodes[0].index()) - return - self.updateAll() - - def nodeAddChild(self): - """Add new child to selected parent. - """ - self.activeWindow.treeView.endEditing() - undo.ChildListUndo(self.model.undoList, self.currentSelectionModel(). - selectedNodes()) - newNodes = [] - for node in self.currentSelectionModel().selectedNodes(): - newNodes.append(node.addNewChild()) - node.expandInView() - if globalref.genOptions.getValue('RenameNewNodes'): - self.currentSelectionModel().selectNodes(newNodes, False) - if len(newNodes) == 1: - self.updateAll() - self.activeWindow.treeView.edit(newNodes[0].index()) - return + treeView = self.activeWindow.treeView + treeView.endEditing() + selSpots = self.currentSelectionModel().selectedSpots() + saveSpots = chain.from_iterable([spot.parentSpot.childSpots() for + spot in selSpots]) + expandState = treeView.savedExpandState(saveSpots) + newSpots = selSpots.insertSibling(self.structure, False) + treeView.restoreExpandState(expandState) self.updateAll() + if globalref.genOptions['RenameNewNodes']: + self.currentSelectionModel().selectSpots(newSpots) + if len(newSpots) == 1: + treeView.edit(newSpots[0].index(self.model)) def nodeDelete(self): """Delete the selected nodes. """ - selNodes = self.currentSelectionModel().selectedNodes() - if not selNodes or self.model.root in selNodes: - return - # gather next selected node in increasing order of desirability - nextSel = [node.parent for node in selNodes] - undo.ChildListUndo(self.model.undoList, nextSel) - nextSel.extend([node.prevSibling() for node in selNodes]) - nextSel.extend([node.nextSibling() for node in selNodes]) - while not nextSel[-1] or nextSel[-1] in selNodes: - del nextSel[-1] - for node in selNodes: - node.delete() - self.currentSelectionModel().selectNode(nextSel[-1], False) - self.updateAll() + treeView = self.activeWindow.treeView + selSpots = self.currentSelectionModel().selectedBranchSpots() + if selSpots: + # collapse deleted items to avoid crash + for spot in selSpots: + treeView.collapseSpot(spot) + # clear hover to avoid crash if deleted child item was hovered over + self.activeWindow.treeView.clearHover() + # clear selection to avoid invalid multiple selection bug + self.currentSelectionModel().selectSpots([], False) + # clear selections in other windows that are about to be deleted + for window in self.windowList: + if window != self.activeWindow: + selectModel = window.treeView.selectionModel() + ancestors = set() + for spot in selectModel.selectedBranchSpots(): + ancestors.update(set(spot.spotChain())) + if ancestors & set(selSpots): + selectModel.selectSpots([], False) + saveSpots = chain.from_iterable([spot.parentSpot.childSpots() for + spot in selSpots]) + saveSpots = set(saveSpots) - set(selSpots) + expandState = treeView.savedExpandState(saveSpots) + nextSel = selSpots.delete(self.structure) + treeView.restoreExpandState(expandState) + self.currentSelectionModel().selectSpots([nextSel]) + self.updateAll() def nodeIndent(self): """Indent the selected nodes. + + Makes them children of their previous siblings. """ - self.currentSelectionModel().sortSelection() - selNodes = self.currentSelectionModel().selectedNodes() - parentList = [node.parent for node in selNodes] - siblingList = [node.prevSibling() for node in selNodes] - undo.ChildListUndo(self.model.undoList, parentList + siblingList) - for node in selNodes: - node.indent() - node.parent.expandInView() - self.currentSelectionModel().selectNodes(selNodes, False) + treeView = self.activeWindow.treeView + selSpots = self.currentSelectionModel().selectedSpots() + saveSpots = chain.from_iterable([spot.parentSpot.childSpots() for + spot in selSpots]) + expandState = treeView.savedExpandState(saveSpots) + newSpots = selSpots.indent(self.structure) + treeView.restoreExpandState(expandState) + for spot in selSpots: + treeView.expandSpot(spot.parentSpot) + self.currentSelectionModel().selectSpots(newSpots, False) self.updateAll() def nodeUnindent(self): """Unindent the selected nodes. + + Makes them their parent's next sibling. """ - self.currentSelectionModel().sortSelection() - selNodes = self.currentSelectionModel().selectedNodes() - parentList = [node.parent for node in selNodes] - grandParentList = [node.parent for node in parentList] - undo.ChildListUndo(self.model.undoList, parentList + grandParentList) - for node in reversed(selNodes): - node.unindent() - self.currentSelectionModel().selectNodes(selNodes, False) + treeView = self.activeWindow.treeView + selSpots = self.currentSelectionModel().selectedSpots() + saveSpots = chain.from_iterable([spot.parentSpot.childSpots() for + spot in selSpots]) + expandState = treeView.savedExpandState(saveSpots) + newSpots = selSpots.unindent(self.structure) + treeView.restoreExpandState(expandState) + self.currentSelectionModel().selectSpots(newSpots, False) self.updateAll() def nodeMoveUp(self): """Move the selected nodes upward in the sibling list. """ - self.currentSelectionModel().sortSelection() - selNodes = self.currentSelectionModel().selectedNodes() - undo.ChildListUndo(self.model.undoList, - [node.parent for node in selNodes]) - for node in selNodes: - pos = node.parent.childList.index(node) - del node.parent.childList[pos] - node.parent.childList.insert(pos - 1, node) - self.currentSelectionModel().selectNodes(selNodes, False) + treeView = self.activeWindow.treeView + selSpots = self.currentSelectionModel().selectedSpots() + saveSpots = chain.from_iterable([(spot, spot.prevSiblingSpot()) for + spot in selSpots]) + expandState = treeView.savedExpandState(saveSpots) + selSpots.move(self.structure) self.updateAll() + treeView.restoreExpandState(expandState) + self.currentSelectionModel().selectSpots(selSpots) def nodeMoveDown(self): """Move the selected nodes downward in the sibling list. """ - self.currentSelectionModel().sortSelection() - selNodes = self.currentSelectionModel().selectedNodes() - undo.ChildListUndo(self.model.undoList, - [node.parent for node in selNodes]) - for node in reversed(selNodes): - pos = node.parent.childList.index(node) - del node.parent.childList[pos] - node.parent.childList.insert(pos + 1, node) - self.currentSelectionModel().selectNodes(selNodes, False) + treeView = self.activeWindow.treeView + selSpots = self.currentSelectionModel().selectedSpots() + saveSpots = chain.from_iterable([(spot, spot.nextSiblingSpot()) for + spot in selSpots]) + expandState = treeView.savedExpandState(saveSpots) + selSpots.move(self.structure, False) self.updateAll() + treeView.restoreExpandState(expandState) + self.currentSelectionModel().selectSpots(selSpots) def nodeMoveFirst(self): """Move the selected nodes to be the first children. """ - self.currentSelectionModel().sortSelection() - selNodes = self.currentSelectionModel().selectedNodes() - undo.ChildListUndo(self.model.undoList, - [node.parent for node in selNodes]) - for node in reversed(selNodes): - node.parent.childList.remove(node) - node.parent.childList.insert(0, node) - self.currentSelectionModel().selectNodes(selNodes, False) + treeView = self.activeWindow.treeView + selSpots = self.currentSelectionModel().selectedSpots() + saveSpots = chain.from_iterable([(spot, + spot.parentSpot.childSpots()[0]) + for spot in selSpots]) + expandState = treeView.savedExpandState(saveSpots) + selSpots.moveToEnd(self.structure) self.updateAll() + treeView.restoreExpandState(expandState) + self.currentSelectionModel().selectSpots(selSpots) def nodeMoveLast(self): """Move the selected nodes to be the last children. """ - self.currentSelectionModel().sortSelection() - selNodes = self.currentSelectionModel().selectedNodes() - undo.ChildListUndo(self.model.undoList, - [node.parent for node in selNodes]) - for node in selNodes: - node.parent.childList.remove(node) - node.parent.childList.append(node) - self.currentSelectionModel().selectNodes(selNodes, False) + treeView = self.activeWindow.treeView + selSpots = self.currentSelectionModel().selectedSpots() + saveSpots = chain.from_iterable([(spot, + spot.parentSpot.childSpots()[-1]) + for spot in selSpots]) + expandState = treeView.savedExpandState(saveSpots) + selSpots.moveToEnd(self.structure, False) self.updateAll() + treeView.restoreExpandState(expandState) + self.currentSelectionModel().selectSpots(selSpots) def dataSetType(self, action): """Change the type of selected nodes based on a menu selection. @@ -1035,20 +1166,33 @@ """ newType = action.toolTip() # gives menu name without the accelerator nodes = [node for node in self.currentSelectionModel().selectedNodes() - if node.formatName != newType] + if node.formatRef.name != newType] if nodes: - undo.TypeUndo(self.model.undoList, nodes) + undo.TypeUndo(self.structure.undoList, nodes) for node in nodes: - node.changeDataType(newType) - self.updateTreeNode(node) + node.changeDataType(self.structure.treeFormats[newType]) self.updateAll() def loadTypeSubMenu(self): """Update type select submenu with type names and check marks. """ - selectTypes = {node.formatName for node in - self.currentSelectionModel().selectedNodes()} - typeNames = self.model.formats.typeNames() + selectTypeNames = set() + typeLimitNames = set() + for node in self.currentSelectionModel().selectedNodes(): + selectTypeNames.add(node.formatRef.name) + if typeLimitNames is not None: + for parent in node.parents(): + limit = (parent.formatRef.childTypeLimit if + parent.formatRef else None) + if (not limit or (typeLimitNames and + limit != typeLimitNames)): + typeLimitNames = None + elif typeLimitNames is not None: + typeLimitNames = limit + if typeLimitNames: + typeNames = sorted(list(typeLimitNames)) + else: + typeNames = self.structure.treeFormats.typeNames() self.typeSubMenu.clear() usedShortcuts = [] for name in typeNames: @@ -1062,7 +1206,7 @@ text = name action = self.typeSubMenu.addAction(text) action.setCheckable(True) - if name in selectTypes: + if name in selectTypeNames: action.setChecked(True) def showTypeContextMenu(self): @@ -1073,151 +1217,218 @@ def dataCopyType(self): """Copy the configuration from another TreeLine file. """ - filters = ';;'.join((globalref.fileFilters['trl'], - globalref.fileFilters['trlgz'], - globalref.fileFilters['trlenc'], + filters = ';;'.join((globalref.fileFilters['trlnv3'], globalref.fileFilters['all'])) - fileName = QtGui.QFileDialog.getOpenFileName(self.activeWindow, - _('TreeLine - Open Configuration File'), - globalref.mainControl.defaultFilePath(True), - filters) + fileName, selectFilter = QFileDialog.getOpenFileName(self.activeWindow, + _('TreeLine - Open Configuration File'), + str(globalref.mainControl. + defaultPathObj(True)), filters) if not fileName: return - QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) - tmpModel = None + QApplication.setOverrideCursor(Qt.WaitCursor) + newStructure = None try: - opener = treeopener.TreeOpener() - tmpModel = opener.readFile(fileName) - QtGui.QApplication.restoreOverrideCursor() + with open(fileName, 'r', encoding='utf-8') as f: + fileData = json.load(f) + newStructure = treestructure.TreeStructure(fileData, + addSpots=False) except IOError: pass - except treeopener.ParseError: - QtGui.QApplication.restoreOverrideCursor() - compressed = False - encrypted = False + except (ValueError, KeyError, TypeError): fileObj = open(fileName, 'rb') - # decompress before decrypt to support TreeLine 1.4 and earlier - fileObj, compressed = (globalref.mainControl. - decompressFile(fileName, fileObj)) - fileObj, encrypted = globalref.mainControl.decryptFile(fileName, - fileObj) - if fileObj: - if encrypted and not compressed: - fileObj, compressed = (globalref.mainControl. - decompressFile(fileName, fileObj)) - if compressed or encrypted: - try: - QtGui.QApplication.setOverrideCursor(QtCore.Qt. - WaitCursor) - tmpModel = opener.readFile(fileObj) - except (treeopener.ParseError, zlib.error): - pass - fileObj.close() - if not tmpModel: - QtGui.QApplication.restoreOverrideCursor() - QtGui.QMessageBox.warning(self.activeWindow, 'TreeLine', - _('Error - could not read file {0}'). - format(fileName)) + fileObj, encrypted = globalref.mainControl.decryptFile(fileObj) + if not fileObj: + QApplication.restoreOverrideCursor() + return + fileObj, compressed = globalref.mainControl.decompressFile(fileObj) + if compressed or encrypted: + try: + textFileObj = io.TextIOWrapper(fileObj, encoding='utf-8') + fileData = json.load(textFileObj) + textFileObj.close() + newStructure = treestructure.TreeStructure(fileData, + addSpots=False) + except (ValueError, KeyError, TypeError): + pass + fileObj.close() + if not newStructure: + QApplication.restoreOverrideCursor() + QMessageBox.warning(self.activeWindow, 'TreeLine', + _('Error - could not read file {0}'). + format(fileName)) return - self.model.formats.copyTypes(tmpModel.formats, self.model) - QtGui.QApplication.restoreOverrideCursor() - pluginInterface = globalref.mainControl.pluginInterface - if pluginInterface: - pluginInterface.execCallback(pluginInterface.formatChangeCallbacks) + undo.FormatUndo(self.structure.undoList, self.structure.treeFormats, + treeformats.TreeFormats()) + for nodeFormat in newStructure.treeFormats.values(): + self.structure.treeFormats.addTypeIfMissing(nodeFormat) + QApplication.restoreOverrideCursor() + self.updateAll() + globalref.mainControl.updateConfigDialog() + + def dataCloneMatches(self): + """Convert all matching nodes into clones. + """ + QApplication.setOverrideCursor(Qt.WaitCursor) + selSpots = self.currentSelectionModel().selectedSpots() + titleDict = {} + for node in self.structure.nodeDict.values(): + titleDict.setdefault(node.title(), set()).add(node) + undoObj = undo.ChildListUndo(self.structure.undoList, + self.structure.childList, addBranch=True) + numChanges = 0 + for node in self.structure.descendantGen(): + matches = titleDict[node.title()] + if len(matches) > 1: + matches = matches.copy() + matches.remove(node) + for matchedNode in matches: + if node.isIdentical(matchedNode): + numChanges += 1 + if len(matchedNode.spotRefs) > len(node.spotRefs): + tmpNode = node + node = matchedNode + matchedNode = tmpNode + numSpots = len(matchedNode.spotRefs) + for parent in matchedNode.parents(): + pos = parent.childList.index(matchedNode) + parent.childList[pos] = node + node.addSpotRef(parent) + for child in matchedNode.descendantGen(): + if len(child.spotRefs) <= numSpots: + titleDict[child.title()].remove(child) + self.structure.removeNodeDictRef(child) + child.removeInvalidSpotRefs(False) + if numChanges: + msg = _('Converted {0} branches into clones').format(numChanges) + self.currentSelectionModel().selectSpots([spot for spot in selSpots + if spot.isValid()], + False) + self.updateAll() + else: + msg = _('No identical nodes found') + self.structure.undoList.removeLastUndo(undoObj) + QApplication.restoreOverrideCursor() + QMessageBox.information(self.activeWindow, 'TreeLine', msg) + + def dataDetachClones(self): + """Detach all cloned nodes in current branches. + """ + QApplication.setOverrideCursor(Qt.WaitCursor) + selSpots = self.currentSelectionModel().selectedBranchSpots() + undoObj = undo.ChildListUndo(self.structure.undoList, + [spot.parentSpot.nodeRef for spot in + selSpots], addBranch=True) + numChanges = 0 + for branchSpot in selSpots: + for spot in branchSpot.spotDescendantGen(): + if (len(spot.nodeRef.spotRefs) > 1 and + len(spot.parentSpot.nodeRef.spotRefs) <= 1): + numChanges += 1 + linkedNode = spot.nodeRef + linkedNode.spotRefs.remove(spot) + newNode = treenode.TreeNode(linkedNode.formatRef) + newNode.data = linkedNode.data.copy() + newNode.childList = linkedNode.childList[:] + newNode.spotRefs.add(spot) + spot.nodeRef = newNode + parent = spot.parentSpot.nodeRef + pos = parent.childList.index(linkedNode) + parent.childList[pos] = newNode + self.structure.addNodeDictRef(newNode) + if numChanges: + self.updateAll() + else: + self.structure.undoList.removeLastUndo(undoObj) + QApplication.restoreOverrideCursor() def dataFlatCategory(self): """Collapse descendant nodes by merging fields. Overwrites data in any fields with the same name. """ - QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) - selectList = self.currentSelectionModel().uniqueBranches() - undo.BranchFormatUndo(self.model.undoList, selectList, - self.model.formats) - origFormats = self.model.undoList[-1].treeFormats + QApplication.setOverrideCursor(Qt.WaitCursor) + selectList = self.currentSelectionModel().selectedBranches() + undo.ChildDataUndo(self.structure.undoList, selectList, True, + self.structure.treeFormats) + origFormats = self.structure.undoList[-1].treeFormats for node in selectList: - node.flatChildCategory(origFormats) + node.flatChildCategory(origFormats, self.structure) self.updateAll() - dialog = globalref.mainControl.configDialog - if dialog and dialog.isVisible(): - dialog.reset() - QtGui.QApplication.restoreOverrideCursor() + globalref.mainControl.updateConfigDialog() + QApplication.restoreOverrideCursor() def dataAddCategory(self): """Insert category nodes above children. """ - selectList = self.currentSelectionModel().uniqueBranches() + selectList = self.currentSelectionModel().selectedBranches() children = [] for node in selectList: children.extend(node.childList) - fieldList = self.model.formats.commonFields(children) + fieldList = self.structure.treeFormats.commonFields(children) if not fieldList: - QtGui.QMessageBox.warning(self.activeWindow, 'TreeLine', - _('Cannot expand without common fields')) + QMessageBox.warning(self.activeWindow, 'TreeLine', + _('Cannot expand without common fields')) return dialog = miscdialogs.FieldSelectDialog(_('Category Fields'), _('Select fields for new level'), fieldList, self.activeWindow) - if dialog.exec_() != QtGui.QDialog.Accepted: + if dialog.exec_() != QDialog.Accepted: return - QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) - undo.BranchFormatUndo(self.model.undoList, selectList, - self.model.formats) + QApplication.setOverrideCursor(Qt.WaitCursor) + undo.ChildDataUndo(self.structure.undoList, selectList, True, + self.structure.treeFormats) for node in selectList: - node.addChildCategory(dialog.selectedFields) + node.addChildCategory(dialog.selectedFields, self.structure) self.updateAll() - dialog = globalref.mainControl.configDialog - if dialog and dialog.isVisible(): - dialog.reset() - QtGui.QApplication.restoreOverrideCursor() - - def dataFlatLink(self): - """Collapse descendants and insert parent links. - """ - dialog = configdialog.NameEntryDialog(_('Flatten by Link'), - _('Enter a new field name for ' - 'parent links'), '', '', [], - self.activeWindow) - if dialog.exec_() != QtGui.QDialog.Accepted: - return - QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) - newFieldName = dialog.text - selectList = self.currentSelectionModel().uniqueBranches() - undo.BranchFormatUndo(self.model.undoList, selectList, - self.model.formats) - for node in selectList: - node.flatChildLink(newFieldName) + globalref.mainControl.updateConfigDialog() + QApplication.restoreOverrideCursor() + + def dataSwapCategory(self): + """Swap child and grandchild category nodes. + """ + QApplication.setOverrideCursor(Qt.WaitCursor) + selectList = self.currentSelectionModel().selectedBranches() + undo.ChildListUndo(self.structure.undoList, selectList, addBranch=True) + doneNodes = set() + for ancestor in selectList: + for child in ancestor.childList[:]: + for catNode in child.childList[:]: + if catNode not in doneNodes: + doneNodes.add(catNode) + childSpots = [spot.parentSpot for spot in + catNode.spotRefs] + childSpots.sort(key=operator.methodcaller('sortKey')) + children = [childSpot.nodeRef for childSpot in + childSpots] + catNode.childList[0:0] = children + for ancestor in selectList: + position = 0 + doneNodes = set() + for child in ancestor.childList[:]: + for catNode in child.childList[:]: + if catNode not in doneNodes: + doneNodes.add(catNode) + for catSpot in catNode.spotRefs: + child = catSpot.parentSpot.nodeRef + child.childList = [] + if child in ancestor.childList: + position = ancestor.childList.index(child) + del ancestor.childList[position] + ancestor.childList.insert(position, catNode) + position += 1 + catNode.addSpotRef(ancestor) + catNode.removeInvalidSpotRefs() self.updateAll() - dialog = globalref.mainControl.configDialog - if dialog and dialog.isVisible(): - dialog.reset() - QtGui.QApplication.restoreOverrideCursor() + QApplication.restoreOverrideCursor() - def dataArrangeLink(self): - """Arrange nodes using parent links. + def toolsSpellCheck(self): + """Spell check the tree text data. """ - selectList = self.currentSelectionModel().uniqueBranches() - children = [] - for node in selectList: - children.extend(node.childList) - fieldList = self.model.formats.commonFields(children) - if not fieldList: - QtGui.QMessageBox.warning(self.activeWindow, 'TreeLine', - _('Cannot expand without common fields')) - return - linkField, ok = QtGui.QInputDialog.getItem(self.activeWindow, - _('Link Field'), - _('Select field with links ' - 'to parents'), fieldList, - 0, False) - if not ok: + try: + spellCheckOp = spellcheck.SpellCheckOperation(self) + except spellcheck.SpellCheckError: return - QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) - undo.BranchUndo(self.model.undoList, selectList) - for node in selectList: - node.arrangeByLink(linkField) - self.updateAll() - QtGui.QApplication.restoreOverrideCursor() + spellCheckOp.spellCheck() def findNodesByWords(self, wordList, titlesOnly=False, forward=True): """Search for and select nodes that match the word list criteria. @@ -1229,24 +1440,29 @@ titleOnly -- search only in the title text if True forward -- next if True, previous if False """ - currentNode = self.currentSelectionModel().currentNode() - node = currentNode + currentSpot = self.currentSelectionModel().currentSpot() + spot = currentSpot while True: - if self.activeWindow.isFiltering(): - node = self.activeWindow.treeFilterView.nextPrevNode(node, + if self.activeWindow.treeFilterView: + spot = self.activeWindow.treeFilterView.nextPrevSpot(spot, forward) else: if forward: - node = node.nextTreeNode(True) + spot = spot.nextTreeSpot(True) else: - node = node.prevTreeNode(True) - if node is currentNode: + spot = spot.prevTreeSpot(True) + if spot is currentSpot: return False - if node.wordSearch(wordList, titlesOnly): - self.currentSelectionModel().selectNode(node, True, True) + if spot.nodeRef.wordSearch(wordList, titlesOnly, spot): + self.currentSelectionModel().selectSpots([spot], True, True) rightView = self.activeWindow.rightParentView() + if not rightView: + # view update required if (and only if) view is newly shown + QApplication.processEvents() + rightView = self.activeWindow.rightParentView() if rightView: rightView.highlightSearch(wordList=wordList) + QApplication.processEvents() return True def findNodesByRegExp(self, regExpList, titlesOnly=False, forward=True): @@ -1259,22 +1475,56 @@ titleOnly -- search only in the title text if True forward -- next if True, previous if False """ - currentNode = self.currentSelectionModel().currentNode() - node = currentNode + currentSpot = self.currentSelectionModel().currentSpot() + spot = currentSpot while True: - if forward: - node = node.nextTreeNode(True) + if self.activeWindow.treeFilterView: + spot = self.activeWindow.treeFilterView.nextPrevSpot(spot, + forward) else: - node = node.prevTreeNode(True) - if node is currentNode: + if forward: + spot = spot.nextTreeSpot(True) + else: + spot = spot.prevTreeSpot(True) + if spot is currentSpot: return False - if node.regExpSearch(regExpList, titlesOnly): - self.currentSelectionModel().selectNode(node, True, True) + if spot.nodeRef.regExpSearch(regExpList, titlesOnly, spot): + self.currentSelectionModel().selectSpots([spot], True, True) rightView = self.activeWindow.rightParentView() + if not rightView: + # view update required if (and only if) view is newly shown + QApplication.processEvents() + rightView = self.activeWindow.rightParentView() if rightView: rightView.highlightSearch(regExpList=regExpList) return True + def findNodesByCondition(self, conditional, forward=True): + """Search for and select nodes that match the regular exp criteria. + + Called from the conditional find dialog. + Returns True if found, otherwise False. + Arguments: + conditional -- the Conditional object to be evaluated + forward -- next if True, previous if False + """ + currentSpot = self.currentSelectionModel().currentSpot() + spot = currentSpot + while True: + if self.activeWindow.treeFilterView: + spot = self.activeWindow.treeFilterView.nextPrevSpot(spot, + forward) + else: + if forward: + spot = spot.nextTreeSpot(True) + else: + spot = spot.prevTreeSpot(True) + if spot is currentSpot: + return False + if conditional.evaluate(spot.nodeRef): + self.currentSelectionModel().selectSpots([spot], True, True) + return True + def findNodesForReplace(self, searchText='', regExpObj=None, typeName='', fieldName='', forward=True): """Search for & select nodes that match the criteria prior to replace. @@ -1288,12 +1538,12 @@ fieldName -- if given, only find matches under this type name forward -- next if True, previous if False """ - currentNode = self.currentSelectionModel().currentNode() - lastFoundNode, currentNumMatches = self.findReplaceNodeRef + currentSpot = self.currentSelectionModel().currentSpot() + lastFoundSpot, currentNumMatches = self.findReplaceSpotRef numMatches = currentNumMatches - if lastFoundNode is not currentNode: + if lastFoundSpot is not currentSpot: numMatches = 0 - node = currentNode + spot = currentSpot if not forward: if numMatches == 0: numMatches = -1 # find last one if backward @@ -1302,32 +1552,38 @@ else: numMatches -= 2 while True: - matchedField, numMatches, fieldPos = node.searchReplace(searchText, - regExpObj, - numMatches, - typeName, - fieldName) + matchedField, numMatches, fieldPos = (spot.nodeRef. + searchReplace(searchText, + regExpObj, + numMatches, + typeName, + fieldName)) if matchedField: - fieldNum = node.nodeFormat().fieldNames().index(matchedField) - self.currentSelectionModel().selectNode(node, True, True) + fieldNum = (spot.nodeRef.formatRef.fieldNames(). + index(matchedField)) + self.currentSelectionModel().selectSpots([spot], True, True) self.activeWindow.rightTabs.setCurrentWidget(self.activeWindow. editorSplitter) dataView = self.activeWindow.rightParentView() + if not dataView: + # view update required if (and only if) view is newly shown + QApplication.processEvents() + dataView = self.activeWindow.rightParentView() if dataView: dataView.highlightMatch(searchText, regExpObj, fieldNum, fieldPos - 1) - self.findReplaceNodeRef = (node, numMatches) + self.findReplaceSpotRef = (spot, numMatches) return True - if self.activeWindow.isFiltering(): - node = self.activeWindow.treeFilterView.nextPrevNode(node, + if self.activeWindow.treeFilterView: + node = self.activeWindow.treeFilterView.nextPrevSpot(spot, forward) else: if forward: - node = node.nextTreeNode(True) + spot = spot.nextTreeSpot(True) else: - node = node.prevTreeNode(True) - if node is currentNode and currentNumMatches == 0: - self.findReplaceNodeRef = (None, 0) + spot = spot.prevTreeSpot(True) + if spot is currentSpot and currentNumMatches == 0: + self.findReplaceSpotRef = (None, 0) return False numMatches = 0 if forward else -1 @@ -1344,26 +1600,28 @@ fieldName -- if given, only find matches under this type name replaceText -- if not None, replace a match with this string """ - node = self.currentSelectionModel().currentNode() - lastFoundNode, numMatches = self.findReplaceNodeRef + spot = self.currentSelectionModel().currentSpot() + lastFoundSpot, numMatches = self.findReplaceSpotRef if numMatches > 0: numMatches -= 1 - if lastFoundNode is not node: + if lastFoundSpot is not spot: numMatches = 0 - dataUndo = undo.DataUndo(self.model.undoList, node) - matchedField, num1, num2 = node.searchReplace(searchText, regExpObj, - numMatches, typeName, - fieldName, replaceText) + dataUndo = undo.DataUndo(self.structure.undoList, spot.nodeRef) + matchedField, num1, num2 = (spot.nodeRef. + searchReplace(searchText, regExpObj, + numMatches, typeName, + fieldName, replaceText)) if ((searchText and searchText in replaceText) or (regExpObj and r'\g<0>' in replaceText) or (regExpObj and regExpObj.pattern.startswith('(') and regExpObj.pattern.endswith(')') and r'\1' in replaceText)): numMatches += 1 # check for recursive matches - self.findReplaceNodeRef = (node, numMatches) + self.findReplaceSpotRef = (spot, numMatches) if matchedField: - self.updateRightViews(True) + self.updateTreeNode(spot.nodeRef) + self.updateRightViews() return True - self.model.undoList.removeLastUndo(dataUndo) + self.structure.undoList.removeLastUndo(dataUndo) return False def replaceAll(self, searchText='', regExpObj=None, typeName='', @@ -1379,82 +1637,39 @@ fieldName -- if given, only find matches under this type name replaceText -- if not None, replace a match with this string """ - QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) - dataUndo = undo.BranchUndo(self.model.undoList, self.model.root) + QApplication.setOverrideCursor(Qt.WaitCursor) + dataUndo = undo.DataUndo(self.structure.undoList, + self.structure.childList, addBranch=True) totalMatches = 0 - for node in self.model.root.descendantGen(): + for node in self.structure.nodeDict.values(): field, matchQty, num = node.searchReplace(searchText, regExpObj, 0, typeName, fieldName, replaceText, True) totalMatches += matchQty - self.findReplaceNodeRef = (None, 0) + self.findReplaceSpotRef = (None, 0) if totalMatches > 0: self.updateAll(True) else: - self.model.undoList.removeLastUndo(dataUndo) - QtGui.QApplication.restoreOverrideCursor() + self.structure.undoList.removeLastUndo(dataUndo) + QApplication.restoreOverrideCursor() return totalMatches - def findNodesByCondition(self, conditional, forward=True): - """Search for and select nodes that match the regular exp criteria. + def windowNew(self, checked=False, offset=30): + """Open a new window for this file. - Called from the conditional find dialog. - Returns True if found, otherwise False. Arguments: - conditional -- the Conditional object to be evaluated - forward -- next if True, previous if False - """ - currentNode = self.currentSelectionModel().currentNode() - node = currentNode - while True: - if forward: - node = node.nextTreeNode(True) - else: - node = node.prevTreeNode(True) - if node is currentNode: - return False - if conditional.evaluate(node): - self.currentSelectionModel().selectNode(node, True, True) - return True - - def toolsSpellCheck(self): - """Spell check the tree text data. - """ - try: - spellCheckOp = spellcheck.SpellCheckOperation(self) - except spellcheck.SpellCheckError: - return - spellCheckOp.spellCheck() - - def windowNew(self): - """Open a new window for this file. + checked -- unused parameter needed by QAction signal + offset -- location offset from previously saved position """ window = treewindow.TreeWindow(self.model, self.allActions) - window.selectChanged.connect(self.updateCommandsAvail) - window.nodeModified.connect(self.updateTreeNode) - window.treeModified.connect(self.updateTree) - window.winActivated.connect(self.setActiveWin) - window.winClosing.connect(self.checkWindowClose) + self.setWindowSignals(window) self.windowList.append(window) - self.updateWindowCaptions() + window.setCaption(self.filePathObj) oldControl = globalref.mainControl.activeControl - if not oldControl: - if globalref.genOptions.getValue('SaveWindowGeom'): - # restore window geometry for first window - window.restoreWindowGeom() - elif (globalref.genOptions.getValue('OpenNewWindow') or - len(self.windowList) > 1): - # cascade additional windows - oldControl.activeWindow.saveWindowGeom() - window.restoreWindowGeom(30) - else: - # close old window for single-window operation + if oldControl: oldControl.activeWindow.saveWindowGeom() - oldControl.activeWindow.close() - window.restoreWindowGeom() + window.restoreWindowGeom(offset) self.activeWindow = window + self.expandRootNodes() window.show() window.updateRightViews() - pluginInterface = globalref.mainControl.pluginInterface - if pluginInterface and oldControl == self: - pluginInterface.execCallback(pluginInterface.newWindowCallbacks) diff -Nru treeline-2.0.2/source/treemaincontrol.py treeline-3.0.1/source/treemaincontrol.py --- treeline-2.0.2/source/treemaincontrol.py 2015-05-15 01:59:30.000000000 +0000 +++ treeline-3.0.1/source/treemaincontrol.py 2018-09-27 01:06:43.000000000 +0000 @@ -4,7 +4,7 @@ # treemaincontrol.py, provides a class for global tree commands # # TreeLine, an information storage program -# Copyright (C) 2015, Douglas W. Bell +# Copyright (C) 2018, 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 @@ -13,24 +13,28 @@ #****************************************************************************** import sys -import os.path +import pathlib +import ast import io import gzip import zlib -from PyQt4 import QtCore, QtGui, QtNetwork +import platform +from PyQt5.QtCore import QIODevice, QObject, Qt, PYQT_VERSION_STR, qVersion +from PyQt5.QtGui import QFont +from PyQt5.QtNetwork import QLocalServer, QLocalSocket +from PyQt5.QtWidgets import (QAction, QApplication, QDialog, QFileDialog, + QMessageBox, qApp) import globalref +import treelocalcontrol import options import optiondefaults import recentfiles -import treelocalcontrol -import treeopener -import plugininterface import p3 +import icondict +import imports import configdialog import miscdialogs import conditional -import imports -import icondict import helpview try: from __main__ import __version__, __author__ @@ -48,24 +52,21 @@ encryptPrefix = b'>>TL+enc' -class TreeMainControl(QtCore.QObject): +class TreeMainControl(QObject): """Class to handle all global controls. - + Provides methods for all controls and stores local control objects. """ - def __init__(self, filePaths, parent=None): + def __init__(self, pathObjects, parent=None): """Initialize the main tree controls Arguments: - filePaths -- a list of files to open + pathObjects -- a list of file objects to open parent -- the parent QObject if given """ super().__init__(parent) self.localControls = [] self.activeControl = None - self.pluginInterface = None - self.pluginInstances = [] - self.pluginDescriptions = [] self.configDialog = None self.sortDialog = None self.numberingDialog = None @@ -75,23 +76,24 @@ self.filterTextDialog = None self.filterConditionDialog = None self.basicHelpView = None - self.serverSocket = None self.passwords = {} globalref.mainControl = self + self.allActions = {} try: # check for existing TreeLine session - socket = QtNetwork.QLocalSocket() - socket.connectToServer('treeline2-session', - QtCore.QIODevice.WriteOnly) + socket = QLocalSocket() + socket.connectToServer('treeline3-session', + QIODevice.WriteOnly) # if found, send files to open and exit TreeLine if socket.waitForConnected(1000): - socket.write(bytes(repr(filePaths), 'utf-8')) + socket.write(bytes(repr([str(path) for path in pathObjects]), + 'utf-8')) if socket.waitForBytesWritten(1000): socket.close() sys.exit(0) # start local server to listen for attempt to start new session - self.serverSocket = QtNetwork.QLocalServer() - self.serverSocket.listen('treeline2-session') + self.serverSocket = QLocalServer() + self.serverSocket.listen('treeline3-session') self.serverSocket.newConnection.connect(self.getSocket) except AttributeError: print(_('Warning: Could not create local socket')) @@ -110,38 +112,37 @@ try: globalref.genOptions.readFile() globalref.miscOptions.readFile() - recentfiles.setRecentOptionDefaults() globalref.histOptions.readFile() globalref.toolbarOptions.readFile() globalref.keyboardOptions.readFile() except IOError: - QtGui.QMessageBox.warning(QtGui.QApplication.activeWindow(), - 'TreeLine', + QMessageBox.warning(None, 'TreeLine', _('Error - could not write config file to {}'). format(options.Options.basePath)) iconPathList = self.findResourcePaths('icons', iconPath) - globalref.toolIcons = icondict.IconDict([os.path.join(path, 'toolbar') - for path in iconPathList], + globalref.toolIcons = icondict.IconDict([path / 'toolbar' for path + in iconPathList], ['', '32x32', '16x16']) globalref.toolIcons.loadAllIcons() windowIcon = globalref.toolIcons.getIcon('treelogo') if windowIcon: - QtGui.QApplication.setWindowIcon(windowIcon) + QApplication.setWindowIcon(windowIcon) globalref.treeIcons = icondict.IconDict(iconPathList, ['', 'tree']) + icon = globalref.treeIcons.getIcon('default') self.recentFiles = recentfiles.RecentFileList() - if globalref.genOptions.getValue('AutoFileOpen') and not filePaths: + if globalref.genOptions['AutoFileOpen'] and not pathObjects: recentPath = self.recentFiles.firstPath() if recentPath: - filePaths = [recentPath] - self.allActions = {} + pathObjects = [recentPath] self.setupActions() - QtGui.qApp.focusChanged.connect(self.updateActionsAvail) - if filePaths: - for path in filePaths: - self.openFile(path) + self.systemFont = QApplication.font() + self.updateAppFont() + qApp.focusChanged.connect(self.updateActionsAvail) + if pathObjects: + for pathObj in pathObjects: + self.openFile(pathObj, True) else: self.createLocalControl() - self.setupPlugins() def getSocket(self): """Open a socket from an attempt to open a second Treeline instance. @@ -151,33 +152,35 @@ socket = self.serverSocket.nextPendingConnection() if socket and socket.waitForReadyRead(1000): data = str(socket.readAll(), 'utf-8') - if data.startswith('[') and data.endswith(']'): - filePaths = eval(data) - if filePaths: - for path in filePaths: - self.openFile(path) + try: + paths = ast.literal_eval(data) + if paths: + for path in paths: + self.openFile(pathlib.Path(path), True) else: self.activeControl.activeWindow.activateAndRaise() + except(SyntaxError, ValueError, TypeError): + pass - def findResourcePaths(self, resourceName, preferredPath=None): - """Return a list of potential non-empty paths for the resource name. + def findResourcePaths(self, resourceName, preferredPath=''): + """Return list of potential non-empty pathlib objects for the resource. List includes preferred, module and user option paths. Arguments: resourceName -- the typical name of the resource directory - preferredPath -- add this as the first path if given + preferredPath -- add this as the second path if given """ - modPath = os.path.abspath(sys.path[0]) - pathList = [os.path.join(options.Options.basePath, resourceName), - os.path.join(modPath, '..', resourceName), - os.path.join(modPath, resourceName)] + modPath = pathlib.Path(sys.path[0]).resolve() + basePath = pathlib.Path(options.Options.basePath) + pathList = [basePath / resourceName, modPath / '..' / resourceName, + modPath / resourceName] if preferredPath: - pathList.insert(1, preferredPath) - return [path for path in pathList if os.path.exists(path) and - os.listdir(path)] + pathList.insert(1, pathlib.Path(preferredPath)) + return [path.resolve() for path in pathList if path.is_dir() and + list(path.iterdir())] - def findResourceFile(self, fileName, resourceName, preferredPath=None): - """Return a full path to a resource file. + def findResourceFile(self, fileName, resourceName, preferredPath=''): + """Return a path object for a resource file. Add a language code before the extension if it exists. Arguments: @@ -193,252 +196,252 @@ format(globalref.lang[:2]))] for fileName in fileList: for path in self.findResourcePaths(resourceName, preferredPath): - if os.access(os.path.join(path, fileName), os.R_OK): - return os.path.join(path, fileName) - return '' + if (path / fileName).is_file(): + return path / fileName + return None - def updateAllViews(self): - """Update the views associated with all local controls. - """ - for control in self.localControls: - control.updateAll(False) - - def defaultFilePath(self, dirOnly=False): - """Return a reasonable default file path. + def defaultPathObj(self, dirOnly=False): + """Return a reasonable default file path object. Used for open, save-as, import and export. Arguments: dirOnly -- if True, do not include basename of file """ - filePath = '' + pathObj = None if self.activeControl: - filePath = self.activeControl.filePath - if not filePath: - filePath = self.recentFiles.firstDir() - if not filePath: - filePath = os.path.expanduser('~') - if filePath == '~': - filePath = '' + pathObj = self.activeControl.filePathObj + if not pathObj: + pathObj = self.recentFiles.firstDir() + if not pathObj: + pathObj = pathlib.Path.home() if dirOnly: - filePath = os.path.dirname(filePath) - return filePath + pathObj = pathObj.parent + return pathObj - def openFile(self, path, checkModified=False, importOnFail=True): + def openFile(self, pathObj, forceNewWindow=False, checkModified=False, + importOnFail=True): """Open the file given by path if not already open. If already open in a different window, focus and raise the window. Arguments: - path -- the name of the file path to read - checkModified -- if True, prompt user about current modified file + pathObj -- the path object to read + forceNewWindow -- if True, use a new window regardless of option + checkModified -- if True & not new win, prompt if file modified importOnFail -- if True, prompts for import on non-TreeLine files """ - path = os.path.abspath(path) - match = [control for control in self.localControls if - os.path.normcase(control.filePath) == os.path.normcase(path)] + match = [control for control in self.localControls if + pathObj == control.filePathObj] if match and self.activeControl not in match: control = match[0] control.activeWindow.activateAndRaise() self.updateLocalControlRef(control) - elif (not checkModified or - globalref.genOptions.getValue('OpenNewWindow') or - self.activeControl.promptModifiedOk()): - if not self.checkAutoSave(path): + return + if checkModified and not (forceNewWindow or + globalref.genOptions['OpenNewWindow'] or + self.activeControl.checkSaveChanges()): + return + if not self.checkAutoSave(pathObj): + if not self.localControls: + self.createLocalControl() + return + QApplication.setOverrideCursor(Qt.WaitCursor) + try: + self.createLocalControl(pathObj, None, forceNewWindow) + self.recentFiles.addItem(pathObj) + if not (globalref.genOptions['SaveTreeStates'] and + self.recentFiles.retrieveTreeState(self.activeControl)): + self.activeControl.expandRootNodes() + QApplication.restoreOverrideCursor() + except IOError: + QApplication.restoreOverrideCursor() + QMessageBox.warning(QApplication.activeWindow(), 'TreeLine', + _('Error - could not read file {0}'). + format(pathObj)) + self.recentFiles.removeItem(pathObj) + except (ValueError, KeyError, TypeError): + fileObj = pathObj.open('rb') + fileObj, encrypted = self.decryptFile(fileObj) + if not fileObj: if not self.localControls: self.createLocalControl() - return - try: - QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) - self.createLocalControl(path) - self.recentFiles.addItem(path) - if globalref.genOptions.getValue('SaveTreeStates'): - self.recentFiles.retrieveTreeState(self.activeControl) - QtGui.QApplication.restoreOverrideCursor() - if self.pluginInterface: - self.pluginInterface.execCallback(self.pluginInterface. - fileOpenCallbacks) - except IOError: - QtGui.QApplication.restoreOverrideCursor() - QtGui.QMessageBox.warning(QtGui.QApplication.activeWindow(), - 'TreeLine', - _('Error - could not read file {0}'). - format(path)) - self.recentFiles.removeItem(path) - except treeopener.ParseError: - QtGui.QApplication.restoreOverrideCursor() - compressed = False - encrypted = False - fileObj = open(path, 'rb') - # decompress before decrypt to support TreeLine 1.4 and earlier - fileObj, compressed = self.decompressFile(path, fileObj) - fileObj, encrypted = self.decryptFile(path, fileObj) - if not fileObj: - if not self.localControls: - self.createLocalControl() - QtGui.QApplication.restoreOverrideCursor() - return - if encrypted and not compressed: - fileObj, compressed = self.decompressFile(path, fileObj) - if compressed or encrypted: - try: - QtGui.QApplication.setOverrideCursor(QtCore.Qt. - WaitCursor) - self.createLocalControl(fileObj) - self.recentFiles.addItem(path) - if globalref.genOptions.getValue('SaveTreeStates'): - self.recentFiles.retrieveTreeState(self. - activeControl) - self.activeControl.compressed = compressed - self.activeControl.encrypted = encrypted - QtGui.QApplication.restoreOverrideCursor() - if self.pluginInterface: - self.pluginInterface.execCallback(self. - pluginInterface. - fileOpenCallbacks) - except (treeopener.ParseError, zlib.error): - QtGui.QApplication.restoreOverrideCursor() - QtGui.QMessageBox.warning(QtGui.QApplication. - activeWindow(), - 'TreeLine', - _('Error - {0} is not a ' - 'valid TreeLine file'). - format(path)) - fileObj.close() - else: + QApplication.restoreOverrideCursor() + return + fileObj, compressed = self.decompressFile(fileObj) + if compressed or encrypted: + try: + textFileObj = io.TextIOWrapper(fileObj, encoding='utf-8') + self.createLocalControl(textFileObj, None, forceNewWindow) fileObj.close() - if importOnFail: - importControl = imports.ImportControl(path) - model = importControl.interactiveImport(True) - if model: - self.createLocalControl(importControl.filePath, - model) - self.activeControl.imported = True - if not self.localControls: - self.createLocalControl() - - def decompressFile(self, path, fileObj): - """Check for compression and decompress the fileObj if needed. - - Return a tuple of the file object and True if it was compressed. - Arguments: - path -- the path name for reference - fileObj -- the file object to check and decompress - """ - prefix = fileObj.read(2) - fileObj.seek(0) - if prefix != b'\037\213': - return (fileObj, False) - fileObj = gzip.GzipFile(fileobj=fileObj) - fileObj.name = path - return (fileObj, True) + textFileObj.close() + self.recentFiles.addItem(pathObj) + if not (globalref.genOptions['SaveTreeStates'] and + self.recentFiles.retrieveTreeState(self. + activeControl)): + self.activeControl.expandRootNodes() + self.activeControl.compressed = compressed + self.activeControl.encrypted = encrypted + QApplication.restoreOverrideCursor() + return + except (ValueError, KeyError, TypeError): + pass + fileObj.close() + importControl = imports.ImportControl(pathObj) + structure = importControl.importOldTreeLine() + if structure: + self.createLocalControl(pathObj, structure, forceNewWindow) + self.activeControl.printData.readData(importControl. + treeLineRootAttrib) + self.recentFiles.addItem(pathObj) + self.activeControl.expandRootNodes() + self.activeControl.imported = True + QApplication.restoreOverrideCursor() + return + QApplication.restoreOverrideCursor() + if importOnFail: + importControl = imports.ImportControl(pathObj) + structure = importControl.interactiveImport(True) + if structure: + self.createLocalControl(pathObj, structure, forceNewWindow) + self.activeControl.imported = True + return + else: + QMessageBox.warning(QApplication.activeWindow(), 'TreeLine', + _('Error - invalid TreeLine file {0}'). + format(pathObj)) + self.recentFiles.removeItem(pathObj) + if not self.localControls: + self.createLocalControl() - def decryptFile(self, path, fileObj): + def decryptFile(self, fileObj): """Check for encryption and decrypt the fileObj if needed. Return a tuple of the file object and True if it was encrypted. Return None for the file object if the user cancels. Arguments: - path -- the path name for reference fileObj -- the file object to check and decrypt """ if fileObj.read(len(encryptPrefix)) != encryptPrefix: fileObj.seek(0) return (fileObj, False) while True: - password = self.passwords.get(path, '') + pathObj = pathlib.Path(fileObj.name) + password = self.passwords.get(pathObj, '') if not password: - dialog = miscdialogs.PasswordDialog(False, - os.path.basename(path), - QtGui.QApplication. + QApplication.restoreOverrideCursor() + dialog = miscdialogs.PasswordDialog(False, pathObj.name, + QApplication. activeWindow()) - if dialog.exec_() != QtGui.QDialog.Accepted: + if dialog.exec_() != QDialog.Accepted: fileObj.close() return (None, True) + QApplication.setOverrideCursor(Qt.WaitCursor) password = dialog.password if miscdialogs.PasswordDialog.remember: - self.passwords[path] = password + self.passwords[pathObj] = password try: text = p3.p3_decrypt(fileObj.read(), password.encode()) fileIO = io.BytesIO(text) - fileIO.name = path + fileIO.name = fileObj.name + fileObj.close() return (fileIO, True) except p3.CryptError: try: - del self.passwords[path] + del self.passwords[pathObj] except KeyError: pass - def checkAutoSave(self, filePath): + def decompressFile(self, fileObj): + """Check for compression and decompress the fileObj if needed. + + Return a tuple of the file object and True if it was compressed. + Arguments: + fileObj -- the file object to check and decompress + """ + prefix = fileObj.read(2) + fileObj.seek(0) + if prefix != b'\037\213': + return (fileObj, False) + try: + newFileObj = gzip.GzipFile(fileobj=fileObj) + except zlib.error: + return (fileObj, False) + newFileObj.name = fileObj.name + return (newFileObj, True) + + def checkAutoSave(self, pathObj): """Check for presence of auto save file & prompt user. - Return True if OK to contimue, False if aborting. + Return True if OK to contimue, False if aborting or already loaded. Arguments: - filePath -- the base path name to search for a backup + pathObj -- the base path object to search for a backup """ - if not globalref.genOptions.getValue('AutoSaveMinutes'): + if not globalref.genOptions['AutoSaveMinutes']: return True - basePath = filePath - filePath = filePath + '~' - if not os.access(filePath, os.R_OK): + basePath = pathObj + pathObj = pathlib.Path(str(pathObj) + '~') + if not pathObj.is_file(): return True - msgBox = QtGui.QMessageBox(QtGui.QMessageBox.Information, 'TreeLine', - _('Backup file "{}" exists.\nA previous ' - 'session may have crashed').format(filePath), - QtGui.QMessageBox.NoButton, - QtGui.QApplication.activeWindow()) + msgBox = QMessageBox(QMessageBox.Information, 'TreeLine', + _('Backup file "{}" exists.\nA previous ' + 'session may have crashed'). + format(pathObj), QMessageBox.NoButton, + QApplication.activeWindow()) restoreButton = msgBox.addButton(_('&Restore Backup'), - QtGui.QMessageBox.ApplyRole) + QMessageBox.ApplyRole) deleteButton = msgBox.addButton(_('&Delete Backup'), - QtGui.QMessageBox.DestructiveRole) + QMessageBox.DestructiveRole) cancelButton = msgBox.addButton(_('&Cancel File Open'), - QtGui.QMessageBox.RejectRole) + QMessageBox.RejectRole) msgBox.exec_() if msgBox.clickedButton() == restoreButton: - self.openFile(filePath) - if self.activeControl.filePath != filePath: + self.openFile(pathObj) + if self.activeControl.filePathObj != pathObj: return False try: - os.remove(basePath) - os.rename(filePath, basePath) + basePath.unlink() + pathObj.rename(basePath) except OSError: - QtGui.QMessageBox.warning(QtGui.QApplication.activeWindow(), + QMessageBox.warning(QApplication.activeWindow(), 'TreeLine', _('Error - could not rename "{0}" to "{1}"'). - format(filePath, basePath)) + format(pathObj, basePath)) return False - self.activeControl.filePath = basePath + self.activeControl.filePathObj = basePath self.activeControl.updateWindowCaptions() + self.recentFiles.removeItem(pathObj) + self.recentFiles.addItem(basePath) + return False elif msgBox.clickedButton() == deleteButton: try: - os.remove(filePath) + pathObj.unlink() except OSError: - QtGui.QMessageBox.warning(QtGui.QApplication.activeWindow(), + QMessageBox.warning(QApplication.activeWindow(), 'TreeLine', _('Error - could not remove backup file {}'). - format(filePath)) + format(pathObj)) else: # cancel button return False return True - def createLocalControl(self, path='', model=None): + def createLocalControl(self, pathObj=None, treeStruct=None, + forceNewWindow=False): """Create a new local control object and add it to the list. - Use an imported model if given or open the file if path is given. + Use an imported structure if given or open the file if path is given. Arguments: - path -- the path for the control to open - model -- the imported model to use - """ - localControl = treelocalcontrol.TreeLocalControl(self.allActions, path, - model) + pathObj -- the path object or file object for the control to open + treeStruct -- the imported structure to use + forceNewWindow -- if True, use a new window regardless of option + """ + localControl = treelocalcontrol.TreeLocalControl(self.allActions, + pathObj, treeStruct, + forceNewWindow) localControl.controlActivated.connect(self.updateLocalControlRef) localControl.controlClosed.connect(self.removeLocalControlRef) self.localControls.append(localControl) self.updateLocalControlRef(localControl) - if self.pluginInterface: - if not path and not model: - self.pluginInterface.execCallback(self.pluginInterface. - fileNewCallbacks) - self.pluginInterface.execCallback(self.pluginInterface. - newWindowCallbacks) + localControl.updateRightViews() + localControl.updateCommandsAvail() def updateLocalControlRef(self, localControl): """Set the given local control as active. @@ -451,21 +454,7 @@ if localControl != self.activeControl: self.activeControl = localControl if self.configDialog and self.configDialog.isVisible(): - self.configDialog.setRefs(self.activeControl.model, - self.activeControl. - currentSelectionModel()) - if self.sortDialog and self.sortDialog.isVisible(): - self.sortDialog.updateCommandsAvail() - if self.numberingDialog and self.numberingDialog.isVisible(): - self.numberingDialog.updateCommandsAvail() - if (self.findConditionDialog and - self.findConditionDialog.isVisible()): - self.findConditionDialog.loadTypeNames() - if self.findReplaceDialog and self.findReplaceDialog.isVisible(): - self.findReplaceDialog.loadTypeNames() - if (self.filterConditionDialog and - self.filterConditionDialog.isVisible()): - self.filterConditionDialog.loadTypeNames() + self.configDialog.setRefs(self.activeControl) def removeLocalControlRef(self, localControl): """Remove ref to local control based on a closing signal. @@ -475,18 +464,23 @@ localControl -- the local control that is closing """ self.localControls.remove(localControl) - if globalref.genOptions.getValue('SaveTreeStates'): + if globalref.genOptions['SaveTreeStates']: self.recentFiles.saveTreeState(localControl) if not self.localControls: - if globalref.genOptions.getValue('SaveWindowGeom'): + if globalref.genOptions['SaveWindowGeom']: localControl.windowList[0].saveWindowGeom() + else: + localControl.windowList[0].resetWindowGeom() self.recentFiles.writeItems() localControl.windowList[0].saveToolbarPosition() + globalref.histOptions.writeFile() + localControl.deleteLater() - def currentTreeView(self): - """Return the current left-hand tree view. + def updateConfigDialog(self): + """Update the config dialog for changes if it exists. """ - return self.activeControl.currentTreeView() + if self.configDialog: + self.configDialog.reset() def currentStatusBar(self): """Return the status bar from the current main window. @@ -509,123 +503,83 @@ oldWidget -- the previously focused widget newWidget -- the newly focused widget """ - self.allActions['EditSelectAll'].setEnabled(hasattr(newWidget, - 'selectAll') and + self.allActions['FormatSelectAll'].setEnabled(hasattr(newWidget, + 'selectAll') and not hasattr(newWidget, 'editTriggers')) - def setupPlugins(self): - """Load and initialize any available plugin modules. - """ - self.pluginInterface = plugininterface.PluginInterface() - pluginNames = set() - for pluginPath in self.findResourcePaths('plugins'): - names = [name[:-3] for name in os.listdir(pluginPath) if - name.endswith('.py')] - if names: - pluginNames.update(names) - sys.path.insert(1, pluginPath) - errorList = [] - for name in sorted(pluginNames): - try: - module = __import__(name) - if not hasattr(module, 'main'): - raise ImportError - self.pluginInstances.append(module.main(self.pluginInterface)) - descript = module.__doc__.strip().split('\n', 1)[0].strip() - if not descript: - descript = name - self.pluginDescriptions.append(descript) - except ImportError: - errorList.append(name) - if not self.pluginInstances: - self.pluginInterface = None - if errorList: - QtGui.QMessageBox.warning(QtGui.QApplication.activeWindow(), - 'TreeLine', - _('Warning - could not load plugin module {0}'). - format(', '.join(errorList))) - - def setupActions(self): """Add the actions for contols at the global level. """ - fileNewAct = QtGui.QAction(_('&New...'), self, toolTip=_('New File'), - statusTip=_('Start a new file')) + fileNewAct = QAction(_('&New...'), self, toolTip=_('New File'), + statusTip=_('Start a new file')) fileNewAct.triggered.connect(self.fileNew) self.allActions['FileNew'] = fileNewAct - fileOpenAct = QtGui.QAction(_('&Open...'), self, - toolTip=_('Open File'), - statusTip=_('Open a file from disk')) + fileOpenAct = QAction(_('&Open...'), self, toolTip=_('Open File'), + statusTip=_('Open a file from disk')) fileOpenAct.triggered.connect(self.fileOpen) self.allActions['FileOpen'] = fileOpenAct - fileSampleAct = QtGui.QAction(_('Open Sa&mple...'), self, + fileSampleAct = QAction(_('Open Sa&mple...'), self, toolTip=_('Open Sample'), statusTip=_('Open a sample file')) fileSampleAct.triggered.connect(self.fileOpenSample) self.allActions['FileOpenSample'] = fileSampleAct - fileImportAct = QtGui.QAction(_('&Import...'), self, + fileImportAct = QAction(_('&Import...'), self, statusTip=_('Open a non-TreeLine file')) fileImportAct.triggered.connect(self.fileImport) self.allActions['FileImport'] = fileImportAct - fileQuitAct = QtGui.QAction(_('&Quit'), self, - statusTip=_('Exit the application')) + fileQuitAct = QAction(_('&Quit'), self, + statusTip=_('Exit the application')) fileQuitAct.triggered.connect(self.fileQuit) self.allActions['FileQuit'] = fileQuitAct - editSelectAllAct = QtGui.QAction(_('&Select All'), self, - statusTip=_('Select all text in an editor')) - editSelectAllAct.setEnabled(False) - editSelectAllAct.triggered.connect(self.editSelectAll) - self.allActions['EditSelectAll'] = editSelectAllAct - - dataConfigAct = QtGui.QAction(_('&Configure Data Types...'), self, + dataConfigAct = QAction(_('&Configure Data Types...'), self, statusTip=_('Modify data types, fields & output lines'), checkable=True) - dataConfigAct.triggered.connect(self.dataConfigDlg) + dataConfigAct.triggered.connect(self.dataConfigDialog) self.allActions['DataConfigType'] = dataConfigAct - dataSortAct = QtGui.QAction(_('Sor&t Nodes...'), self, + dataSortAct = QAction(_('Sor&t Nodes...'), self, statusTip=_('Define node sort operations'), checkable=True) dataSortAct.triggered.connect(self.dataSortDialog) self.allActions['DataSortNodes'] = dataSortAct - dataNumberingAct = QtGui.QAction(_('Update &Numbering...'), self, + dataNumberingAct = QAction(_('Update &Numbering...'), self, statusTip=_('Update node numbering fields'), checkable=True) dataNumberingAct.triggered.connect(self.dataNumberingDialog) self.allActions['DataNumbering'] = dataNumberingAct - toolsFindTextAct = QtGui.QAction(_('&Find Text...'), self, + toolsFindTextAct = QAction(_('&Find Text...'), self, statusTip=_('Find text in node titles & data'), checkable=True) toolsFindTextAct.triggered.connect(self.toolsFindTextDialog) self.allActions['ToolsFindText'] = toolsFindTextAct - toolsFindConditionAct = QtGui.QAction(_('&Conditional Find...'), self, + toolsFindConditionAct = QAction(_('&Conditional Find...'), self, statusTip=_('Use field conditions to find nodes'), checkable=True) toolsFindConditionAct.triggered.connect(self.toolsFindConditionDialog) self.allActions['ToolsFindCondition'] = toolsFindConditionAct - toolsFindReplaceAct = QtGui.QAction(_('Find and &Replace...'), self, + toolsFindReplaceAct = QAction(_('Find and &Replace...'), self, statusTip=_('Replace text strings in node data'), checkable=True) toolsFindReplaceAct.triggered.connect(self.toolsFindReplaceDialog) self.allActions['ToolsFindReplace'] = toolsFindReplaceAct - toolsFilterTextAct = QtGui.QAction(_('&Text Filter...'), self, + toolsFilterTextAct = QAction(_('&Text Filter...'), self, statusTip=_('Filter nodes to only show text matches'), checkable=True) toolsFilterTextAct.triggered.connect(self.toolsFilterTextDialog) self.allActions['ToolsFilterText'] = toolsFilterTextAct - toolsFilterConditionAct = QtGui.QAction(_('C&onditional Filter...'), + toolsFilterConditionAct = QAction(_('C&onditional Filter...'), self, statusTip=_('Use field conditions to filter nodes'), checkable=True) @@ -633,111 +587,120 @@ toolsFilterConditionDialog) self.allActions['ToolsFilterCondition'] = toolsFilterConditionAct - toolsGenOptionsAct = QtGui.QAction(_('&General Options...'), self, + toolsGenOptionsAct = QAction(_('&General Options...'), self, statusTip=_('Set user preferences for all files')) toolsGenOptionsAct.triggered.connect(self.toolsGenOptions) self.allActions['ToolsGenOptions'] = toolsGenOptionsAct - toolsShortcutAct = QtGui.QAction(_('Set &Keyboard Shortcuts...'), self, + toolsShortcutAct = QAction(_('Set &Keyboard Shortcuts...'), self, statusTip=_('Customize keyboard commands')) toolsShortcutAct.triggered.connect(self.toolsCustomShortcuts) self.allActions['ToolsShortcuts'] = toolsShortcutAct - toolsToolbarAct = QtGui.QAction(_('C&ustomize Toolbars...'), self, + toolsToolbarAct = QAction(_('C&ustomize Toolbars...'), self, statusTip=_('Customize toolbar buttons')) toolsToolbarAct.triggered.connect(self.toolsCustomToolbars) self.allActions['ToolsToolbars'] = toolsToolbarAct - toolsFontsAct = QtGui.QAction(_('Customize Fo&nts...'), self, + toolsFontsAct = QAction(_('Customize Fo&nts...'), self, statusTip=_('Customize fonts in various views')) toolsFontsAct.triggered.connect(self.toolsCustomFonts) self.allActions['ToolsFonts'] = toolsFontsAct - helpBasicAct = QtGui.QAction(_('&Basic Usage...'), self, + helpBasicAct = QAction(_('&Basic Usage...'), self, statusTip=_('Display basic usage instructions')) helpBasicAct.triggered.connect(self.helpViewBasic) self.allActions['HelpBasic'] = helpBasicAct - helpFullAct = QtGui.QAction(_('&Full Documentation...'), self, + helpFullAct = QAction(_('&Full Documentation...'), self, statusTip=_('Open a TreeLine file with full documentation')) helpFullAct.triggered.connect(self.helpViewFull) self.allActions['HelpFull'] = helpFullAct - helpAboutAct = QtGui.QAction(_('&About TreeLine...'), self, + helpAboutAct = QAction(_('&About TreeLine...'), self, statusTip=_('Display version info about this program')) helpAboutAct.triggered.connect(self.helpAbout) self.allActions['HelpAbout'] = helpAboutAct - helpPluginAct = QtGui.QAction(_('&About &Plugins...'), self, - statusTip=_('Show loaded plugin modules')) - helpPluginAct.triggered.connect(self.helpPlugin) - self.allActions['HelpPlugin'] = helpPluginAct + formatSelectAllAct = QAction(_('&Select All'), self, + statusTip=_('Select all text in an editor')) + formatSelectAllAct.setEnabled(False) + formatSelectAllAct.triggered.connect(self.formatSelectAll) + self.allActions['FormatSelectAll'] = formatSelectAllAct + + helpAboutAct = QAction(_('&About TreeLine...'), self, + statusTip=_('Display version info about this program')) + helpAboutAct.triggered.connect(self.helpAbout) + self.allActions['HelpAbout'] = helpAboutAct for name, action in self.allActions.items(): icon = globalref.toolIcons.getIcon(name.lower()) if icon: action.setIcon(icon) - key = globalref.keyboardOptions.getValue(name) + key = globalref.keyboardOptions[name] if not key.isEmpty(): action.setShortcut(key) def fileNew(self): """Start a new blank file. """ - if (globalref.genOptions.getValue('OpenNewWindow') or - self.activeControl.promptModifiedOk()): + if (globalref.genOptions['OpenNewWindow'] or + self.activeControl.checkSaveChanges()): searchPaths = self.findResourcePaths('templates', templatePath) if searchPaths: dialog = miscdialogs.TemplateFileDialog(_('New File'), _('&Select Template'), searchPaths) - if dialog.exec_() == QtGui.QDialog.Accepted: + if dialog.exec_() == QDialog.Accepted: self.createLocalControl(dialog.selectedPath()) - self.activeControl.filePath = '' + self.activeControl.filePathObj = None self.activeControl.updateWindowCaptions() + self.activeControl.expandRootNodes() else: self.createLocalControl() def fileOpen(self): """Prompt for a filename and open it. """ - if (globalref.genOptions.getValue('OpenNewWindow') or - self.activeControl.promptModifiedOk()): - filters = ';;'.join((globalref.fileFilters['trl'], - globalref.fileFilters['trlgz'], - globalref.fileFilters['trlenc'], + if (globalref.genOptions['OpenNewWindow'] or + self.activeControl.checkSaveChanges()): + filters = ';;'.join((globalref.fileFilters['trlnopen'], globalref.fileFilters['all'])) - fileName = QtGui.QFileDialog.getOpenFileName(QtGui.QApplication. - activeWindow(), - _('TreeLine - Open File'), - self.defaultFilePath(True), - filters) + fileName, selFilter = QFileDialog.getOpenFileName(QApplication. + activeWindow(), + _('TreeLine - Open File'), + str(self.defaultPathObj(True)), + filters) if fileName: - self.openFile(fileName) + self.openFile(pathlib.Path(fileName)) def fileOpenSample(self): """Open a sample file from the doc directories. """ - if (globalref.genOptions.getValue('OpenNewWindow') or - self.activeControl.promptModifiedOk()): + if (globalref.genOptions['OpenNewWindow'] or + self.activeControl.checkSaveChanges()): searchPaths = self.findResourcePaths('samples', samplePath) dialog = miscdialogs.TemplateFileDialog(_('Open Sample File'), _('&Select Sample'), searchPaths, False) - if dialog.exec_() == QtGui.QDialog.Accepted: + if dialog.exec_() == QDialog.Accepted: self.createLocalControl(dialog.selectedPath()) - name = dialog.selectedName() + '.trl' - self.activeControl.filePath = name + name = dialog.selectedName() + '.trln' + self.activeControl.filePathObj = pathlib.Path(name) self.activeControl.updateWindowCaptions() + self.activeControl.expandRootNodes() self.activeControl.imported = True def fileImport(self): """Prompt for an import type, then a file to import. """ importControl = imports.ImportControl() - model = importControl.interactiveImport() - if model: - self.createLocalControl(importControl.filePath, model) + structure = importControl.interactiveImport() + if structure: + self.createLocalControl(importControl.pathObj, structure) + if importControl.treeLineRootAttrib: + self.activeControl.printData.readData(importControl. + treeLineRootAttrib) self.activeControl.imported = True def fileQuit(self): @@ -746,15 +709,7 @@ for control in self.localControls[:]: control.closeWindows() - def editSelectAll(self): - """Select all text in any currently focused editor. - """ - try: - QtGui.QApplication.focusWidget().selectAll() - except AttributeError: - pass - - def dataConfigDlg(self, show): + def dataConfigDialog(self, show): """Show or hide the non-modal data config dialog. Arguments: @@ -765,9 +720,7 @@ self.configDialog = configdialog.ConfigDialog() dataConfigAct = self.allActions['DataConfigType'] self.configDialog.dialogShown.connect(dataConfigAct.setChecked) - self.configDialog.setRefs(self.activeControl.model, - self.activeControl. - currentSelectionModel(), True) + self.configDialog.setRefs(self.activeControl, True) self.configDialog.show() else: self.configDialog.close() @@ -813,10 +766,7 @@ """ if show: if not self.findTextDialog: - self.findTextDialog = (miscdialogs. - FindFilterDialog(miscdialogs. - FindFilterDialog. - findDialog)) + self.findTextDialog = miscdialogs.FindFilterDialog() toolsFindTextAct = self.allActions['ToolsFindText'] self.findTextDialog.dialogShown.connect(toolsFindTextAct. setChecked) @@ -833,7 +783,7 @@ """ if show: if not self.findConditionDialog: - dialogType = conditional.ConditionDialog.findDialog + dialogType = conditional.FindDialogType.findDialog self.findConditionDialog = (conditional. ConditionDialog(dialogType, _('Conditional Find'))) @@ -872,10 +822,7 @@ """ if show: if not self.filterTextDialog: - self.filterTextDialog = (miscdialogs. - FindFilterDialog(miscdialogs. - FindFilterDialog. - filterDialog)) + self.filterTextDialog = miscdialogs.FindFilterDialog(True) toolsFilterTextAct = self.allActions['ToolsFilterText'] self.filterTextDialog.dialogShown.connect(toolsFilterTextAct. setChecked) @@ -892,12 +839,12 @@ """ if show: if not self.filterConditionDialog: - dialogType = conditional.ConditionDialog.filterDialog + dialogType = conditional.FindDialogType.filterDialog self.filterConditionDialog = (conditional. ConditionDialog(dialogType, _('Conditional Filter'))) toolsFilterConditionAct = (self. - allActions[_('ToolsFilterCondition')]) + allActions['ToolsFilterCondition']) (self.filterConditionDialog.dialogShown. connect(toolsFilterConditionAct.setChecked)) else: @@ -909,27 +856,28 @@ def toolsGenOptions(self): """Set general user preferences for all files. """ - oldAutoSaveMinutes = globalref.genOptions.getValue('AutoSaveMinutes') + oldAutoSaveMinutes = globalref.genOptions['AutoSaveMinutes'] dialog = options.OptionDialog(globalref.genOptions, - QtGui.QApplication.activeWindow()) + QApplication.activeWindow()) dialog.setWindowTitle(_('General Options')) - if (dialog.exec_() == QtGui.QDialog.Accepted and + if (dialog.exec_() == QDialog.Accepted and globalref.genOptions.modified): globalref.genOptions.writeFile() - self.recentFiles.updateNumEntries() - autoSaveMinutes = globalref.genOptions.getValue('AutoSaveMinutes') + self.recentFiles.updateOptions() + autoSaveMinutes = globalref.genOptions['AutoSaveMinutes'] for control in self.localControls: for window in control.windowList: - window.treeView.updateTreeGenOptions() + window.updateWinGenOptions() + control.structure.undoList.setNumLevels() + control.updateAll(False) if autoSaveMinutes != oldAutoSaveMinutes: control.resetAutoSave() - self.updateAllViews() def toolsCustomShortcuts(self): """Show dialog to customize keyboard commands. """ actions = self.activeControl.activeWindow.allActions - dialog = miscdialogs.CustomShortcutsDialog(actions, QtGui.QApplication. + dialog = miscdialogs.CustomShortcutsDialog(actions, QApplication. activeWindow()) dialog.exec_() @@ -938,7 +886,7 @@ """ actions = self.activeControl.activeWindow.allActions dialog = miscdialogs.CustomToolbarDialog(actions, self.updateToolbars, - QtGui.QApplication. + QApplication. activeWindow()) dialog.exec_() @@ -952,7 +900,7 @@ def toolsCustomFonts(self): """Show dialog to customize fonts in various views. """ - dialog = miscdialogs.CustomFontDialog(QtGui.QApplication. + dialog = miscdialogs.CustomFontDialog(QApplication. activeWindow()) dialog.updateRequired.connect(self.updateCustomFonts) dialog.exec_() @@ -960,11 +908,30 @@ def updateCustomFonts(self): """Update fonts in all windows based on a dialog signal. """ + self.updateAppFont() for control in self.localControls: for window in control.windowList: window.updateFonts() control.printData.setDefaultFont() - self.updateAllViews() + for control in self.localControls: + control.updateAll(False) + + def updateAppFont(self): + """Update application default font from settings. + """ + appFont = QFont(self.systemFont) + appFontName = globalref.miscOptions['AppFont'] + if appFontName: + appFont.fromString(appFontName) + QApplication.setFont(appFont) + + def formatSelectAll(self): + """Select all text in any currently focused editor. + """ + try: + QApplication.focusWidget().selectAll() + except AttributeError: + pass def helpViewBasic(self): """Display basic usage instructions. @@ -972,9 +939,8 @@ if not self.basicHelpView: path = self.findResourceFile('basichelp.html', 'doc', docPath) if not path: - QtGui.QMessageBox.warning(QtGui.QApplication.activeWindow(), - 'TreeLine', - _('Error - basic help file not found')) + QMessageBox.warning(QApplication.activeWindow(), 'TreeLine', + _('Error - basic help file not found')) return self.basicHelpView = helpview.HelpView(path, _('TreeLine Basic Usage'), @@ -984,35 +950,31 @@ def helpViewFull(self): """Open a TreeLine file with full documentation. """ - path = self.findResourceFile('documentation.trl', 'doc', docPath) + path = self.findResourceFile('documentation.trln', 'doc', docPath) if not path: - QtGui.QMessageBox.warning(QtGui.QApplication.activeWindow(), - 'TreeLine', - _('Error - documentation file not found')) + QMessageBox.warning(QApplication.activeWindow(), 'TreeLine', + _('Error - documentation file not found')) return - newWindowSetting = globalref.genOptions.getValue('OpenNewWindow') - if not newWindowSetting: - globalref.genOptions.changeValue('OpenNewWindow', True) - self.createLocalControl(path) - self.activeControl.filePath = 'documentation.trl' + self.createLocalControl(path, forceNewWindow=True) + self.activeControl.filePathObj = pathlib.Path('documentation.trln') self.activeControl.updateWindowCaptions() + self.activeControl.expandRootNodes() self.activeControl.imported = True win = self.activeControl.activeWindow win.rightTabs.setCurrentWidget(win.outputSplitter) - if not newWindowSetting: - globalref.genOptions.changeValue('OpenNewWindow', False) def helpAbout(self): """ Display version info about this program. """ - QtGui.QMessageBox.about(QtGui.QApplication.activeWindow(), 'TreeLine', - _('TreeLine, Version {0}\nby {1}'). - format(__version__, __author__)) - - def helpPlugin(self): - """Show dialog with loaded plugin modules. - """ - dialog = miscdialogs.PluginListDialog(self.pluginDescriptions, - QtGui.QApplication. - activeWindow()) + pyVersion = '.'.join([repr(num) for num in sys.version_info[:3]]) + textLines = [_('TreeLine version {0}').format(__version__), + _('written by {0}').format(__author__), '', + _('Library versions:'), + ' Python: {0}'.format(pyVersion), + ' Qt: {0}'.format(qVersion()), + ' PyQt: {0}'.format(PYQT_VERSION_STR), + ' OS: {0}'.format(platform.platform())] + dialog = miscdialogs.AboutDialog('TreeLine', textLines, + QApplication.windowIcon(), + QApplication.activeWindow()) dialog.exec_() diff -Nru treeline-2.0.2/source/treemodel.py treeline-3.0.1/source/treemodel.py --- treeline-2.0.2/source/treemodel.py 2015-03-05 02:29:09.000000000 +0000 +++ treeline-3.0.1/source/treemodel.py 2018-04-08 19:19:50.000000000 +0000 @@ -4,7 +4,7 @@ # treemodel.py, provides a class for the tree's data # # TreeLine, an information storage program -# Copyright (C) 2015, Douglas W. Bell +# Copyright (C) 2017, 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 @@ -12,58 +12,30 @@ # but WITTHOUT ANY WARRANTY. See the included LICENSE file for details. #****************************************************************************** -import copy -import io -from xml.etree import ElementTree -from PyQt4 import QtCore, QtGui -import treeformats -import nodeformat -import treenode -import treeopener +import json +from PyQt5.QtCore import (QAbstractItemModel, QMimeData, QModelIndex, Qt, + pyqtSignal) import undo -import linkref +import treestructure import globalref -defaultRootName = _('Main') - -class TreeModel(QtCore.QAbstractItemModel): - """Class containing the tree's model/document information - - Stores document information and interfaces with view classes. +class TreeModel(QAbstractItemModel): + """Class interfacing between the tree structure and the tree view. """ - allModified = QtCore.pyqtSignal() - nodeTitleModified = QtCore.pyqtSignal(bool) - storedDragNodes = [] - storedDragModel = None - def __init__(self, newFile=False, parent=None): + treeModified = pyqtSignal(bool) + def __init__(self, treeStructure, parent=None): """Initialize a TreeModel. - + Arguments: - newFile -- if true, adds default root node and formats + treeStructure -- a ref to the main tree structure parent -- optional QObject parent for the model """ super().__init__(parent) - self.root = None - self.configDialogFormats = None - self.undoList = None - self.redoList = None - self.nodeIdDict = {} - self.linkRefCollect = linkref.LinkRefCollection() - self.mathZeroBlanks = True - if newFile: - self.formats = treeformats.TreeFormats(True) - self.root = treenode.TreeNode(None, treeformats.defaultTypeName, - self) - self.root.setTitle(defaultRootName) - else: - self.formats = treeformats.TreeFormats() - self.fileInfoNode = treenode.TreeNode(None, - self.formats.fileInfoFormat.name, - self) + self.treeStructure = treeStructure def index(self, row, column, parentIndex): - """Returns the index of a node in the model based on the parent index. + """Returns the index of a spot in the model based on the parent index. Uses createIndex() to generate the model indices. Arguments: @@ -71,60 +43,66 @@ column -- the column (always 0 for now) parentIndex -- the parent's model index in the tree structure """ - if not parentIndex.isValid(): - return self.createIndex(row, column, self.root) - parent = parentIndex.internalPointer() try: - return self.createIndex(row, column, parent.childList[row]) + if not parentIndex.isValid(): + node = self.treeStructure.childList[row] + fakeSpot = list(self.treeStructure.spotRefs)[0] + spot = node.matchedSpot(fakeSpot) + return self.createIndex(row, column, spot) + parentSpot = parentIndex.internalPointer() + node = parentSpot.nodeRef.childList[row] + return self.createIndex(row, column, node.matchedSpot(parentSpot)) except IndexError: - return QtCore.QModelIndex() + return QModelIndex() def parent(self, index): - """Returns the parent model index of the node at the given index. + """Returns the parent model index of the spot at the given index. Arguments: index -- the child model index """ - try: - parent = index.internalPointer().parent - return self.createIndex(parent.row(), 0, parent) - except AttributeError: - return QtCore.QModelIndex() + parentSpot = index.internalPointer().parentSpot + if parentSpot.parentSpot: + return self.createIndex(parentSpot.row(), 0, parentSpot) + return QModelIndex() def rowCount(self, parentIndex): - """Returns the number of children for the node at the given index. + """Returns the number of children for the spot at the given index. Arguments: parentIndex -- the parent model index """ try: - parent = parentIndex.internalPointer() - return parent.numChildren() + parentSpot = parentIndex.internalPointer() + return parentSpot.nodeRef.numChildren() except AttributeError: - return 1 # a single root node has no valid parentIndex + # top level if no parentIndex + return len(self.treeStructure.childList) def columnCount(self, parentIndex): """The number of columns -- always 1 for now. """ return 1 - def data(self, index, role=QtCore.Qt.DisplayRole): + def data(self, index, role=Qt.DisplayRole): """Return the output data for the node in the given role. Arguments: - index -- the node's model index + index -- the spot's model index role -- the type of data requested """ - node = index.internalPointer() - if role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole): + spot = index.internalPointer() + if not spot: + return None + node = spot.nodeRef + if role in (Qt.DisplayRole, Qt.EditRole): return node.title() - if (role == QtCore.Qt.DecorationRole and - globalref.genOptions.getValue('ShowTreeIcons')): - return globalref.treeIcons.getIcon(node.nodeFormat().iconName, - True) + if (role == Qt.DecorationRole and + globalref.genOptions['ShowTreeIcons']): + return globalref.treeIcons.getIcon(node.formatRef.iconName, True) return None - def setData(self, index, value, role=QtCore.Qt.EditRole): + def setData(self, index, value, role=Qt.EditRole): """Set node title after edit operation. Return True on success. @@ -133,15 +111,15 @@ value -- the string result of the editing role -- the edit role of the data """ - if role != QtCore.Qt.EditRole: + if role != Qt.EditRole: return super().setData(index, value, role) - node = index.internalPointer() - dataUndo = undo.DataUndo(self.undoList, node) + node = index.internalPointer().nodeRef + dataUndo = undo.DataUndo(self.treeStructure.undoList, node) if node.setTitle(value): self.dataChanged.emit(index, index) - self.nodeTitleModified.emit(True) + self.treeModified.emit(True) return True - self.undoList.removeLastUndo(dataUndo) + self.treeStructure.undoList.removeLastUndo(dataUndo) return False def flags(self, index): @@ -150,9 +128,9 @@ Arguments: index -- the node's model index """ - return (QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | - QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsDragEnabled | - QtCore.Qt.ItemIsDropEnabled) + return (Qt.ItemIsEnabled | Qt.ItemIsSelectable | + Qt.ItemIsEditable | Qt.ItemIsDragEnabled | + Qt.ItemIsDropEnabled) def mimeData(self, indexList): """Return a mime data object for the given node index branches. @@ -160,42 +138,36 @@ Arguments: indexList -- a list of node indexes to convert """ - allNodes = [index.internalPointer() for index in indexList] - nodes = [] - # accept only nodes on top of unique branches - for node in allNodes: - parent = node.parent - while parent and parent not in allNodes: - parent = parent.parent - if not parent: - nodes.append(node) - TreeModel.storedDragNodes = nodes + spots = [index.internalPointer() for index in indexList] + # remove selections from the same branch + TreeModel.storedDragSpots = [spot for spot in spots if + spot.parentSpotSet(). + isdisjoint(set(spots))] + nodes = [spot.nodeRef for spot in TreeModel.storedDragSpots] TreeModel.storedDragModel = self - dummyFormat = None - if len(nodes) > 1: - dummyFormat = self.formats.addDummyRootType() - root = treenode.TreeNode(None, dummyFormat.name, self) - for node in nodes: - root.childList.append(copy.copy(node)) - root.childList[-1].parent = root - else: - root = nodes[0] - text = ElementTree.tostring(root.elementXml({dummyFormat}, True, - False), 'utf-8') - self.formats.removeDummyRootType() - mime = QtCore.QMimeData() - mime.setData('text/xml', text) + struct = treestructure.TreeStructure(topNodes=nodes, addSpots=False) + generics = {formatRef.genericType for formatRef in + struct.treeFormats.values() if formatRef.genericType} + for generic in generics: + genericRef = self.treeStructure.treeFormats[generic] + struct.treeFormats.addTypeIfMissing(genericRef) + for formatRef in genericRef.derivedTypes: + struct.treeFormats.addTypeIfMissing(formatRef) + data = struct.fileData() + dataStr = json.dumps(data, indent=0, sort_keys=True) + mime = QMimeData() + mime.setData('application/json', bytes(dataStr, encoding='utf-8')) return mime def mimeTypes(self): """Return a list of supported mime types for model objects. """ - return ['text/xml'] + return ['application/json'] def supportedDropActions(self): """Return drop action enum values that are supported by this model. """ - return QtCore.Qt.CopyAction | QtCore.Qt.MoveAction + return Qt.CopyAction | Qt.MoveAction def dropMimeData(self, mimeData, dropAction, row, column, index): """Decode mime data and add as a child node to the given index. @@ -204,108 +176,35 @@ Arguments: mimeData -- data for the node branch to be added dropAction -- a drop type enum value - row -- a row number for the drop location (ignored, can be 0) - column -- the coumn number for the drop location (normally 0) + row -- a row number for the drop location + column -- the column number for the drop location (normally 0) index -- the index of the parent node for the drop """ - parent = index.internalPointer() - if not parent: - return False - isMove = (dropAction == QtCore.Qt.MoveAction and + parent = (index.internalPointer().nodeRef if index.internalPointer() + else self.treeStructure) + isMove = (dropAction == Qt.MoveAction and TreeModel.storedDragModel == self) undoParents = [parent] if isMove: - moveParents = {node.parent for node in TreeModel.storedDragNodes} + moveParents = {spot.parentSpot.nodeRef for spot in + TreeModel.storedDragSpots} undoParents.extend(list(moveParents)) - undoObj = undo.BranchFormatUndo(self.undoList, undoParents, - self.formats) - if self.addMimeData(mimeData, parent, row): + newStruct = treestructure.structFromMimeData(mimeData) + # check for valid structure and no circular clone ref and not siblings: + if newStruct and (not isMove or (not parent.uId in newStruct.nodeDict + and (row >= 0 or {node.uId for node in + parent.childList}. + isdisjoint({node.uId for node in + newStruct.childList})))): + undo.ChildListUndo(self.treeStructure.undoList, undoParents, + treeFormats=self.treeStructure.treeFormats) if isMove: - for node in TreeModel.storedDragNodes: - node.delete() - self.allModified.emit() + for spot in TreeModel.storedDragSpots: + self.treeStructure.deleteNodeSpot(spot) + newStruct.replaceClonedBranches(self.treeStructure) + else: + newStruct.replaceDuplicateIds(self.treeStructure.nodeDict) + self.treeStructure.addNodesFromStruct(newStruct, parent, row) return True - self.undoList.removeLastUndo(undoObj) return False - - def addMimeData(self, mimeData, parent, position=-1): - """Decode mime data and add as a child node to the given parent. - - Return True if successful. - Arguments: - mimeData -- data for the node branch to be added - parent -- the parent node for the drop - position -- the location to insert (-1 is appended) - """ - text = str(mimeData.data('text/xml'), 'utf-8') - opener = treeopener.TreeOpener() - try: - newModel = opener.readFile(io.StringIO(text)) - except treeopener.ParseError: - return False - if newModel.root.formatName == treeformats.dummyRootTypeName: - newNodes = newModel.root.childList - else: - newNodes = [newModel.root] - for format in newModel.formats.values(): - self.formats.addTypeIfMissing(format) - for node in newNodes: - if position >= 0: - parent.childList.insert(position, node) - position += 1 - else: - parent.childList.append(node) - node.parent = parent - for child in node.descendantGen(): - child.modelRef = self - child.setUniqueId(True) - self.formats.removeDummyRootType() - return True - - def getConfigDialogFormats(self, forceReset=False): - """Return duplicate formats for use in the config dialog. - - Arguments: - forceReset -- if True, sets duplicate formats back to original - """ - if not self.configDialogFormats or forceReset: - self.configDialogFormats = copy.deepcopy(self.formats) - return self.configDialogFormats - - def applyConfigDialogFormats(self, addUndo=True): - """Replace the formats with the duplicates and signal for view update. - - Also updates all nodes for changed type and field names. - """ - self.configDialogFormats.updateMathFieldRefs() - if addUndo: - undo.FormatUndo(self.undoList, self.formats, - self.configDialogFormats) - self.formats = self.configDialogFormats - self.getConfigDialogFormats(True) - if self.formats.typeRenameDict or self.formats.fieldRenameDict: - for node in self.root.descendantGen(): - node.formatName = (self.formats.typeRenameDict. - get(node.formatName, node.formatName)) - fieldRenameDict = (self.formats.fieldRenameDict. - get(node.formatName, {})) - for oldName, newName in fieldRenameDict.items(): - if oldName in node.data: - node.data[newName] = node.data[oldName] - del node.data[oldName] - self.formats.typeRenameDict = {} - self.formats.fieldRenameDict = {} - if self.formats.changedIdFieldTypes: - for node in self.root.descendantGen(): - if node.nodeFormat() in self.formats.changedIdFieldTypes: - node.updateUniqueId() - self.formats.changedIdFieldTypes = set() - if self.formats.emptiedMathDict: - for node in self.root.descendantGen(): - for fieldName in self.formats.emptiedMathDict.get(node. - formatName, - set()): - node.data.pop(fieldName, None) - self.formats.emptiedMathDict = {} - self.allModified.emit() diff -Nru treeline-2.0.2/source/treenodelist.py treeline-3.0.1/source/treenodelist.py --- treeline-2.0.2/source/treenodelist.py 2014-05-02 00:43:17.000000000 +0000 +++ treeline-3.0.1/source/treenodelist.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,63 +0,0 @@ -#!/usr/bin/env python3 - -#****************************************************************************** -# treenodelist.py, provides a class to do operations on groups of nodes -# -# TreeLine, an information storage program -# Copyright (C) 2014, 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 -# version. This program is distributed in the hope that it will be useful, -# but WITTHOUT ANY WARRANTY. See the included LICENSE file for details. -#****************************************************************************** - -from PyQt4 import QtCore, QtGui -import undo - - -class TreeNodeList(list): - """Class to do operations on groups of nodes. - - Stores a list of nodes. - """ - def __init__(self, nodeList=None): - """Initialize a tree node group. - - Arguments: - nodeList -- the initial list of nodes - """ - super().__init__() - if nodeList: - self[:] = nodeList - - def copyTree(self): - """Copy these node branches to the clipboard. - """ - if not self: - return - clip = QtGui.QApplication.clipboard() - if clip.supportsSelection(): - titleList = [] - for node in self: - titleList.extend(node.exportTitleText()) - clip.setText('\n'.join(titleList), QtGui.QClipboard.Selection) - clip.setMimeData(self[0].modelRef.mimeData([node.index() for node in - self])) - - def pasteMimeData(self, mimeData): - """Decode mime data and paste into these nodes. - - Returns True on success. - Arguments: - mimeData - the data to paste. - """ - if not self: - return False - undoObj = undo.BranchFormatUndo(self[0].modelRef.undoList, self, - self[0].modelRef.formats) - for parent in self: - if not self[0].modelRef.addMimeData(mimeData, parent): - self[0].modelRef.undoList.removeLastUndo(undoObj) - return False - return True diff -Nru treeline-2.0.2/source/treenode.py treeline-3.0.1/source/treenode.py --- treeline-2.0.2/source/treenode.py 2015-09-20 01:11:26.000000000 +0000 +++ treeline-3.0.1/source/treenode.py 2018-09-27 00:52:18.000000000 +0000 @@ -4,7 +4,7 @@ # treenode.py, provides a class to store tree node data # # TreeLine, an information storage program -# Copyright (C) 2015, Douglas W. Bell +# Copyright (C) 2018, 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 @@ -13,458 +13,239 @@ #****************************************************************************** import re -import sys -import os.path +import uuid import operator import itertools -from xml.etree import ElementTree -import globalref -import fieldformat +import treespot import nodeformat -import treeoutput -import linkref -import imports -import exports -import urltools -try: - from __main__ import __version__ -except ImportError: - __version__ = '' -_idReplaceCharsRe = re.compile(r'[^a-zA-Z0-9_-]+') _replaceBackrefRe = (re.compile(r'\\(\d+)'), re.compile(r'\\g<(\d+)>')) -_htmlLinkRe = re.compile(r'') -_imgLinkRe = re.compile(r'') -_exportHtmlLevel = 0 # temporary storage _origBackrefMatch = None -_maxIdLength = 50 class TreeNode: """Class to store tree node data and the tree's linked structure. - - Stores links to the parent and lists of children and a format name string. + + Stores a data dict, lists of children and a format name string. Provides methods to get info on the structure and the data. """ - def __init__(self, parent, formatName, modelRef, attrs=None): + def __init__(self, formatRef, fileData=None): """Initialize a tree node. Arguments: - parent -- the parent tree node - formatName -- a string name for this node's format info - modelRef -- a stored ref to the model - attrs -- a dict of stored node attributes - """ - self.parent = parent - self.formatName = formatName - self.modelRef = modelRef - if not attrs: - attrs = {} - self.uniqueId = attrs.get('uniqueid', '') - self.data = {} - self.childList = [] - - def index(self): - """Returns the index of this node in the model. - """ - return self.modelRef.createIndex(self.row(), 0, self) - - def row(self): - """Return the rank of this node in its parent's child list. + formatRef -- a ref to this node's format info + fileData -- a dict with uid, data, child refs & parent refs """ - if self.parent: - return self.parent.childList.index(self) - return 0 + self.formatRef = formatRef + if not fileData: + fileData = {} + self.uId = fileData.get('uid', uuid.uuid1().hex) + self.data = fileData.get('data', {}) + self.tmpChildRefs = fileData.get('children', []) + self.childList = [] + self.spotRefs = set() - def numChildren(self): - """Return number of children. - """ - return len(self.childList) + def assignRefs(self, nodeDict): + """Add actual refs to child nodes from data in self.tmpChildRefs. - def nodeFormat(self): - """Return the node format used for this node. + Any bad node refs (corrupt file data) are left in self.tmpChildRefs. + Arguments: + nodeDict -- all nodes stored by uid """ - return self.modelRef.formats[self.formatName] + try: + self.childList = [nodeDict[uid] for uid in self.tmpChildRefs] + self.tmpChildRefs = [] + except KeyError: # due to corrupt file data + badChildRefs = [] + for uid in self.tmpChildRefs: + if uid in nodeDict: + self.childList.append(nodeDict[uid]) + else: + badChildRefs.append(uid) + self.tmpChildRefs = badChildRefs - def descendantGen(self): - """Return a generator to step through all nodes in this branch. + def generateSpots(self, parentSpot): + """Recursively generate spot references for this branch. - Includes self and closed nodes. + Arguments: + parentSpot -- the parent spot reference """ - yield self + spot = treespot.TreeSpot(self, parentSpot) + self.spotRefs.add(spot) for child in self.childList: - for node in child.descendantGen(): - yield node + child.generateSpots(spot) - def selectiveDescendantGen(self, openOnly=False): - """Return a generator to step through nodes in this branch. + def addSpotRef(self, parentNode, includeChildren=True): + """Add a spot ref here to the given parent if not already there. - Does not include the root node. + If changed, propogate to descendant nodes. Arguments: - openOnly -- if True, only include children open in the current view + parentNode -- the parent to ref in the new spot + includeChildren -- if True, propogate to descendant nodes """ - if not openOnly or self.isExpanded(): - for child in self.childList: - yield child - for node in child.selectiveDescendantGen(openOnly): - yield node - - def levelDescendantGen(self, includeRoot=True, maxLevel=None, - openOnly=False, initLevel=0): - """Return generator with (node, level) tuples for this branch. - - Arguments: - includeRoot -- if True, the root node is included - maxLevel -- the max number of levels to return (no limit if none) - openOnly -- if True, only include children open in the current view - initLevel -- the level number to start with - """ - if maxLevel == None: - maxLevel = sys.maxsize - if includeRoot: - yield (self, initLevel) - initLevel += 1 - if initLevel < maxLevel and (not openOnly or self.isExpanded()): + changed = False + origParentSpots = {spot.parentSpot for spot in self.spotRefs} + for parentSpot in parentNode.spotRefs: + if parentSpot not in origParentSpots: + self.spotRefs.add(treespot.TreeSpot(self, parentSpot)) + changed = True + if changed and includeChildren: for child in self.childList: - for node, level in child.levelDescendantGen(True, maxLevel, - openOnly, - initLevel): - yield (node, level) - - def openNodes(self): - """Return a list of all open parent nodes in this branch. - """ - if self.childList and self.isExpanded(): - return [self] + [node for node in - self.selectiveDescendantGen(True) if - node.childList and node.isExpanded()] - return [] - - def prevSibling(self): - """Return the nearest previous sibling or None. - """ - if self.parent: - pos = self.parent.childList.index(self) - if pos > 0: - return self.parent.childList[pos - 1] - return None + child.addSpotRef(self) - def nextSibling(self): - """Return the nearest next sibling or None. + def removeInvalidSpotRefs(self, includeChildren=True, forceDesend=False): + """Verify existing spot refs and remove any that aren't valid. + + If changed and includeChilderen, propogate to descendant nodes. + Arguments: + includeChildren -- if True, propogate to descendants if changes + forceDesend -- if True, force propogate to descendant nodes """ - if self.parent: - pos = self.parent.childList.index(self) + 1 - if pos < len(self.parent.childList): - return self.parent.childList[pos] - return None + goodSpotRefs = {spot for spot in self.spotRefs if + (self in spot.parentSpot.nodeRef.childList and + spot.parentSpot in spot.parentSpot.nodeRef.spotRefs)} + changed = len(self.spotRefs) != len(goodSpotRefs) + self.spotRefs = goodSpotRefs + if includeChildren and (changed or forceDesend): + for child in self.childList: + child.removeInvalidSpotRefs(includeChildren) - def nextTreeNode(self, loop=False): - """Return the next node in the tree order. + def spotByNumber(self, num): + """Return the spot at the given rank in the spot sequence. - Return None at the end of the tree unless loop is true. Arguments: - loop -- return the root node at the end of the tree if true + num -- the rank number to return """ - if self.childList: - return self.childList[0] - ancestor = self - while ancestor: - sibling = ancestor.nextSibling() - if sibling: - return sibling - ancestor = ancestor.parent - if loop: - return self.modelRef.root - return None + spotList = sorted(list(self.spotRefs), + key=operator.methodcaller('sortKey')) + return spotList[num] - def prevTreeNode(self, loop=False): - """Return the previous node in the tree order. + def matchedSpot(self, parentSpot): + """Return the spot for this node that matches a parent spot. - Return None at the root of the tree unless loop is true. + Return None if not found. Arguments: - loop -- return the last node of the tree after the root if true + parentSpot -- the parent to match """ - sibling = self.prevSibling() - if sibling: - return sibling.lastDescendant() - if self.parent: - return self.parent - elif loop: - return self.lastDescendant() + for spot in self.spotRefs: + if spot.parentSpot is parentSpot: + return spot return None - def lastDescendant(self): - """Return the last node of this node's branch (last in tree order). - """ - node = self - while node.childList: - node = node.childList[-1] - return node + def setInitDefaultData(self, overwrite=False): + """Add initial default data from fields into internal data. - def isValid(self): - """Return True if this node has a valid ancestry. + Arguments: + overwrite -- if true, replace previous data entries """ - node = self - while node.parent: - node = node.parent - return node == self.modelRef.root + self.formatRef.setInitDefaultData(self.data, overwrite) - def isExpanded(self): - """Return True if this node is expanded in the current tree view. - """ - return globalref.mainControl.currentTreeView().isExpanded(self.index()) + def parents(self): + """Return a set of parent nodes for this node. - def expandInView(self): - """Expand this node in the current tree view. + Returns an empty set if called from the tree structure. """ - globalref.mainControl.currentTreeView().expand(self.index()) + try: + return {spot.parentSpot.nodeRef for spot in self.spotRefs} + except AttributeError: + return set() - def collapseInView(self): - """Collapse this node in the current tree view. + def numChildren(self): + """Return number of children. """ - globalref.mainControl.currentTreeView().collapse(self.index()) + return len(self.childList) - def saveExpandViewStatus(self, statusDict=None): - """Recursively save the expand/collapse status of nodes in the branch. + def descendantGen(self): + """Return a generator to step through all nodes in this branch. - Saves by unique ID, returns dictionary. - Arguments: - statusDict -- a dictionary to save in + Includes self and closed nodes. """ - if not statusDict: - statusDict = {} - statusDict[self.uniqueId] = self.isExpanded() - for node in self.childList: - statusDict = node.saveExpandViewStatus(statusDict) - return statusDict - - def restoreExpandViewStatus(self, statusDict): - """Recursively restore expand/collapse status of nodes in the branch. + yield self + for child in self.childList: + for node in child.descendantGen(): + yield node - Arguments: - statusDict -- a dictionary with status by unique ID + def ancestors(self): + """Return a set of all ancestor nodes (including self). """ - try: - expanded = statusDict[self.uniqueId] - if expanded: - self.expandInView() - else: - self.collapseInView() - return - except KeyError: - pass - for node in self.childList: - node.restoreExpandViewStatus(statusDict) - - def setUniqueId(self, validate=False): - """Add this node's unique ID to the ref dict. - - Check for format and uniqueness if not set or if validate is true. - Arguments: - validate -- check for format and uniqueness - """ - if validate or not self.uniqueId: - typeFormat = self.nodeFormat() - self.uniqueId = typeFormat.idField.outputText(self, True, - typeFormat.formatHtml) - self.uniqueId = adjustId(self.uniqueId) - if self.uniqueId in self.modelRef.nodeIdDict: - if self.uniqueId == 'id_1': - self.uniqueId = 'id' - i = 1 - while (self.uniqueId + '_' + repr(i) in - self.modelRef.nodeIdDict): - i += 1 - self.uniqueId = self.uniqueId + '_' + repr(i) - if self.uniqueId in self.modelRef.nodeIdDict: - raise ValueError('duplicate unique ID') - self.modelRef.nodeIdDict[self.uniqueId] = self - - def updateUniqueId(self): - """Update and verify the unique ID and replace the ref dict entry. - """ - if self.uniqueId: - oldId = self.uniqueId - self.removeUniqueId() - self.setUniqueId(True) - self.modelRef.linkRefCollect.renameTarget(oldId, self.uniqueId) - else: - self.setUniqueId(True) + spots = set() + for spot in self.spotRefs: + spots.update(spot.spotChain()) + return {spot.nodeRef for spot in spots} - def removeUniqueId(self): - """Remove the ref dict entry for this unique ID. + def treeStructureRef(self): + """Return the tree structure based on the root spot ref. """ - try: - del self.modelRef.nodeIdDict[self.uniqueId] - except KeyError: - pass - - def treePosSortKey(self): - """Return a sort key used to sort the selection by tree position. + return next(iter(self.spotRefs)).rootSpot().nodeRef - The key is a list of descendant row numbers. + def fileData(self): + """Return the file data dict for this node. """ - nums = [self.row()] - parent = self.parent - while parent: - nums.insert(0, parent.row()) - parent = parent.parent - return nums + children = [node.uId for node in self.childList] + fileData = {'format': self.formatRef.name, 'uid': self.uId, + 'data': self.data, 'children': children} + return fileData - def usesType(self, formatName): - """Return True if dataType is used by self or descendants. + def title(self, spotRef=None): + """Return the title string for this node. + If spotRef not given, ancestor fields assume first spot. Arguments: - formatName -- the type name to search for + spotRef -- optional, used for ancestor field refs """ - for node in self.descendantGen(): - if node.formatName == formatName: - return True - return False + return self.formatRef.formatTitle(self, spotRef) - def title(self): - """Return title info for use in a tree view. - """ - return self.nodeFormat().formatTitle(self) + def setTitle(self, title): + """Change this node's data based on a new title string. - def setTitle(self, title, updateUniqueId=True): - """Set this node's title based on a provided string. - - Match the title format to the string, return True if successful. - Also update the unique ID if previously set and if ID ref field changed - Arguments: - title -- the string with the new title - updateUniqueId -- if True, update the unique ID if necessary + Return True if successfully changed. """ if title == self.title(): return False - idFieldName = self.nodeFormat().idField.name - idData = self.data.get(idFieldName, '') - if self.nodeFormat().extractTitleData(title, self.data): - if updateUniqueId and (not self.uniqueId or - idData != self.data.get(idFieldName, '')): - self.updateUniqueId() - return True - return False + return self.formatRef.extractTitleData(title, self.data) - def formatOutput(self, plainText=False, keepBlanks=False): - """Return a list of formatted text output lines for this node. + def output(self, plainText=False, keepBlanks=False, spotRef=None): + """Return a list of formatted text output lines. + If spotRef not given, ancestor fields assume first spot. Arguments: plainText -- if True, remove HTML markup from fields and formats keepBlanks -- if True, keep lines with empty fields + spotRef -- optional, used for ancestor field refs """ - return self.nodeFormat().formatOutput(self, plainText, keepBlanks) + return self.formatRef.formatOutput(self, plainText, keepBlanks, + spotRef) - def elementXml(self, skipTypeFormats=None, addVersion=True, - extraFormats=True, genericFormats=None, addChildren=True): - """Return an Element object with the XML for this branch. - - Arguments: - skipTypeFormats -- a set of node format types not included in XML - addVersion -- if True, add TreeLine version string - extraFormats -- if True, includes unused format info - genericFormats -- internal set of generic formats to be included - addChildren -- if True, include descendant data - """ - if skipTypeFormats == None: - skipTypeFormats = set() - if genericFormats == None: - genericFormats = set() - nodeFormat = self.nodeFormat() - addFormat = nodeFormat not in skipTypeFormats - element = ElementTree.Element(nodeFormat.name, {'item':'y'}) - # add line feeds to make output somewhat readable - element.tail = '\n' - element.text = '\n' - if addVersion and __version__: - element.set('tlversion', __version__) - element.set('uniqueid', self.uniqueId) - if addFormat: - element.attrib.update(nodeFormat.xmlAttr()) - skipTypeFormats.add(nodeFormat) - if nodeFormat.genericType: - generic = self.modelRef.formats[nodeFormat.genericType] - genericFormats.add(generic) - for field in nodeFormat.fields(): - text = self.data.get(field.name, '') - if text or addFormat: - fieldElement = ElementTree.SubElement(element, field.name) - fieldElement.tail = '\n' - fieldElement.text = text - linkCount = self.modelRef.linkRefCollect.linkCount(self, - field.name) - if linkCount: - fieldElement.attrib['linkcount'] = repr(linkCount) - if addFormat: - fieldElement.attrib.update(field.xmlAttr()) - if field is nodeFormat.idField: - fieldElement.attrib['idref'] = 'y' - if addChildren: - for child in self.childList: - element.append(child.elementXml(skipTypeFormats, False, False, - genericFormats)) - nodeFormats = [] - if extraFormats: # write format info for unused formats - nodeFormats = list(self.modelRef.formats.values()) - if self.modelRef.formats.fileInfoFormat.fieldFormatModified: - nodeFormats.append(self.modelRef.formats.fileInfoFormat) - elif addVersion: - nodeFormats = list(genericFormats) - for nodeFormat in nodeFormats: - if nodeFormat not in skipTypeFormats: - formatElement = ElementTree.SubElement(element, - nodeFormat.name, - {'item':'n'}) - formatElement.tail = '\n' - formatElement.attrib.update(nodeFormat.xmlAttr()) - for field in nodeFormat.fields(): - fieldElement = ElementTree.SubElement(formatElement, - field.name) - fieldElement.tail = '\n' - fieldElement.attrib.update(field.xmlAttr()) - if field is nodeFormat.idField: - fieldElement.attrib['idref'] = 'y' - return element - - def setInitDefaultData(self, overwrite=False): - """Add initial default data from fields into internal data. - - Arguments: - overwrite -- if true, replace previous data entries - """ - self.nodeFormat().setInitDefaultData(self.data, overwrite) - - def changeDataType(self, newTypeName): + def changeDataType(self, formatRef): """Change this node's data type to the given name. Set init default data and update the title if blank. Arguments: - newTypeName -- the name of the new data type + formatRef -- the new tree format type """ origTitle = self.title() - self.formatName = newTypeName - typeFormat = self.nodeFormat() - typeFormat.setInitDefaultData(self.data) - if not typeFormat.formatTitle(self): - typeFormat.extractTitleData(origTitle, self.data) - self.updateUniqueId() + self.formatRef = formatRef + formatRef.setInitDefaultData(self.data) + if not formatRef.formatTitle(self): + formatRef.extractTitleData(origTitle, self.data) - def setConditionalType(self): + def setConditionalType(self, treeStructure): """Set self to type based on auto conditional settings. Return True if type is changed. + Arguments: + treeStructure -- a ref to the tree structure """ - nodeFormat = self.nodeFormat() - if nodeFormat not in self.modelRef.formats.conditionalTypes: + if self.formatRef not in treeStructure.treeFormats.conditionalTypes: return False - if nodeFormat.genericType: - genericFormat = self.modelRef.formats[nodeFormat.genericType] + if self.formatRef.genericType: + genericFormat = treeStructure.treeFormats[self.formatRef. + genericType] else: - genericFormat = nodeFormat + genericFormat = self.formatRef formatList = [genericFormat] + genericFormat.derivedTypes - formatList.remove(nodeFormat) - formatList.insert(0, nodeFormat) # reorder to give priority + formatList.remove(self.formatRef) + formatList.insert(0, self.formatRef) # reorder to give priority neutralResult = None newType = None for typeFormat in formatList: @@ -476,21 +257,23 @@ neutralResult = typeFormat if not newType and neutralResult: newType = neutralResult - if newType and newType is not nodeFormat: - self.changeDataType(newType.name) + if newType and newType is not self.formatRef: + self.changeDataType(newType) return True return False - def setDescendantConditionalTypes(self): + def setDescendantConditionalTypes(self, treeStructure): """Set auto conditional types for self and all descendants. Return number of changes made. + Arguments: + treeStructure -- a ref to the tree structure """ - if not self.modelRef.formats.conditionalTypes: + if not treeStructure.treeFormats.conditionalTypes: return 0 changes = 0 for node in self.descendantGen(): - if node.setConditionalType(): + if node.setConditionalType(treeStructure): changes += 1 return changes @@ -498,139 +281,29 @@ """Set the data entry for the given field to editorText. If the data does not match the format, sets to the raw text and - re-raises the ValueError. + raises a ValueError. Arguments: field-- the field object to be set editorText -- new text data from an editor """ try: self.data[field.name] = field.storedText(editorText) - except ValueError: - self.data[field.name] = editorText - raise ValueError - if field == self.nodeFormat().idField: - self.updateUniqueId() - - def addNewChild(self, posRefNode=None, insertBefore=True, - newTitle=_('New')): - """Add a new child node with this node as the parent. - - Insert the new node near the posRefNode or at the end if no ref node. - Return the new node. - Arguments: - posRefNode -- a child reference for the new node's position - insertBefore -- insert before the ref node if True, after if False - """ - newTypeName = self.nodeFormat().childType - if newTypeName not in self.modelRef.formats: - if posRefNode: - newTypeName = posRefNode.formatName - elif self.childList: - newTypeName = self.childList[0].formatName + except ValueError as err: + if len(err.args) >= 2: + self.data[field.name] = err.args[1] else: - newTypeName = self.formatName - newNode = TreeNode(self, newTypeName, self.modelRef) - pos = len(self.childList) - if posRefNode: - pos = self.childList.index(posRefNode) - if not insertBefore: - pos += 1 - self.childList.insert(pos, newNode) - newNode.setInitDefaultData() - if newTitle and not newNode.title(): - newNode.setTitle(newTitle, False) - newNode.setUniqueId(True) - return newNode - - def replaceChildren(self, titleList): - """Replace child nodes with titles from a text list. - - Nodes with matches in the titleList are kept, others are added or - deleted as required. - Arguments: - titleList -- the list of new child titles - """ - newTypeName = self.nodeFormat().childType - if newTypeName not in self.modelRef.formats: - newTypeName = (self.childList[0].formatName if self.childList else - self.formatName) - matchList = [] - remainTitles = [child.title() for child in self.childList] - for title in titleList: - try: - match = self.childList.pop(remainTitles.index(title)) - matchList.append((title, match)) - remainTitles = [child.title() for child in self.childList] - except ValueError: - matchList.append((title, None)) - newChildList = [] - firstMiss = True - for title, node in matchList: - if not node: - if (firstMiss and remainTitles and - remainTitles[0].startswith(title)): - # accept partial match on first miss for split tiles - node = self.childList.pop(0) - node.setTitle(title) - else: - node = TreeNode(self, newTypeName, self.modelRef) - node.setTitle(title, False) - node.setInitDefaultData() - node.setUniqueId(True) - self.expandInView() - firstMiss = False - newChildList.append(node) - for child in self.childList: - child.parent = None - for node in child.descendantGen(): - node.removeUniqueId() - self.modelRef.linkRefCollect.removeNodeLinks(node) - self.childList = newChildList - - def delete(self): - """Remove this node from tree structure and from unique ID database. - """ - if self.parent: - self.parent.childList.remove(self) - self.parent = None - for node in self.descendantGen(): - node.removeUniqueId() - self.modelRef.linkRefCollect.removeNodeLinks(node) - - def indent(self): - """Make this node a child of the previous sibling. - """ - newParent = self.prevSibling() - if not newParent: - return - oldParent = self.parent - expandDict = oldParent.saveExpandViewStatus() - self.parent.childList.remove(self) - newParent.childList.append(self) - self.parent = newParent - oldParent.restoreExpandViewStatus(expandDict) - - def unindent(self): - """Make this node its parent's next sibling. - """ - sibling = self.parent - if not sibling or not sibling.parent: - return - expandDict = sibling.parent.saveExpandViewStatus() - self.parent.childList.remove(self) - pos = sibling.parent.childList.index(sibling) + 1 - sibling.parent.childList.insert(pos, self) - self.parent = sibling.parent - sibling.parent.restoreExpandViewStatus(expandDict) + self.data[field.name] = editorText + raise ValueError - def wordSearch(self, wordList, titleOnly=False): + def wordSearch(self, wordList, titleOnly=False, spotRef=None): """Return True if all words in wordlist are found in this node's data. Arguments: wordList -- a list of words or phrases to find titleOnly -- search only in the title text if True + spotRef -- an optional spot reference for ancestor field refs """ - dataStr = self.title().lower() + dataStr = self.title(spotRef).lower() if not titleOnly: # join with null char so phrase matches don't cross borders dataStr = '{0}\0{1}'.format(dataStr, @@ -640,14 +313,15 @@ return False return True - def regExpSearch(self, regExpList, titleOnly=False): + def regExpSearch(self, regExpList, titleOnly=False, spotRef=None): """Return True if the regular expression is found in this node's data. Arguments: regExpList -- a list of regular expression objects to find titleOnly -- search only in the title text if True + spotRef -- an optional spot reference for ancestor field refs """ - dataStr = self.title() + dataStr = self.title(spotRef) if not titleOnly: # join with null char so phrase matches don't cross borders dataStr = '{0}\0{1}'.format(dataStr, '\0'.join(self.data.values())) @@ -673,11 +347,10 @@ replaceText -- if not None, replace a match with this string replaceAll -- if True, replace all matches (returns last fieldName) """ - if typeName and typeName != self.formatName: + if typeName and typeName != self.formatRef.name: return ('', 0, 0) - nodeFormat = self.nodeFormat() - fields = ([nodeFormat.fieldDict[fieldName]] if fieldName - else nodeFormat.fields()) + fields = ([self.formatRef.fieldDict[fieldName]] if fieldName + else self.formatRef.fields()) matchedFieldname = '' findCount = 0 prevFieldFindCount = 0 @@ -733,15 +406,149 @@ """ return _origBackrefMatch.group(int(match.group(1))) + def addNewChild(self, treeStructure, posRefNode=None, insertBefore=True, + newTitle=_('New')): + """Add a new child node with this node as the parent. + + Insert the new node near the posRefNode or at the end if no ref node. + Return the new node. + Arguments: + treeStructure -- a ref to the tree structure + posRefNode -- a child reference for the new node's position + insertBefore -- insert before the ref node if True, after if False + """ + try: + newFormat = treeStructure.treeFormats[self.formatRef.childType] + except (KeyError, AttributeError): + if posRefNode: + newFormat = posRefNode.formatRef + elif self.childList: + newFormat = self.childList[0].formatRef + else: + newFormat = self.formatRef + newNode = TreeNode(newFormat) + pos = len(self.childList) + if posRefNode: + pos = self.childList.index(posRefNode) + if not insertBefore: + pos += 1 + self.childList.insert(pos, newNode) + newNode.setInitDefaultData() + newNode.addSpotRef(self) + if newTitle and not newNode.title(): + newNode.setTitle(newTitle) + treeStructure.addNodeDictRef(newNode) + return newNode + + def changeParent(self, oldParentSpot, newParentSpot, newPos=-1): + """Move this node from oldParent to newParent. + + Used for indent and unindent commands. + Arguments: + oldParent -- the original parent spot + newParent -- the new parent spot + newPos -- the position in the new childList, -1 for append + """ + oldParent = oldParentSpot.nodeRef + oldParent.childList.remove(self) + newParent = newParentSpot.nodeRef + if newPos >= 0: + newParent.childList.insert(newPos, self) + else: + newParent.childList.append(self) + # preserve one spot to maintain tree expand state + self.matchedSpot(oldParentSpot).parentSpot = newParentSpot + self.removeInvalidSpotRefs() + self.addSpotRef(newParent) + + def replaceChildren(self, titleList, treeStructure): + """Replace child nodes with titles from a text list. + + Nodes with matches in the titleList are kept, others are added or + deleted as required. + Arguments: + titleList -- the list of new child titles + treeStructure -- a ref to the tree structure + """ + try: + newFormat = treeStructure.treeFormats[self.formatRef.childType] + except (KeyError, AttributeError): + newFormat = (self.childList[0].formatRef if self.childList + else self.formatRef) + matchList = [] + remainTitles = [child.title() for child in self.childList] + for title in titleList: + try: + match = self.childList.pop(remainTitles.index(title)) + matchList.append((title, match)) + remainTitles = [child.title() for child in self.childList] + except ValueError: + matchList.append((title, None)) + newChildList = [] + firstMiss = True + for title, node in matchList: + if not node: + if (firstMiss and remainTitles and + remainTitles[0].startswith(title)): + # accept partial match on first miss for split tiles + node = self.childList.pop(0) + node.setTitle(title) + else: + node = TreeNode(newFormat) + node.setTitle(title) + node.setInitDefaultData() + node.addSpotRef(self) + treeStructure.addNodeDictRef(node) + firstMiss = False + newChildList.append(node) + for child in self.childList: + for oldNode in child.descendantGen(): + if len(oldNode.spotRefs) <= 1: + treeStructure.removeNodeDictRef(oldNode) + else: + oldNode.removeInvalidSpotRefs(False) + self.childList = newChildList + + def replaceClonedBranches(self, origStruct): + """Replace any duplicate IDs with clones from the given structure. + + Recursively search for duplicates. + Arguments: + origStruct -- the tree structure with the cloned nodes + """ + for i in range(len(self.childList)): + if self.childList[i].uId in origStruct.nodeDict: + self.childList[i] = origStruct.nodeDict[self.childList[i].uId] + else: + self.childList[i].replaceClonedBranches(origStruct) + + def loadChildNodeLevels(self, nodeList, initLevel=-1): + """Recursively add children from a list of nodes and levels. + + Return True on success, False if data levels are not valid. + Arguments: + nodeList -- list of tuples with node and level + initLevel -- the level of this node in the structure + """ + while nodeList: + child, level = nodeList[0] + if level == initLevel + 1: + del nodeList[0] + self.childList.append(child) + if not child.loadChildNodeLevels(nodeList, level): + return False + else: + return -1 < level <= initLevel + return True + def fieldSortKey(self, level=0): """Return a key used to sort by key fields. Arguments: level -- the sort key depth level for the current sort stage """ - nodeFormat = self.nodeFormat() - if len(nodeFormat.sortFields) > level: - return nodeFormat.sortFields[level].sortKey(self) + if len(self.formatRef.sortFields) > level: + return self.formatRef.sortFields[level].sortKey(self) return ('',) def sortChildrenByField(self, recursive=True, forward=True): @@ -751,7 +558,7 @@ recursive -- continue to sort recursively if true forward -- reverse the sort if false """ - formats = set([child.nodeFormat() for child in self.childList]) + formats = set([child.formatRef for child in self.childList]) maxDepth = 0 directions = [] for nodeFormat in formats: @@ -794,63 +601,43 @@ for child in self.childList: child.sortChildrenByTitle(True, forward) - def loadChildLevels(self, textLevelList, initLevel=0): - """Recursively add children from a list of text titles and levels. - - Return True on success, False if data levels are not valid. - Arguments: - textLevelList -- list of tuples with title text and level - initLevel -- the level of this node in the structure - """ - while textLevelList: - text, level = textLevelList[0] - if level == initLevel + 1: - del textLevelList[0] - child = TreeNode(self, self.formatName, self.modelRef) - child.setTitle(text) - self.childList.append(child) - if not child.loadChildLevels(textLevelList, level): - return False - else: - return 0 < level <= initLevel - return True - - def updateNodeMathFields(self): + def updateNodeMathFields(self, treeFormats): """Recalculate math fields that depend on this node and so on. Return True if any data was changed. + Arguments: + treeFormats -- a ref to all of the formats """ changed = False - for field in self.nodeFormat().fields(): - for fieldRef in (self.modelRef.formats.mathFieldRefDict. - get(field.name, [])): + for field in self.formatRef.fields(): + for fieldRef in treeFormats.mathFieldRefDict.get(field.name, []): for node in fieldRef.dependentEqnNodes(self): - if node.recalcMathField(fieldRef.eqnFieldName): + if node.recalcMathField(fieldRef.eqnFieldName, + treeFormats): changed = True return changed - def recalcMathField(self, eqnFieldName): + def recalcMathField(self, eqnFieldName, treeFormats): """Recalculate a math field, if changed, recalc depending math fields. Return True if any data was changed. Arguments: eqnFieldName -- the equation field in this node to update + treeFormats -- a ref to all of the formats """ changed = False oldValue = self.data.get(eqnFieldName, '') - newValue = (self.nodeFormat().fieldDict[eqnFieldName]. - equationValue(self)) + newValue = self.formatRef.fieldDict[eqnFieldName].equationValue(self) if newValue != oldValue: self.data[eqnFieldName] = newValue changed = True - for fieldRef in (self.modelRef.formats.mathFieldRefDict. - get(eqnFieldName, [])): + for fieldRef in treeFormats.mathFieldRefDict.get(eqnFieldName, []): for node in fieldRef.dependentEqnNodes(self): - node.recalcMathField(fieldRef.eqnFieldName) + node.recalcMathField(fieldRef.eqnFieldName, treeFormats) return changed def updateNumbering(self, fieldDict, currentSequence, levelLimit, - includeRoot=True, reserveNums=True, + completedClones, includeRoot=True, reserveNums=True, restartSetting=False): """Add auto incremented numbering to fields by type in the dict. @@ -858,88 +645,120 @@ fieldDict -- numbering field name lists stored by type name currentSequence -- a list of int for the current numbering sequence levelLimit -- the number of child levels to include + completedClones -- set of clone nodes already numbered includeRoot -- if Ture, number the current node reserveNums -- if true, increment number even without num field restartSetting -- if true, restart numbering after a no-field gap """ childSequence = currentSequence[:] if includeRoot: - for fieldName in fieldDict.get(self.formatName, []): + for fieldName in fieldDict.get(self.formatRef.name, []): self.data[fieldName] = '.'.join((repr(num) for num in currentSequence)) - if self.formatName in fieldDict or reserveNums: + if self.formatRef.name in fieldDict or reserveNums: childSequence += [1] + if len(self.spotRefs) > 1: + completedClones.add(self.uId) if levelLimit > 0: for child in self.childList: + if len(child.spotRefs) > 1 and child.uId in completedClones: + return child.updateNumbering(fieldDict, childSequence, levelLimit - 1, - True, reserveNums, restartSetting) - if child.formatName in fieldDict or reserveNums: + completedClones, True, reserveNums, + restartSetting) + if child.formatRef.name in fieldDict or reserveNums: childSequence[-1] += 1 - if restartSetting and child.formatName not in fieldDict: + if restartSetting and child.formatRef.name not in fieldDict: childSequence[-1] = 1 - def flatChildCategory(self, origFormats): + def isIdentical(self, node, checkParents=True): + """Return True if node format, data and descendants are identical. + + Also returns False if checkParents & the nodes have parents in common. + Arguments: + node -- the node to check + """ + if (self.formatRef != node.formatRef or + len(self.childList) != len(node.childList) or + self.data != node.data or + (checkParents and not self.parents().isdisjoint(node.parents()))): + return False + for thisChild, otherChild in zip(self.childList, node.childList): + if not thisChild.isIdentical(otherChild, False): + return False + return True + + def flatChildCategory(self, origFormats, structure): """Collapse descendant nodes by merging fields. Overwrites data in any fields with the same name. Arguments: origFormats -- copy of tree formats before any changes + structure -- a ref to the tree structure """ - self.childList = [node for node in self.selectiveDescendantGen() if - not node.childList] - for node in self.childList: - oldParent = node.parent - while oldParent != self: - for field in origFormats[oldParent.formatName].fields(): - data = oldParent.data.get(field.name, '') - if data: - node.data[field.name] = data - node.nodeFormat().addFieldIfNew(field.name, - field.xmlAttr()) - oldParent.removeUniqueId() - oldParent = oldParent.parent - node.parent = self + thisSpot = self.spotByNumber(0) + newChildList = [] + for spot in thisSpot.spotDescendantOnlyGen(): + if not spot.nodeRef.childList: + oldParentSpot = spot.parentSpot + while oldParentSpot != thisSpot: + for field in origFormats[oldParentSpot.nodeRef.formatRef. + name].fields(): + data = oldParentSpot.nodeRef.data.get(field.name, '') + if data: + spot.nodeRef.data[field.name] = data + spot.nodeRef.formatRef.addFieldIfNew(field.name, + field.formatData()) + oldParentSpot = oldParentSpot.parentSpot + spot.parentSpot = thisSpot + newChildList.append(spot.nodeRef) + else: + structure.removeNodeDictRef(spot.nodeRef) + self.childList = newChildList - def addChildCategory(self, catList): + def addChildCategory(self, catList, structure): """Insert category nodes above children. Arguments: catList -- the field names to add to the new level + structure -- a ref to the tree structure """ newFormat = None catSet = set(catList) similarFormats = [nodeFormat for nodeFormat in - self.modelRef.formats.values() if + structure.treeFormats.values() if catSet.issubset(set(nodeFormat.fieldNames()))] if similarFormats: similarFormat = min(similarFormats, key=lambda f: len(f.fieldDict)) if len(similarFormat.fieldDict) < len(self.childList[0]. - nodeFormat().fieldDict): + formatRef.fieldDict): newFormat = similarFormat if not newFormat: newFormatName = '{0}_TYPE'.format(catList[0].upper()) num = 1 - while newFormatName in self.modelRef.formats: + while newFormatName in structure.treeFormats: newFormatName = '{0}_TYPE_{1}'.format(catList[0].upper(), num) num += 1 newFormat = nodeformat.NodeFormat(newFormatName, - self.modelRef.formats) + structure.treeFormats) newFormat.addFieldList(catList, True, True) - self.modelRef.formats[newFormatName] = newFormat + structure.treeFormats[newFormatName] = newFormat newParents = [] for child in self.childList: newParent = child.findEqualFields(catList, newParents) if not newParent: - newParent = TreeNode(self, newFormat.name, self.modelRef) + newParent = TreeNode(newFormat) for field in catList: data = child.data.get(field, '') if data: newParent.data[field] = data - newParent.setUniqueId(True) + structure.addNodeDictRef(newParent) newParents.append(newParent) newParent.childList.append(child) - child.parent = newParent self.childList = newParents + for child in self.childList: + child.removeInvalidSpotRefs(True, True) + child.addSpotRef(self) def findEqualFields(self, fieldNames, nodes): """Return first node in nodes with same data in fieldNames as self. @@ -955,398 +774,13 @@ else: # this for loop didn't hit break, so we have a match return node - def flatChildLink(self, newFieldName): - """Collapse descendant nodes by adding parent links. - - Arguments: - newFieldName -- the new link field name - """ - self.childList = [node for node in self.selectiveDescendantGen()] - for node in self.childList: - node.nodeFormat().addField(newFieldName, {'type': 'InternalLink'}) - linkField = node.nodeFormat().fieldDict[newFieldName] - node.data[newFieldName] = linkField.storedText(node.parent. - uniqueId) - node.childList = [] - node.parent = self - - def arrangeByLink(self, linkField): - """Place descendant nodes under parents found in link fields. - - Arguments: - linkField -- the field name for the parent links - """ - descendList = [node for node in self.selectiveDescendantGen()] - for node in descendList: - node.childList = [] - self.childList = [] - for node in descendList: - parentNode = None - linkMatch = linkref.intLinkRegExp.search(node.data.get(linkField, - '')) - if linkMatch: - parentId = linkMatch.group(1) - parentNode = self.modelRef.nodeIdDict.get(parentId, None) - if not parentNode: - parentNode = self - node.parent = parentNode - parentNode.childList.append(node) - - def exportTitleText(self, level=0, openOnly=False): + def exportTitleText(self, level=0): """Return a list of tabbed title lines for this node and descendants. Arguments: level -- indicates the indent level needed - openOnly -- if True, only include children open in the current view """ textList = ['\t' * level + self.title()] - if not openOnly or self.isExpanded(): - for child in self.childList: - textList.extend(child.exportTitleText(level + 1, openOnly)) - return textList - - def exportHtmlPage(self, level=0): - """Write web pages with navigation for this node and descendents. - - Arguments: - level -- indicates the depth and how far up the css file is - """ - lines = ['', '', '', - '', - ''.format('../' * level), - '{0}'.format(self.title()), - '', '', '', '
    ']) - outputGroup = treeoutput.OutputGroup([self]) - outputGroup.addSiblingPrefixes() - outputLines = outputGroup.getLines() - newLines = [] - global _exportHtmlLevel - _exportHtmlLevel = level - for line in outputLines: - line = _htmlLinkRe.sub(self.localLinkReplace, line) - line = _imgLinkRe.sub(self.localLinkReplace, line) - newLines.append(line) - outputLines = newLines - for linkSet in (self.modelRef.linkRefCollect.nodeRefDict. - get(self, {}).values()): - nodePath = '' - nodeParent = self.parent - while nodeParent: - nodePath = os.path.join(nodeParent.uniqueId, nodePath) - nodeParent = nodeParent.parent - for linkRef in linkSet: - targetNode = self.modelRef.nodeIdDict[linkRef.targetId] - targetPath = targetNode.uniqueId + '.html' - targetParent = targetNode.parent - while targetParent: - targetPath = os.path.join(targetParent.uniqueId, - targetPath) - targetParent = targetParent.parent - targetPath = os.path.relpath(targetPath, nodePath) - newLines = [] - for line in outputLines: - newLines.append(re.sub(r''. - format(targetNode.uniqueId), - ''.format(targetPath), - line)) - outputLines = newLines - lines.extend(outputLines) - lines.extend(['
    ', '', '']) - fileName = self.uniqueId + '.html' - with open(fileName, 'w', encoding='utf-8') as f: - f.writelines([(line + '\n') for line in lines]) - if self.childList: - if not os.access(self.uniqueId, os.R_OK): - os.mkdir(self.uniqueId, 0o755) - os.chdir(self.uniqueId) - for child in self.childList: - child.exportHtmlPage(level + 1) - os.chdir('..') - - @staticmethod - def localLinkReplace(match): - """Replace a local link address with one pointing up several levels. - - Return the modified match string. - Arguments: - match -- the link match object - """ - path = match.group(1) - fullmatch = match.group(0) - if (urltools.isRelative(path) and path[0] != '#' and - not path.startswith('data:')): - fullmatch = fullmatch.replace(path, - '../' * _exportHtmlLevel + path) - return fullmatch - - def exportHtmlTable(self, parentTitle=None, level=1): - """Write web pages with tables for child data to nested directories. - - Arguments: - parentTitle -- the title of the parent page, used in a go-up link - level -- the depth and how far up local links should point - """ - if not self.childList: - return - if not os.access(self.uniqueId, os.R_OK): - os.mkdir(self.uniqueId, 0o755) - os.chdir(self.uniqueId) - title = self.title() - lines = ['', '', '', - '', '{0}'.format(title), - '', ''] - if exports.ExportDialog.addHeader: - headerText = (globalref.mainControl.activeControl.printData. - formatHeaderFooter(True)) - if headerText: - lines.append(headerText) - lines.append('

    {0}

    '.format(title)) - if parentTitle: - lines.append('

    {0}{1}' - '

    '.format('Parent: ', parentTitle)) - lines.extend(['', '']) - lines.extend([''.format(name) for name in - self.childList[0].nodeFormat().fieldNames()]) - lines.append('') - nodePath = self.uniqueId # used for internal link relative paths - nodeParent = self.parent - while nodeParent: - nodePath = os.path.join(nodeParent.uniqueId, nodePath) - nodeParent = nodeParent.parent for child in self.childList: - cellList = [field.outputText(child, False, True) for field in - child.nodeFormat().fields()] - newList = [] - global _exportHtmlLevel - _exportHtmlLevel = level - for cell in cellList: - cell = _htmlLinkRe.sub(self.localLinkReplace, cell) - cell = _imgLinkRe.sub(self.localLinkReplace, cell) - newList.append(cell) - cellList = newList - if child.childList: - cellList[0] = ('{1}'. - format(child.uniqueId, cellList[0])) - if child.uniqueId in self.modelRef.linkRefCollect.targetIdDict: - cellList[0] = '{1}'.format(child.uniqueId, - cellList[0]) - for linkSet in (self.modelRef.linkRefCollect.nodeRefDict. - get(child, {}).values()): - for linkRef in linkSet: - targetNode = self.modelRef.nodeIdDict[linkRef.targetId] - targetPath = 'index.html#{0}'.format(targetNode.uniqueId) - targetParent = targetNode.parent - while targetParent: - targetPath = os.path.join(targetParent.uniqueId, - targetPath) - targetParent = targetParent.parent - targetPath = os.path.relpath(targetPath, nodePath) - fieldNum = (child.nodeFormat().fieldNames(). - index(linkRef.fieldName)) - cellList[fieldNum] = re.sub(r''. - format(targetNode.uniqueId), - ''. - format(targetPath), - cellList[fieldNum]) - lines.extend([''.format(cell) for cell in cellList]) - lines.append('') - lines.extend(['', '
    {0}
    {0}
    ']) - if exports.ExportDialog.addHeader: - footerText = (globalref.mainControl.activeControl.printData. - formatHeaderFooter(False)) - if footerText: - lines.append(footerText) - lines.extend(['', '']) - with open('index.html', 'w', encoding='utf-8') as f: - f.writelines([(line + '\n') for line in lines]) - for child in self.childList: - child.exportHtmlTable(title, level + 1) - os.chdir('..') - - def exportGenericXml(self, addChildren=True): - """Return an ElementTree element with generic XML from this branch. - - Called recursively for children if addChildren is True. - Arguments: - addChildren -- add branch if True - """ - nodeFormat = self.nodeFormat() - element = ElementTree.Element(nodeFormat.name) - element.tail = '\n' - for fieldName in nodeFormat.fieldNames(): - text = self.data.get(fieldName, '') - if text and fieldName != imports.genericXmlTextFieldName: - element.set(fieldName, text) - if imports.genericXmlTextFieldName in nodeFormat.fieldDict: - text = self.data.get(imports.genericXmlTextFieldName, '') - if text: - element.text = text - if addChildren and self.childList: - if not text: - element.text = '\n' - for child in self.childList: - element.append(child.exportGenericXml()) - return element - - def exportOdf(self, parentElem, addChildren=True, level=1, maxLevel=1): - """Add heading and text elements to the parent element tree element. - - Called recursively for children if addChildren is True. - Returns the maximum indent level used for this branch. - Arguments: - parentElem -- the parent element tree element to add to - addChildren -- add branch if True - level -- the current tree indent level - maxLevel -- the previous max indent level - """ - headElem = exports.addOdfElement('text:h', parentElem, - {'text:outline-level': - '{0}'.format(level), - 'text:style-name': - 'Heading_20_{0}'.format(level)}) - headElem.text = self.title() - output = self.formatOutput(True) - if output and output[0] == self.title(): - del output[0] # remove first line if same as title - for line in output: - textElem = exports.addOdfElement('text:p', parentElem, - {'text:outline-level': - '{0}'.format(level), - 'text:style-name': - 'Text_20_body'}) - textElem.text = line - if addChildren and self.childList: - for child in self.childList: - childlevel = child.exportOdf(parentElem, True, level + 1, - maxLevel) - maxLevel = max(childlevel, maxLevel) - else: - maxLevel = max(level, maxLevel) - return maxLevel - - def exportHtmlBookmarks(self, addChildren=True): - """Return a text list ith descendant bookmarks in Mozilla format. - - Called recursively for children if addChildren is True. - Arguments: - addChildren -- add branch if True - """ - title = self.title() - if not self.childList: - nodeFormat = self.nodeFormat() - field = nodeFormat.findLinkField() - if field: - linkMatch = fieldformat.linkRegExp.search(self.data. - get(field.name, '')) - if linkMatch: - link = linkMatch.group(1) - return ['
    {1}'.format(link, title)] - elif (len(nodeFormat.fieldDict) == 1 and not - self.data.get(nodeFormat.fieldNames()[0], '')): - return ['
    '] - result = ['

    {0}

    '.format(title)] - if addChildren: - result.append('

    ') - for child in self.childList: - result.extend(child.exportHtmlBookmarks()) - result.append('

    ') - return result - - def exportXbel(self, addChildren=True): - """Return an ElementTree element with XBEL bookmarks from this branch. - - Called recursively for children if addChildren is True. - Arguments: - addChildren -- add branch if True - """ - titleElem = ElementTree.Element('title') - titleElem.text = self.title() - if not self.childList: - nodeFormat = self.nodeFormat() - field = nodeFormat.findLinkField() - if field: - linkMatch = fieldformat.linkRegExp.search(self.data. - get(field.name, '')) - if linkMatch: - link = linkMatch.group(1) - element = ElementTree.Element('bookmark', {'href': link}) - element.append(titleElem) - element.tail = '\n' - return element - elif (len(nodeFormat.fieldDict) == 1 and not - self.data.get(nodeFormat.fieldNames()[0], '')): - element = ElementTree.Element('separator') - element.tail = '\n' - return element - element = ElementTree.Element('folder') - element.append(titleElem) - element.tail = '\n' - if addChildren: - for child in self.childList: - element.append(child.exportXbel()) - return element - - -#### Utility Functions #### - -def adjustId(uniqueId): - """Adjust unique ID string by shortening and replacing illegal characters. - - Arguments: - uniqueId -- the ID to adjust - """ - # shorten to first line - uniqueId = uniqueId.strip().split('\n', 1)[0] - # shorten to max length - if len(uniqueId) > _maxIdLength: - pos = uniqueId.rfind(' ', _maxIdLength // 2, _maxIdLength + 1) - if pos < 0: - pos = _maxIdLength - uniqueId = uniqueId[:pos] - uniqueId = uniqueId.replace(' ', '_') - # use lower case only (html links are not case sensitive) - uniqueId = uniqueId.lower() - # only alphanumerics, underscores and dashes OK in HTML IDs - uniqueId = _idReplaceCharsRe.sub('', uniqueId) - if not uniqueId: - uniqueId = 'id_1' - # HTML IDs must begin with a letter - elif not 'a' <= uniqueId[0].lower() <= 'z': - uniqueId = 'id_' + uniqueId - return uniqueId + textList.extend(child.exportTitleText(level + 1)) + return textList diff -Nru treeline-2.0.2/source/treeopener.py treeline-3.0.1/source/treeopener.py --- treeline-2.0.2/source/treeopener.py 2015-09-12 21:30:35.000000000 +0000 +++ treeline-3.0.1/source/treeopener.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,209 +0,0 @@ -#!/usr/bin/env python3 - -#****************************************************************************** -# treeopener.py, provides a class to open and import tree data -# -# TreeLine, an information storage program -# Copyright (C) 2015, 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 -# version. This program is distributed in the hope that it will be useful, -# but WITTHOUT ANY WARRANTY. See the included LICENSE file for details. -#****************************************************************************** - -from xml.etree import ElementTree -import xml.sax.saxutils -import os -import sys -import treemodel -import treenode -import nodeformat -import urltools - - -class TreeOpener: - """Class to open or import tree data files - - Creates a new model and provides methods for file open/import. - """ - def __init__(self): - """Initialize a TreeOpenFile object. - """ - self.model = treemodel.TreeModel() - self.rootAttr = {} - self.duplicateIdList = [] - - def readFile(self, filePath): - """Open the given TreeLine file and return the resulting model. - - Arguments: - filePath -- file path or file object to open - """ - tree = ElementTree.ElementTree() - try: - tree.parse(filePath) - except ElementTree.ParseError: - raise ParseError(_('Invalid XML file')) - if not tree.getroot().get('item') == 'y': - raise ParseError(_('Bad elememnt - not a valid TreeLine file')) - version = tree.getroot().get('tlversion', '').split('.') - try: - version = [int(i) for i in version] - except ValueError: - version = [] - self.rootAttr = tree.getroot().attrib - self.model.formats.loadAttr(self.rootAttr) - self.loadNode(tree.getroot(), None) - self.model.formats.updateLineParsing() - if version < [1, 9]: - self.convertOldFormats() - self.convertOldNodes() - if nodeformat.FileInfoFormat.typeName in self.model.formats: - altFormat = self.model.formats[nodeformat.FileInfoFormat.typeName] - self.model.formats.fileInfoFormat.duplicateFieldFormats(altFormat) - del self.model.formats[nodeformat.FileInfoFormat.typeName] - self.model.formats.updateDerivedRefs() - self.model.formats.updateMathFieldRefs() - return self.model - - def loadNode(self, element, parent=None): - """Recursively load an ElementTree node and its children. - - Arguments: - element -- an ElementTree node - parent -- the parent TreeNode (None for the root node only) - """ - try: - typeFormat = self.model.formats[element.tag] - except KeyError: - typeFormat = nodeformat.NodeFormat(element.tag, self.model.formats, - element.attrib) - self.model.formats[element.tag] = typeFormat - if element.get('item') == 'y': - node = treenode.TreeNode(parent, element.tag, self.model, - element.attrib) - if parent: - parent.childList.append(node) - else: - self.model.root = node - else: # bare format (no nodes) - node = None - for child in element: - if child.get('item') and node: - self.loadNode(child, node) - else: - if node and child.text: - node.data[child.tag] = child.text - if child.get('linkcount'): - self.model.linkRefCollect.searchForLinks(node, - child.tag) - typeFormat.addFieldIfNew(child.tag, child.attrib) - if node and typeFormat.fieldDict: - try: - node.setUniqueId() - except ValueError: - oldId = node.uniqueId - node.setUniqueId(True) - self.duplicateIdList.append('{0} -> {1}'.format(oldId, - node.uniqueId)) - - def convertOldFormats(self): - """Convert node and field formats from old TreeLine versions. - - Set node parameters from old file formats, change date & time formats, - set ID ref field. - """ - oldSpaceBetween = not self.rootAttr.get('nospace', '').startswith('y') - oldFormatHtml = not self.rootAttr.get('nohtml', '').startswith('y') - for nodeFormat in self.model.formats.values(): - nodeFormat.spaceBetween = oldSpaceBetween - nodeFormat.formatHtml = oldFormatHtml - for field in nodeFormat.fields(): - if field.oldRef: - nodeFormat.idField = field - if field.typeName == 'Date': - field.format = field.format.replace('w', 'd') - field.format = field.format.replace('m', 'M') - elif field.typeName == 'Time': - field.format = field.format.replace('M', 'm') - field.format = field.format.replace('s', 'z') - field.format = field.format.replace('S', 's') - field.format = field.format.replace('AA', 'AP') - field.format = field.format.replace('aa', 'ap') - elif field.oldTypeName and field.oldTypeName in ('URL', 'Path', - 'ExecuteLink', - 'Email'): - field.changeType('ExternalLink') - - def convertOldNodes(self): - """Convert node data from old TreeLine versions to match new formats. - - Fix escaping of special characters. - """ - for node in self.model.root.descendantGen(): - for field in node.nodeFormat().fields(): - text = node.data.get(field.name, '') - if text: - if field.typeName == 'Text' and not field.oldHasHtml: - text = text.strip() - text = xml.sax.saxutils.escape(text) - text = text.replace('\n', '
    \n') - node.data[field.name] = text - elif (field.typeName == 'ExternalLink' and - field.oldTypeName): - dispName = node.data.get(field.oldLinkAltField, '') - if not dispName: - dispName = text - if field.oldTypeName == 'URL': - if not urltools.extractScheme(text): - text = urltools.replaceScheme('http', text) - elif field.oldTypeName == 'Path': - text = urltools.replaceScheme('file', text) - elif field.oldTypeName == 'ExecuteLink': - if urltools.isRelative(text): - fullPath = which(text) - if fullPath: - text = fullPath - text = urltools.replaceScheme('file', text) - elif field.oldTypeName == 'Email': - text = urltools.replaceScheme('mailto', text) - node.data[field.name] = ('{1}'. - format(text, dispName)) - elif field.typeName == 'InternalLink': - uniqueId = treenode.adjustId(text) - dispName = node.data.get(field.oldLinkAltField, '') - if not dispName: - dispName = uniqueId - node.data[field.name] = ('{1}'. - format(uniqueId, dispName)) - elif field.typeName == 'Picture': - node.data[field.name] = (''. - format(text)) - if node.nodeFormat().fields(): # skip for dummy root - node.updateUniqueId() - - -class ParseError(Exception): - """Exception raised when the file is not a valid format. - """ - pass - - -def which(fileName): - """Return the full path if the fileName is found somewhere in the PATH. - - If not found, return an empty string. - Similar to the Linux which command. - Arguments: - fileName -- the name to search for - """ - extList = [''] - if sys.platform.startswith('win'): - extList.extend(os.getenv('PATHEXT', '').split(os.pathsep)) - for path in os.get_exec_path(): - for ext in extList: - fullPath = os.path.join(path, fileName + ext) - if os.access(fullPath, os.X_OK): - return fullPath - return '' diff -Nru treeline-2.0.2/source/treeoutput.py treeline-3.0.1/source/treeoutput.py --- treeline-2.0.2/source/treeoutput.py 2014-03-03 21:39:58.000000000 +0000 +++ treeline-3.0.1/source/treeoutput.py 2018-04-08 19:19:50.000000000 +0000 @@ -4,7 +4,7 @@ # treeoutput.py, provides classes for output to views, html and printing # # TreeLine, an information storage program -# Copyright (C) 2014, Douglas W. Bell +# Copyright (C) 2017, 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 @@ -12,38 +12,36 @@ # but WITTHOUT ANY WARRANTY. See the included LICENSE file for details. #****************************************************************************** +import re import itertools import copy -from PyQt4 import QtCore, QtGui +from PyQt5.QtGui import QTextDocument import globalref +_linkRe = re.compile(r']*href="#(.*?)"[^>]*>.*?', re.I | re.S) + class OutputItem: """Class to store output for a single node. Stores text lines and original indent level. """ - def __init__(self, node, level, addAnchor=False, forceAnchor=False): - """Convert the node into an output item. + def __init__(self, spot, level): + """Convert the spot's node into an output item. Arguments: - node -- the tree node to convert + spot -- the tree spot to convert level -- the node's original indent level - addAnchor -- if true, add an ID anchor if node is a link target - forceAnchor -- if true, add an ID anchor in any case """ - nodeFormat = node.nodeFormat() + node = spot.nodeRef + nodeFormat = node.formatRef if not nodeFormat.useTables: self.textLines = [line + '
    ' for line in - node.formatOutput(False)] + node.output(spotRef=spot)] else: - self.textLines = node.formatOutput(False, True) + self.textLines = node.output(keepBlanks=True, spotRef=spot) if not self.textLines: self.textLines = [''] - if (forceAnchor or (addAnchor and node.uniqueId in - node.modelRef.linkRefCollect.targetIdDict)): - self.textLines[0] = '{1}'.format(node.uniqueId, - self.textLines[0]) self.addSpace = nodeFormat.spaceBetween self.siblingPrefix = nodeFormat.siblingPrefix self.siblingSuffix = nodeFormat.siblingSuffix @@ -51,6 +49,7 @@ # remove
    extra space for bullets self.textLines[-1] = self.textLines[-1][:-6] self.level = level + self.uId = node.uId # following variables used by printdata only: height = 0 self.pageNum = 0 @@ -99,6 +98,28 @@ if self.siblingSuffix: self.textLines[-1] += self.siblingSuffix + def addAnchor(self): + """Add a link anchor to this item. + """ + self.textLines[0] = '
    {1}'.format(self.uId, + self.textLines[0]) + + def intLinkIds(self): + """Return a set of uIDs from any internal links in this item. + """ + linkIds = set() + for line in self.textLines: + startPos = 0 + while True: + match = _linkRe.search(line, startPos) + if not match: + break + uId = match.group(1) + if uId: + linkIds.add(uId) + startPos = match.start(1) + return linkIds + def numLines(self): """Return the number of text lines in the item. """ @@ -125,7 +146,7 @@ replaceDoc -- if true, re-create the text document """ if not self.doc or replaceDoc: - self.doc = QtGui.QTextDocument() + self.doc = QTextDocument() lines = '\n'.join(self.textLines) if lines.endswith('
    '): # remove trailing
    tag to avoid excess space @@ -181,6 +202,8 @@ newWords = [] prevHeight = 0 for word in allWords: + if word.strip() == ' maxHeight: + break prevHeight = self.height - return (None, None) # fail to split + newItem.setDocHeight(paintDevice, width, printFont, True) + return (newItem, None) # fail to split class OutputGroup(list): @@ -201,45 +227,37 @@ Modifies the output text for use in views, html and printing. """ - def __init__(self, nodeList, includeRoot=True, includeDescend=False, - openOnly=False, addAnchors=False, extraAnchorLevels=0): + def __init__(self, spotList, includeRoot=True, includeDescend=False, + openOnly=False): """Convert the node iter list into a list of output items. Arguments: - nodeList -- a list of nodes to convert to output + spotList -- a list of spots to convert to output includeRoot -- if True, include the nodes in nodeList includeDescend -- if True, include children, grandchildren, etc. openOnly -- if true, ignore collapsed children in the main treeView - addAnchors -- if true, add ID anchors to nodes used as link targets - extraAnchorLevels -- add extra anchors if the level < this """ super().__init__() - for node in nodeList: + for spot in spotList: level = -1 if includeRoot: level = 0 - self.append(OutputItem(node, level, addAnchors, - level < extraAnchorLevels)) + self.append(OutputItem(spot, level)) if includeDescend: - self.addChildren(node, level, openOnly, addAnchors, - extraAnchorLevels) + self.addChildren(spot, level, openOnly) - def addChildren(self, node, level, openOnly=False, addAnchors=False, - extraAnchorLevels=0): - """Recursively add OutputItems for descendants of the given node. + def addChildren(self, spot, level, openOnly=False): + """Recursively add OutputItems for descendants of the given spot. Arguments: - node -- the parent tree node + spot -- the parent tree spot level -- the parent node's original indent level - addAnchors - if true, add ID anchors to nodes used as link targets - extraAnchorLevels -- add extra anchors if the level < this """ - if not openOnly or node.isExpanded(): - for child in node.childList: - self.append(OutputItem(child, level + 1, addAnchors, - level + 1 < extraAnchorLevels)) - self.addChildren(child, level + 1, openOnly, addAnchors, - extraAnchorLevels) + treeView = globalref.mainControl.activeControl.activeWindow.treeView + if not openOnly or treeView.isSpotExpanded(spot): + for child in spot.childSpots(): + self.append(OutputItem(child, level + 1)) + self.addChildren(child, level + 1, openOnly) def addIndents(self): """Add nested

    elements to define indentations in the output. @@ -270,6 +288,19 @@ if item.addSpace or nextItem.addSpace: item.textLines[-1] += '
    ' + def addAnchors(self, extraLevels=0): + """Add anchors to items that are targets and to low level items. + + Arguments: + extraLevels -- force adding anchors if level < this + """ + linkIds = set() + for item in self: + linkIds.update(item.intLinkIds()) + for item in self: + if item.uId in linkIds or item.level < extraLevels: + item.addAnchor() + def hasPrefixes(self): """Return True if sibling prefixes or suffixes are found. """ diff -Nru treeline-2.0.2/source/treeselection.py treeline-3.0.1/source/treeselection.py --- treeline-2.0.2/source/treeselection.py 2015-01-10 19:27:07.000000000 +0000 +++ treeline-3.0.1/source/treeselection.py 2018-06-23 18:03:42.000000000 +0000 @@ -4,7 +4,7 @@ # treeselection.py, provides a class for the tree view's selection model # # TreeLine, an information storage program -# Copyright (C) 2015, Douglas W. Bell +# Copyright (C) 2018, 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 @@ -12,14 +12,19 @@ # but WITTHOUT ANY WARRANTY. See the included LICENSE file for details. #****************************************************************************** -import operator -from PyQt4 import QtCore, QtGui -import treenodelist +import collections +import json +from PyQt5.QtCore import QItemSelectionModel, QMimeData +from PyQt5.QtGui import QClipboard +from PyQt5.QtWidgets import QApplication +import treestructure +import treespotlist +import globalref _maxHistoryLength = 10 -class TreeSelection(QtGui.QItemSelectionModel): +class TreeSelection(QItemSelectionModel): """Class override for the tree view's selection model. Provides methods for easier access to selected nodes. @@ -28,171 +33,233 @@ """Initialize the selection model. Arguments: - model -- the initial model for view data + model -- the model for view data parent -- the parent tree view """ super().__init__(model, parent) - self.tempExpandedNodes = [] - self.previousNodes = [] - self.nextNodes = [] + self.modelRef = model + self.tempExpandedSpots = [] + self.prevSpots = [] + self.nextSpots = [] self.restoreFlag = False self.selectionChanged.connect(self.updateSelectLists) - def selectedNodes(self): - """Return a TreeNodeList of the currently selected tree nodes. + def selectedCount(self): + """Return the number of selected spots. + """ + return len(self.selectedIndexes()) + + def selectedSpots(self): + """Return a SpotList of selected spots, sorted in tree order. """ - return treenodelist.TreeNodeList([index.internalPointer() for index in + return treespotlist.TreeSpotList([index.internalPointer() for index in self.selectedIndexes()]) - def currentNode(self): - """Return the current tree node. + def selectedBranchSpots(self): + """Return a SpotList of spots at the top of selected branches. + + Remvoves any duplicate spots that are already covered by the branches. """ - return self.currentIndex().internalPointer() + spots = self.selectedSpots() + spotSet = set(spots) + return treespotlist.TreeSpotList([spot for spot in spots if + spot.parentSpotSet(). + isdisjoint(spotSet)]) + + def selectedNodes(self): + """Return a list of the currently selected tree nodes. - def uniqueBranches(self): - """Return a TreeNodeList of selected nodes on top of unique branches. + Removes any duplicate (cloned) nodes. + """ + tmpDict = collections.OrderedDict() + for spot in self.selectedSpots(): + node = spot.nodeRef + tmpDict[node.uId] = node + return list(tmpDict.values()) + + def selectedBranches(self): + """Return a list of nodes at the top of selected branches. - Eliminate nodes that are already descendants of other selected nodes. + Remvoves any duplicates that are already covered by the branches. """ - result = treenodelist.TreeNodeList() - selectedNodes = self.selectedNodes() - for node in selectedNodes: - parent = node.parent - while parent and parent not in selectedNodes: - parent = parent.parent - if not parent: - result.append(node) - return result - - def selectNode(self, node, signalUpdate=True, expandParents=False): - """Clear the current selection and select the given node. - - Arguments: - node -- the TreeNode to be selected - signalUpdate -- if False, block normal right-view update signals - expandParents -- open parent nodes to make selection visible + tmpDict = collections.OrderedDict() + for spot in self.selectedBranchSpots(): + node = spot.nodeRef + tmpDict[node.uId] = node + return list(tmpDict.values()) + + def currentSpot(self): + """Return the current tree spot. + + Can raise AttributeError if no spot is current. """ - expandedNodes = [] - if expandParents: - for expNode in self.tempExpandedNodes: - expNode.collapseInView() - self.tempExpandedNodes = [] - parent = node.parent - while parent: - if not parent.isExpanded(): - parent.expandInView() - expandedNodes.append(parent) - parent = parent.parent - if not signalUpdate: - self.blockSignals(True) - self.addToHistory([node]) - self.clear() - self.setCurrentIndex(node.index(), QtGui.QItemSelectionModel.Select) - self.blockSignals(False) - self.tempExpandedNodes = expandedNodes + return self.currentIndex().internalPointer() - def selectNodes(self, nodeList, signalUpdate=True, expandParents=False): - """Clear the current selection and select the nodes in the given list. + def currentNode(self): + """Return the current tree node. + + Can raise AttributeError if no node is current. + """ + return self.currentSpot().nodeRef + + def selectSpots(self, spotList, signalUpdate=True, expandParents=False): + """Clear the current selection and select the given spots. Arguments: - nodeList -- a list of nodes to be selected. - signalUpdate -- if False, block normal right-view update signals - expandParents -- open parent nodes to make selection visible + spotList -- the spots to select + signalUpdate -- if False, block normal select update signals + expandParents -- open parent spots to make selection visible """ - expandedNodes = [] if expandParents: - for expNode in self.tempExpandedNodes: - expNode.collapseInView() - self.tempExpandedNodes = [] - for node in nodeList: - parent = node.parent - while parent: - if not parent.isExpanded(): - parent.expandInView() - expandedNodes.append(parent) - parent = parent.parent + treeView = (globalref.mainControl.activeControl.activeWindow. + treeView) + for spot in self.tempExpandedSpots: + treeView.collapseSpot(spot) + self.tempExpandedSpots = [] + for spot in spotList: + parent = spot.parentSpot + while parent.parentSpot: + if not treeView.isSpotExpanded(parent): + treeView.expandSpot(parent) + self.tempExpandedSpots.append(parent) + parent = parent.parentSpot if not signalUpdate: self.blockSignals(True) - self.addToHistory(nodeList) + self.addToHistory(spotList) self.clear() - for node in nodeList: - self.select(node.index(), QtGui.QItemSelectionModel.Select) - if nodeList: - self.setCurrentIndex(nodeList[0].index(), - QtGui.QItemSelectionModel.Current) + if spotList: + for spot in spotList: + self.select(spot.index(self.modelRef), + QItemSelectionModel.Select) + self.setCurrentIndex(spotList[0].index(self.modelRef), + QItemSelectionModel.Current) self.blockSignals(False) - self.tempExpandedNodes = expandedNodes def selectNodeById(self, nodeId): - """Select a node when given its unique ID and return True. + """Select the first spot from the given node ID. - Return False if not found. + Return True on success. Arguments: - nodeId -- the unique ID string for the node + nodeId -- the ID of the node to select """ try: - self.selectNode(self.model().nodeIdDict[nodeId]) - return True + node = self.modelRef.treeStructure.nodeDict[nodeId] + self.selectSpots([node.spotByNumber(0)], True, True) except KeyError: return False + return True - def sortSelection(self): - """Sorts the selection by tree position. - """ - self.selectNodes(sorted(self.selectedNodes(), - key=operator.methodcaller('treePosSortKey')), - False) - - def addToHistory(self, nodes): - """Add given nodes to previous select list. + def setCurrentSpot(self, spot): + """Set the current spot. Arguments: - nodes -- a list of nodes to be added + spot -- the spot to make current + """ + self.blockSignals(True) + self.setCurrentIndex(spot.index(self.modelRef), + QItemSelectionModel.Current) + self.blockSignals(False) + + def copySelectedNodes(self): + """Copy these node branches to the clipboard. """ - if nodes and not self.restoreFlag and (not self.previousNodes or - nodes != self.previousNodes[-1]): - self.previousNodes.append(nodes) - if len(self.previousNodes) > _maxHistoryLength: - del self.previousNodes[:2] - self.nextNodes = [] + nodes = self.selectedBranches() + if not nodes: + return + clip = QApplication.clipboard() + if clip.supportsSelection(): + titleList = [] + for node in nodes: + titleList.extend(node.exportTitleText()) + clip.setText('\n'.join(titleList), QClipboard.Selection) + struct = treestructure.TreeStructure(topNodes=nodes, addSpots=False) + generics = {formatRef.genericType for formatRef in + struct.treeFormats.values() if formatRef.genericType} + for generic in generics: + genericRef = self.modelRef.treeStructure.treeFormats[generic] + struct.treeFormats.addTypeIfMissing(genericRef) + for formatRef in genericRef.derivedTypes: + struct.treeFormats.addTypeIfMissing(formatRef) + data = struct.fileData() + dataStr = json.dumps(data, indent=0, sort_keys=True) + mime = QMimeData() + mime.setData('application/json', bytes(dataStr, encoding='utf-8')) + clip.setMimeData(mime) def restorePrevSelect(self): """Go back to the most recent saved selection. """ self.validateHistory() - if len(self.previousNodes) > 1: - del self.previousNodes[-1] - oldSelect = self.selectedNodes() - if oldSelect and (not self.nextNodes or - oldSelect != self.nextNodes[-1]): - self.nextNodes.append(oldSelect) + if len(self.prevSpots) > 1: + del self.prevSpots[-1] + oldSelect = self.selectedSpots() + if oldSelect and (not self.nextSpots or + oldSelect != self.nextSpots[-1]): + self.nextSpots.append(oldSelect) self.restoreFlag = True - self.selectNodes(self.previousNodes[-1], expandParents=True) + self.selectSpots(self.prevSpots[-1], expandParents=True) self.restoreFlag = False def restoreNextSelect(self): """Go forward to the most recent saved selection. """ self.validateHistory() - if self.nextNodes: - select = self.nextNodes.pop(-1) - if select and (not self.previousNodes or - select != self.previousNodes[-1]): - self.previousNodes.append(select) + if self.nextSpots: + select = self.nextSpots.pop(-1) + if select and (not self.prevSpots or + select != self.prevSpots[-1]): + self.prevSpots.append(select) self.restoreFlag = True - self.selectNodes(select, expandParents=True) + self.selectSpots(select, expandParents=True) self.restoreFlag = False + def addToHistory(self, spots): + """Add given spots to previous select list. + + Arguments: + spots -- a list of spots to be added + """ + if spots and not self.restoreFlag and (not self.prevSpots or + spots != self.prevSpots[-1]): + self.prevSpots.append(spots) + if len(self.prevSpots) > _maxHistoryLength: + del self.prevSpots[:2] + self.nextSpots = [] + def validateHistory(self): """Clear invalid items from history lists. """ - for histList in (self.previousNodes, self.nextNodes): - for nodeList in histList: - nodeList[:] = [node for node in nodeList if node.isValid()] - histList[:] = [nodeList for nodeList in histList if nodeList] + for histList in (self.prevSpots, self.nextSpots): + for spots in histList: + spots[:] = [spot for spot in spots if spot.isValid()] + histList[:] = [spots for spots in histList if spots] def updateSelectLists(self): - """Update history and clear temp expanded nodes after a select change. + """Update history after a selection change. """ - self.addToHistory(self.selectedNodes()) - self.tempExpandedNodes = [] + self.addToHistory(self.selectedSpots()) + + def selectTitleMatch(self, searchText, forward=True, includeCurrent=False): + """Select a node with a title matching the search text. + + Returns True if found, otherwise False. + Arguments: + searchText -- the text to look for + forward -- next if True, previous if False + includeCurrent -- look in current node if True + """ + searchText = searchText.lower() + currentSpot = self.currentSpot() + spot = currentSpot + while True: + if not includeCurrent: + if forward: + spot = spot.nextTreeSpot(True) + else: + spot = spot.prevTreeSpot(True) + if spot is currentSpot: + return False + includeCurrent = False + if searchText in spot.nodeRef.title().lower(): + self.selectSpots([spot], True, True) + return True diff -Nru treeline-2.0.2/source/treespotlist.py treeline-3.0.1/source/treespotlist.py --- treeline-2.0.2/source/treespotlist.py 1970-01-01 00:00:00.000000000 +0000 +++ treeline-3.0.1/source/treespotlist.py 2018-06-23 18:03:42.000000000 +0000 @@ -0,0 +1,312 @@ +#!/usr/bin/env python3 + +#****************************************************************************** +# treespotlist.py, provides a class to do operations on groups of spots +# +# TreeLine, an information storage program +# Copyright (C) 2018, 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 +# version. This program is distributed in the hope that it will be useful, +# but WITTHOUT ANY WARRANTY. See the included LICENSE file for details. +#****************************************************************************** + +import collections +import operator +from PyQt5.QtWidgets import QApplication +import treestructure +import undo + + +class TreeSpotList(list): + """Class to do operations on groups of spots. + + Stores a list of nodes. + """ + def __init__(self, spotList=None, sortSpots=True): + """Initialize a tree spot group. + + Arguments: + spotList -- the initial list of spots + sortSpots -- if True sort the spots in tree order + """ + super().__init__() + if spotList: + self[:] = spotList + if sortSpots: + self.sort(key=operator.methodcaller('sortKey')) + + def relatedNodes(self): + """Return a list of nodes related to these spots. + + Removes any duplicate (cloned) nodes. + """ + tmpDict = collections.OrderedDict() + for spot in self: + node = spot.nodeRef + tmpDict[node.uId] = node + return list(tmpDict.values()) + + def pasteChild(self, treeStruct, treeView): + """Paste child nodes from the clipbaord. + + Return True on success. + Arguments: + treeStruct -- a ref to the existing tree structure + treeView -- a ref to the tree view for expanding nodes + """ + mimeData = QApplication.clipboard().mimeData() + parentNodes = self.relatedNodes() + if not parentNodes: + parentNodes = [treeStruct] + undoObj = undo.ChildListUndo(treeStruct.undoList, parentNodes, + treeFormats=treeStruct.treeFormats) + for parent in parentNodes: + newStruct = treestructure.structFromMimeData(mimeData) + if not newStruct: + treeStruct.undoList.removeLastUndo(undoObj) + return False + newStruct.replaceDuplicateIds(treeStruct.nodeDict) + treeStruct.addNodesFromStruct(newStruct, parent) + for spot in self: + treeView.expandSpot(spot) + return True + + def pasteSibling(self, treeStruct, insertBefore=True): + """Paste a sibling at the these spots. + + Return True on success. + Arguments: + treeStruct -- a ref to the existing tree structure + insertBefore -- if True, insert before these nodes, o/w after + """ + mimeData = QApplication.clipboard().mimeData() + parentNodes = [spot.parentSpot.nodeRef for spot in self] + undoObj = undo.ChildListUndo(treeStruct.undoList, parentNodes, + treeFormats=treeStruct.treeFormats) + for spot in self: + newStruct = treestructure.structFromMimeData(mimeData) + if not newStruct: + treeStruct.undoList.removeLastUndo(undoObj) + return False + newStruct.replaceDuplicateIds(treeStruct.nodeDict) + parent = spot.parentSpot.nodeRef + pos = parent.childList.index(spot.nodeRef) + if not insertBefore: + pos += 1 + treeStruct.addNodesFromStruct(newStruct, parent, pos) + return True + + def pasteCloneChild(self, treeStruct, treeView): + """Paste child clones from the clipbaord. + + Return True on success. + Arguments: + treeStruct -- a ref to the existing tree structure + treeView -- a ref to the tree view for expanding nodes + """ + mimeData = QApplication.clipboard().mimeData() + newStruct = treestructure.structFromMimeData(mimeData) + if not newStruct: + return False + try: + existNodes = [treeStruct.nodeDict[node.uId] for node in + newStruct.childList] + except KeyError: + return False # nodes copied from other file + parentNodes = self.relatedNodes() + if not parentNodes: + parentNodes = [treeStruct] + for parent in parentNodes: + if not parent.ancestors().isdisjoint(set(existNodes)): + return False # circular ref + for node in existNodes: + if parent in node.parents(): + return False # identical siblings + undoObj = undo.ChildListUndo(treeStruct.undoList, parentNodes, + treeFormats=treeStruct.treeFormats) + for parent in parentNodes: + for node in existNodes: + parent.childList.append(node) + node.addSpotRef(parent) + for spot in self: + treeView.expandSpot(spot) + return True + + def pasteCloneSibling(self, treeStruct, insertBefore=True): + """Paste sibling clones at the these spots. + + Return True on success. + Arguments: + treeStruct -- a ref to the existing tree structure + insertBefore -- if True, insert before these nodes, o/w after + """ + mimeData = QApplication.clipboard().mimeData() + newStruct = treestructure.structFromMimeData(mimeData) + if not newStruct: + return False + try: + existNodes = [treeStruct.nodeDict[node.uId] for node in + newStruct.childList] + except KeyError: + return False # nodes copied from other file + parentNodes = [spot.parentSpot.nodeRef for spot in self] + for parent in parentNodes: + if not parent.ancestors().isdisjoint(set(existNodes)): + return False # circular ref + for node in existNodes: + if parent in node.parents(): + return False # identical siblings + undoObj = undo.ChildListUndo(treeStruct.undoList, parentNodes, + treeFormats=treeStruct.treeFormats) + for spot in self: + parent = spot.parentSpot.nodeRef + pos = parent.childList.index(spot.nodeRef) + if not insertBefore: + pos += 1 + for node in existNodes: + parent.childList.insert(pos, node) + node.addSpotRef(parent) + return True + + def addChild(self, treeStruct, treeView): + """Add new child to these spots. + + Return the new spots. + Arguments: + treeStruct -- a ref to the existing tree structure + treeView -- a ref to the tree view for expanding nodes + """ + selSpots = self + if not selSpots: + selSpots = list(treeStruct.spotRefs) + undo.ChildListUndo(treeStruct.undoList, [spot.nodeRef for spot in + selSpots]) + newSpots = [] + for spot in selSpots: + newNode = spot.nodeRef.addNewChild(treeStruct) + newSpots.append(newNode.matchedSpot(spot)) + if spot.parentSpot: # can't expand root struct spot + treeView.expandSpot(spot) + return newSpots + + def insertSibling(self, treeStruct, insertBefore=True): + """Insert a new sibling node at these nodes. + + Return the new spots. + Arguments: + treeStruct -- a ref to the existing tree structure + insertBefore -- if True, insert before these nodes, o/w after + """ + undo.ChildListUndo(treeStruct.undoList, [spot.parentSpot.nodeRef for + spot in self]) + newSpots = [] + for spot in self: + newNode = spot.parentSpot.nodeRef.addNewChild(treeStruct, + spot.nodeRef, + insertBefore) + newSpots.append(newNode.matchedSpot(spot.parentSpot)) + return newSpots + + def delete(self, treeStruct): + """Delete these spots, return a new spot to select. + + Arguments: + treeStruct -- a ref to the existing tree structure + """ + # gather next selected node in decreasing order of desirability + nextSel = [spot.nextSiblingSpot() for spot in self] + nextSel.extend([spot.prevSiblingSpot() for spot in self]) + nextSel.extend([spot.parentSpot for spot in self]) + while (not nextSel[0] or not nextSel[0].parentSpot or + nextSel[0] in self): + del nextSel[0] + spotSet = set(self) + branchSpots = [spot for spot in self if + spot.parentSpotSet().isdisjoint(spotSet)] + undoParents = {spot.parentSpot.nodeRef for spot in branchSpots} + undo.ChildListUndo(treeStruct.undoList, list(undoParents)) + for spot in branchSpots: + treeStruct.deleteNodeSpot(spot) + return nextSel[0] + + def indent(self, treeStruct): + """Indent these spots. + + Makes them children of their previous siblings. + Return the new spots. + Arguments: + treeStruct -- a ref to the existing tree structure + """ + undoSpots = ([spot.parentSpot for spot in self] + + [spot.prevSiblingSpot() for spot in self]) + undo.ChildListUndo(treeStruct.undoList, [spot.nodeRef for spot in + undoSpots]) + newSpots = [] + for spot in self: + node = spot.nodeRef + newParentSpot = spot.prevSiblingSpot() + node.changeParent(spot.parentSpot, newParentSpot) + newSpots.append(node.matchedSpot(newParentSpot)) + return newSpots + + def unindent(self, treeStruct): + """Unindent these spots. + + Makes them their parent's next sibling. + Return the new spots. + Arguments: + treeStruct -- a ref to the existing tree structure + """ + undoSpots = [spot.parentSpot for spot in self] + undoSpots.extend([spot.parentSpot for spot in undoSpots]) + undo.ChildListUndo(treeStruct.undoList, [spot.nodeRef for spot in + undoSpots]) + newSpots = [] + for spot in reversed(self): + node = spot.nodeRef + oldParentSpot = spot.parentSpot + newParentSpot = oldParentSpot.parentSpot + pos = (newParentSpot.nodeRef.childList.index(oldParentSpot.nodeRef) + + 1) + node.changeParent(oldParentSpot, newParentSpot, pos) + newSpots.append(node.matchedSpot(newParentSpot)) + return newSpots + + def move(self, treeStruct, up=True): + """Move these spots up or down by one item. + + Arguments: + treeStruct -- a ref to the existing tree structure + up -- if True move up, o/w down + """ + undo.ChildListUndo(treeStruct.undoList, [spot.parentSpot.nodeRef + for spot in self]) + if not up: + self.reverse() + for spot in self: + parent = spot.parentSpot.nodeRef + pos = parent.childList.index(spot.nodeRef) + del parent.childList[pos] + pos = pos - 1 if up else pos + 1 + parent.childList.insert(pos, spot.nodeRef) + + def moveToEnd(self, treeStruct, first=True): + """Move these spots to the first or last position. + + Arguments: + treeStruct -- a ref to the existing tree structure + first -- if True move to first position, o/w last + """ + undo.ChildListUndo(treeStruct.undoList, [spot.parentSpot.nodeRef + for spot in self]) + if first: + self.reverse() + for spot in self: + parent = spot.parentSpot.nodeRef + parent.childList.remove(spot.nodeRef) + if first: + parent.childList.insert(0, spot.nodeRef) + else: + parent.childList.append(spot.nodeRef) diff -Nru treeline-2.0.2/source/treespot.py treeline-3.0.1/source/treespot.py --- treeline-2.0.2/source/treespot.py 1970-01-01 00:00:00.000000000 +0000 +++ treeline-3.0.1/source/treespot.py 2018-09-26 01:15:36.000000000 +0000 @@ -0,0 +1,242 @@ +#!/usr/bin/env python3 + +#****************************************************************************** +# treespot.py, provides a class to store locations of tree node instances +# +# TreeLine, an information storage program +# Copyright (C) 2018, 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 +# version. This program is distributed in the hope that it will be useful, +# but WITTHOUT ANY WARRANTY. See the included LICENSE file for details. +#****************************************************************************** + +import sys +import operator + + +class TreeSpot: + """Class to store location info for tree node instances. + + Used to generate breadcrumb navigation and interface with tree views. + A spot without a parent spot is an imaginary root spot, wihout a real node. + """ + def __init__(self, nodeRef, parentSpot): + """Initialize a tree spot. + + Arguments: + nodeRef -- reference to the associated tree node + parentSpot -- the parent TreeSpot object + """ + self.nodeRef = nodeRef + self.parentSpot = parentSpot + + def index(self, modelRef): + """Returns the index of this spot in the tree model. + + Arguments: + modelRef -- a ref to the tree model + """ + return modelRef.createIndex(self.row(), 0, self) + + def row(self): + """Return the rank of this spot in its parent's child list. + + Should never be called from the imaginary root spot. + """ + try: + return self.parentSpot.nodeRef.childList.index(self.nodeRef) + except ValueError: + return 0 # avoid error message from interim view updates + + def instanceNumber(self): + """Return this spot's rank in the node's spot list. + """ + spotList = sorted(list(self.nodeRef.spotRefs), + key=operator.methodcaller('sortKey')) + return spotList.index(self) + + def spotId(self): + """Return a spot ID string, in the form "nodeID:spotInstance". + """ + return '{0}:{1:d}'.format(self.nodeRef.uId, self.instanceNumber()) + + def isValid(self): + """Return True if spot references and all parents are valid. + """ + spot = self + while spot.parentSpot: + if not (spot in spot.nodeRef.spotRefs and + spot.nodeRef in spot.parentSpot.nodeRef.childList): + return False + spot = spot.parentSpot + if not spot in spot.nodeRef.spotRefs: + return False + return True + + def spotDescendantGen(self): + """Return a generator to step through all spots in this branch. + + Includes self. + """ + yield self + for childSpot in self.childSpots(): + for spot in childSpot.spotDescendantGen(): + yield spot + + def spotDescendantOnlyGen(self): + """Return a generator to step through the spots in this branch. + + Does not include self. + """ + for childSpot in self.childSpots(): + yield childSpot + for spot in childSpot.spotDescendantGen(): + yield spot + + def expandedSpotDescendantGen(self, treeView): + """Return a generator to step through expanded spots in this branch. + + Does not include root spot. + Arguments: + treeView -- a ref to the treeview + """ + for childSpot in self.childSpots(): + if treeView.isSpotExpanded(childSpot): + yield childSpot + for spot in childSpot.expandedSpotDescendantGen(treeView): + yield spot + + def levelSpotDescendantGen(self, treeView, includeRoot=True, maxLevel=None, + openOnly=False, initLevel=0): + """Return generator with (spot, level) tuples for this branch. + + Arguments: + treeView -- a ref to the treeview, requiired to check if open + includeRoot -- if True, the root spot is included + maxLevel -- the max number of levels to return (no limit if none) + openOnly -- if True, only include children open in the given view + initLevel -- the level number to start with + """ + if maxLevel == None: + maxLevel = sys.maxsize + if includeRoot: + yield (self, initLevel) + initLevel += 1 + if initLevel < maxLevel and (not openOnly or + treeView.isSpotExpanded(self)): + for childSpot in self.childSpots(): + for spot, level in childSpot.levelSpotDescendantGen(treeView, + True, + maxLevel, + openOnly, + initLevel): + yield (spot, level) + + def childSpots(self): + """Return a list of immediate child spots. + """ + return [childNode.matchedSpot(self) for childNode in + self.nodeRef.childList] + + def prevSiblingSpot(self): + """Return the nearest previous sibling spot or None. + """ + if self.parentSpot: + pos = self.row() + if pos > 0: + node = self.parentSpot.nodeRef.childList[pos - 1] + return node.matchedSpot(self.parentSpot) + return None + + def nextSiblingSpot(self): + """Return the nearest next sibling spot or None. + """ + if self.parentSpot: + childList = self.parentSpot.nodeRef.childList + pos = self.row() + 1 + if pos < len(childList): + return childList[pos].matchedSpot(self.parentSpot) + return None + + def prevTreeSpot(self, loop=False): + """Return the previous node in the tree order. + + Return None at the start of the tree unless loop is true. + Arguments: + loop -- return the last node of the tree after the first if true + """ + sibling = self.prevSiblingSpot() + if sibling: + return sibling.lastDescendantSpot() + if self.parentSpot.parentSpot: + return self.parentSpot + elif loop: + return self.rootSpot().lastDescendantSpot() + return None + + def nextTreeSpot(self, loop=False): + """Return the next node in the tree order. + + Return None at the end of the tree unless loop is true. + Arguments: + loop -- return the root node at the end of the tree if true + """ + if self.nodeRef.childList: + return self.nodeRef.childList[0].matchedSpot(self) + ancestor = self + while ancestor.parentSpot: + sibling = ancestor.nextSiblingSpot() + if sibling: + return sibling + ancestor = ancestor.parentSpot + if loop: + return ancestor.nodeRef.childList[0].matchedSpot(ancestor) + return None + + def lastDescendantSpot(self): + """Return the last spot of this spots's branch (last in tree order). + """ + spot = self + while spot.nodeRef.childList: + spot = spot.nodeRef.childList[-1].matchedSpot(spot) + return spot + + def spotChain(self): + """Return a list of parent spots, including self. + """ + chain = [] + spot = self + while spot.parentSpot: + chain.insert(0, spot) + spot = spot.parentSpot + return chain + + def parentSpotSet(self): + """Return a set of ancestor spots, not including self. + """ + result = set() + spot = self.parentSpot + while spot.parentSpot: + result.add(spot) + spot = spot.parentSpot + return result + + def rootSpot(self): + """Return the root spot that references the tree structure. + """ + spot = self + while spot.parentSpot: + spot = spot.parentSpot + return spot + + def sortKey(self): + """Return a tuple of parent row positions for sorting in tree order. + """ + positions = [] + spot = self + while spot.parentSpot: + positions.insert(0, spot.row()) + spot = spot.parentSpot + return tuple(positions) diff -Nru treeline-2.0.2/source/treestructure.py treeline-3.0.1/source/treestructure.py --- treeline-2.0.2/source/treestructure.py 1970-01-01 00:00:00.000000000 +0000 +++ treeline-3.0.1/source/treestructure.py 2018-09-27 00:53:52.000000000 +0000 @@ -0,0 +1,338 @@ +#!/usr/bin/env python3 + +#****************************************************************************** +# treestructure.py, provides a class to store the tree's data +# +# TreeLine, an information storage program +# Copyright (C) 2018, 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 +# version. This program is distributed in the hope that it will be useful, +# but WITTHOUT ANY WARRANTY. See the included LICENSE file for details. +#****************************************************************************** + +import operator +import copy +import json +import uuid +import treenode +import treeformats +import undo +try: + from __main__ import __version__ +except ImportError: + __version__ = '' + +defaultRootTitle = _('Main') + + +class TreeStructure(treenode.TreeNode): + """Class to store all tree data. + + Inherits TreeNode to get childList (holds top nodes) and other methods. + """ + def __init__(self, fileData=None, topNodes=None, addDefaults=False, + addSpots=True): + """Create and store a tree structure from file data. + + If no file data is given, create an empty or a default new structure. + Arguments: + fileData -- a dict in JSON file format of a structure + topNodes -- existing top-level nodes to add to a structure + addDefaults -- if True, adds default new structure + addSpots -- if True, adds parent spot references + """ + super().__init__(None) # init TreeNode, with no formatRef + self.nodeDict = {} + self.undoList = None + self.redoList = None + self.configDialogFormats = None + self.mathZeroBlanks = True + self.childRefErrorNodes = [] + if fileData: + self.treeFormats = treeformats.TreeFormats(fileData['formats']) + self.treeFormats.loadGlobalSavedConditions(fileData['properties']) + for nodeInfo in fileData['nodes']: + formatRef = self.treeFormats[nodeInfo['format']] + node = treenode.TreeNode(formatRef, nodeInfo) + self.nodeDict[node.uId] = node + for node in self.nodeDict.values(): + node.assignRefs(self.nodeDict) + if node.tmpChildRefs: + self.childRefErrorNodes.append(node) + node.tmpChildRefs = [] + for uId in fileData['properties']['topnodes']: + node = self.nodeDict[uId] + self.childList.append(node) + if 'zeroblanks' in fileData['properties']: + self.mathZeroBlanks = fileData['properties']['zeroblanks'] + if addSpots: + self.generateSpots(None) + elif topNodes: + self.childList = topNodes + self.treeFormats = treeformats.TreeFormats() + for topNode in topNodes: + for node in topNode.descendantGen(): + self.nodeDict[node.uId] = node + self.treeFormats.addTypeIfMissing(node.formatRef) + if addSpots: + self.generateSpots(None) + elif addDefaults: + self.treeFormats = treeformats.TreeFormats(setDefault=True) + node = treenode.TreeNode(self.treeFormats[treeformats. + defaultTypeName]) + node.setTitle(defaultRootTitle) + self.nodeDict[node.uId] = node + self.childList.append(node) + if addSpots: + self.generateSpots(None) + else: + self.treeFormats = treeformats.TreeFormats() + self.fileInfoNode = treenode.TreeNode(self.treeFormats.fileInfoFormat) + + def fileData(self): + """Return a fileData dict in JSON file format. + """ + formats = self.treeFormats.storeFormats() + nodeList = sorted([node.fileData() for node in self.nodeDict.values()], + key=operator.itemgetter('uid')) + topNodeIds = [node.uId for node in self.childList] + properties = {'tlversion': __version__, 'topnodes': topNodeIds} + self.treeFormats.storeGlobalSavedConditions(properties) + if not self.mathZeroBlanks: + fileData['properties']['zeroblanks'] = False + fileData = {'formats': formats, 'nodes': nodeList, + 'properties': properties} + return fileData + + def purgeOldFieldData(self): + """Remove data from obsolete fields from all nodes. + """ + fieldSets = self.treeFormats.fieldNameDict() + for node in self.nodeDict.values(): + oldKeys = set(node.data.keys()) - fieldSets[node.formatRef.name] + for key in oldKeys: + del node.data[key] + + def addNodeDictRef(self, node): + """Add the given node to the node dictionary. + + Arguments: + node -- the node to add + """ + self.nodeDict[node.uId] = node + + def removeNodeDictRef(self, node): + """Remove the given node from the node dictionary. + + Arguments: + node -- the node to remove + """ + try: + del self.nodeDict[node.uId] + except KeyError: + pass + + def rebuildNodeDict(self): + """Remove and re-create the entire node dictionary. + """ + self.nodeDict = {} + for node in self.descendantGen(): + self.nodeDict[node.uId] = node + + def replaceAllSpots(self, removeUnusedNodes=True): + """Remove and regenerate all spot refs for the tree. + + Arguments: + removeUnusedNodes -- if True, delete refs to nodes without spots + """ + self.spotRefs = set() + for node in self.nodeDict.values(): + node.spotRefs = set() + self.generateSpots(None) + if removeUnusedNodes: + self.nodeDict = {uId:node for (uId, node) in self.nodeDict.items() + if node.spotRefs} + + def deleteNodeSpot(self, spot): + """Remove the given spot, removing the entire node if no spots remain. + + Arguments: + spot -- the spot to remove + """ + spot.parentSpot.nodeRef.childList.remove(spot.nodeRef) + for node in spot.nodeRef.descendantGen(): + if len(node.spotRefs) <= 1: + self.removeNodeDictRef(node) + node.spotRefs = set() + else: + node.removeInvalidSpotRefs(False) + + def structSpot(self): + """Return the top spot (not tied to a node). + """ + (topSpot, ) = self.spotRefs + return topSpot + + def rootSpots(self): + """Return a list of spots from root nodes. + """ + (topSpot, ) = self.spotRefs + return topSpot.childSpots() + + def spotById(self, spotId): + """Return a spot based on a spot ID string. + + Raises KeyError on invalid node ID, an IndexError on invalid spot num. + Arguments: + spotId -- a spot ID string, in the form "nodeID:spotInstance" + """ + nodeId, spotNum = spotId.split(':', 1) + return self.nodeDict[nodeId].spotByNumber(int(spotNum)) + + def descendantGen(self): + """Return a generator to step through all nodes in tree order. + + Override from TreeNode to exclude self. + """ + for child in self.childList: + for node in child.descendantGen(): + yield node + + def getConfigDialogFormats(self, forceReset=False): + """Return duplicate formats for use in the config dialog. + + Arguments: + forceReset -- if True, sets duplicate formats back to original + """ + if not self.configDialogFormats or forceReset: + self.configDialogFormats = copy.deepcopy(self.treeFormats) + return self.configDialogFormats + + def applyConfigDialogFormats(self, addUndo=True): + """Replace the formats with the duplicates and signal for view update. + + Also updates all nodes for changed type and field names. + """ + if addUndo: + undo.FormatUndo(self.undoList, self.treeFormats, + self.configDialogFormats) + self.treeFormats.copySettings(self.configDialogFormats) + self.treeFormats.updateDerivedRefs() + self.treeFormats.updateMathFieldRefs() + if self.configDialogFormats.fieldRenameDict: + for node in self.nodeDict.values(): + fieldRenameDict = (self.configDialogFormats.fieldRenameDict. + get(node.formatRef.name, {})) + tmpDataDict = {} + for oldName, newName in fieldRenameDict.items(): + if oldName in node.data: + tmpDataDict[newName] = node.data[oldName] + del node.data[oldName] + node.data.update(tmpDataDict) + self.configDialogFormats.fieldRenameDict = {} + if self.treeFormats.emptiedMathDict: + for node in self.nodeDict.values(): + for fieldName in self.treeFormats.emptiedMathDict.get(node. + formatRef. + name, + set()): + node.data.pop(fieldName, None) + self.formats.emptiedMathDict = {} + + def usesType(self, typeName): + """Return true if any nodes use the give node format type. + + Arguments: + typeName -- the format name to search for + """ + for node in self.nodeDict.values(): + if node.formatRef.name == typeName: + return True + return False + + def replaceDuplicateIds(self, duplicateDict): + """Generate new unique IDs for any nodes found in newNodeDict. + + Arguments: + newNodeDict -- a dict to search for duplicates + """ + for node in list(self.nodeDict.values()): + if node.uId in duplicateDict: + del self.nodeDict[node.uId] + node.uId = uuid.uuid1().hex + self.nodeDict[node.uId] = node + + def addNodesFromStruct(self, treeStruct, parent, position=-1): + """Add nodes from the given structure under the given parent. + + Arguments: + treeStruct -- the structure to insert + parent -- the parent of the new nodes + position -- the location to insert (-1 is appended) + """ + for nodeFormat in treeStruct.treeFormats.values(): + self.treeFormats.addTypeIfMissing(nodeFormat) + for node in treeStruct.nodeDict.values(): + self.nodeDict[node.uId] = node + node.formatRef = self.treeFormats[node.formatRef.name] + for node in treeStruct.childList: + if position >= 0: + parent.childList.insert(position, node) + position += 1 + else: + parent.childList.append(node) + node.addSpotRef(parent) + + def debugCheck(self): + """Run debugging checks on structure nodeDict, nodes and spots. + + Reports results to std output. Not to be run in production releases. + """ + print('\nChecking nodes in nodeDict:') + nodeIds = set() + errorCount = 0 + for node in self.descendantGen(): + nodeIds.add(node.uId) + if node.uId not in self.nodeDict: + print(' Node not in nodeDict, ID: {}, Title: {}'. + format(node.uId, node.title())) + errorCount += 1 + for uId in set(self.nodeDict.keys()) - nodeIds: + node = self.nodeDict[uId] + print(' Node not in structure, ID: {}, Title: {}'. + format(node.uId, node.title())) + errorCount += 1 + print(' {} errors found in nodeDict'.format(errorCount)) + + print('\nChecking spots:') + errorCount = 0 + for topNode in self.childList: + for node in topNode.descendantGen(): + for spot in node.spotRefs: + if node not in spot.parentSpot.nodeRef.childList: + print(' Invalid spot in node, ID: {}, Title: {}'. + format(node.uId, node.title())) + errorCount += 1 + for child in node.childList: + if len(child.spotRefs) < len(node.spotRefs): + print(' Missing spot in node, ID: {}, Title: {}'. + format(child.uId, child.title())) + errorCount += 1 + print(' {} errors found in spots'.format(errorCount)) + + +#### Utility Functions #### + +def structFromMimeData(mimeData): + """Return a tree structure based on mime data. + + Arguments: + mimeData -- data to be used + """ + try: + data = json.loads(str(mimeData.data('application/json'), 'utf-8')) + return TreeStructure(data, addSpots=False) + except (ValueError, KeyError, TypeError): + return None diff -Nru treeline-2.0.2/source/treeview.py treeline-3.0.1/source/treeview.py --- treeline-2.0.2/source/treeview.py 2015-01-31 19:16:31.000000000 +0000 +++ treeline-3.0.1/source/treeview.py 2018-06-27 02:05:56.000000000 +0000 @@ -4,7 +4,7 @@ # treeview.py, provides a class for the indented tree view # # TreeLine, an information storage program -# Copyright (C) 2015, Douglas W. Bell +# Copyright (C) 2018, 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 @@ -12,20 +12,27 @@ # but WITTHOUT ANY WARRANTY. See the included LICENSE file for details. #****************************************************************************** -from PyQt4 import QtCore, QtGui -import treenode +import re +import unicodedata +from PyQt5.QtCore import QEvent, QPoint, QPointF, Qt, pyqtSignal +from PyQt5.QtGui import (QContextMenuEvent, QKeySequence, QMouseEvent, + QTextDocument) +from PyQt5.QtWidgets import (QAbstractItemView, QApplication, QHeaderView, + QLabel, QListWidget, QListWidgetItem, QMenu, + QStyledItemDelegate, QTreeView) import treeselection +import treenode import miscdialogs import globalref -class TreeView(QtGui.QTreeView): +class TreeView(QTreeView): """Class override for the indented tree view. - + Sets view defaults and links with document for content. """ - skippedMouseSelect = QtCore.pyqtSignal(treenode.TreeNode) - shortcutEntered = QtCore.pyqtSignal(QtGui.QKeySequence) + skippedMouseSelect = pyqtSignal(treenode.TreeNode) + shortcutEntered = pyqtSignal(QKeySequence) def __init__(self, model, allActions, parent=None): """Initialize the tree view. @@ -35,114 +42,237 @@ parent -- the parent main window """ super().__init__(parent) - self.setModel(model) + self.resetModel(model) self.allActions = allActions - self.menu = None + self.incremSearchMode = False + self.incremSearchString = '' self.noMouseSelectMode = False - self.setSelectionModel(treeselection.TreeSelection(model, self)) - self.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) - self.header().setResizeMode(0, QtGui.QHeaderView.ResizeToContents) + self.mouseFocusNoEditMode = False + self.prevSelSpot = None # temp, to check for edit at mouse release + self.setSelectionMode(QAbstractItemView.ExtendedSelection) + self.header().setSectionResizeMode(0, QHeaderView.ResizeToContents) self.header().setStretchLastSection(False) self.setHeaderHidden(True) self.setItemDelegate(TreeEditDelegate(self)) + # use mouse event for editing to avoid with multiple select + self.setEditTriggers(QAbstractItemView.NoEditTriggers) self.updateTreeGenOptions() - self.setDragDropMode(QtGui.QAbstractItemView.DragDrop) - self.setDefaultDropAction(QtCore.Qt.MoveAction) + self.setDragDropMode(QAbstractItemView.DragDrop) + self.setDefaultDropAction(Qt.MoveAction) self.setDropIndicatorShown(True) self.setUniformRowHeights(True) - self.selectionModel().selectNode(model.root) - self.expand(model.root.index()) + + def resetModel(self, model): + """Change the model assigned to this view. + + Also assigns a new selection model. + Arguments: + model -- the new model to assign + """ + self.setModel(model) + self.setSelectionModel(treeselection.TreeSelection(model, self)) def updateTreeGenOptions(self): """Set the tree to match the current general options. """ - if globalref.genOptions.getValue('ClickRename'): - self.setEditTriggers(QtGui.QAbstractItemView.SelectedClicked) - else: - self.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers) - dragAvail = globalref.genOptions.getValue('DragTree') + dragAvail = globalref.genOptions['DragTree'] self.setDragEnabled(dragAvail) self.setAcceptDrops(dragAvail) - self.setIndentation(globalref.genOptions.getValue('IndentOffset') * + self.setIndentation(globalref.genOptions['IndentOffset'] * self.fontInfo().pixelSize()) - def showTypeMenu(self, menu): - """Show a popup menu for setting the item type. + def isSpotExpanded(self, spot): + """Return True if the given spot is expanded (showing children). + + Arguments: + spot -- the spot to check """ - index = self.selectionModel().currentIndex() - self.scrollTo(index) - rect = self.visualRect(index) - pt = self.mapToGlobal(QtCore.QPoint(rect.center().x(), rect.bottom())) - menu.popup(pt) + return self.isExpanded(spot.index(self.model())) - def expandSelectedBranches(self): - """Set all nodes in the selected branches to expanded. + def expandSpot(self, spot): + """Expand a spot in this view. - Uses expandAll() if root is selected. - Otherwise it collapses root temporarily to avoid extreme slowness. + Arguments: + spot -- the spot to expand """ - selectNodes = self.selectionModel().selectedNodes() - rootNode = self.model().root - if rootNode in selectNodes: - self.expandAll() - else: - self.collapse(rootNode.index()) - for node in selectNodes: - self.expandBranch(node) - self.expand(rootNode.index()) + self.expand(spot.index(self.model())) + + def collapseSpot(self, spot): + """Collapse a spot in this view. + + Arguments: + spot -- the spot to collapse + """ + self.collapse(spot.index(self.model())) + + def expandBranch(self, parentSpot): + """Expand all spots in the given branch. + + Collapses parentSpot first to avoid extreme slowness. + Arguments: + parentSpot -- the top spot in the branch + """ + self.collapse(parentSpot.index(self.model())) + for spot in parentSpot.spotDescendantOnlyGen(): + if spot.nodeRef.childList: + self.expand(spot.index(self.model())) + self.expand(parentSpot.index(self.model())) + + def collapseBranch(self, parentSpot): + """Collapse all spots in the given branch. + + Arguments: + parentSpot -- the top spot in the branch + """ + for spot in parentSpot.spotDescendantGen(): + if spot.nodeRef.childList: + self.collapse(spot.index(self.model())) + + def savedExpandState(self, spots): + """Return a list of tuples of spots and expanded state (True/False). - def expandBranch(self, parentNode): - """Recursively expand all nodes in the branch. + Arguments: + spots -- an iterable of spots to save + """ + return [(spot, self.isSpotExpanded(spot)) for spot in spots] + + def restoreExpandState(self, expandState): + """Expand or collapse based on saved tuples. + + Arguments: + expandState -- a list of tuples of spots and expanded state + """ + for spot, expanded in expandState: + try: + if expanded: + self.expandSpot(spot) + else: + self.collapseSpot(spot) + except ValueError: + pass + + def spotAtTop(self): + """If view is scrolled, return the spot at the top of the view. + + If not scrolled, return None. + """ + if self.verticalScrollBar().value() > 0: + return self.indexAt(QPoint(0, 0)).internalPointer() + return None + + def scrollToSpot(self, spot): + """Scroll the view to move the spot to the top position. - This goes very slowly if root is not temporarily collapsed first in - expandSelectedBranches(). Arguments: - parentNode -- the top of the branch to expand + spot -- the spot to move to the top """ - self.expand(parentNode.index()) - for node in parentNode.childList: - if node.childList: - self.expandBranch(node) + self.scrollTo(spot.index(self.model()), + QAbstractItemView.PositionAtTop) - def collapseBranch(self, parentNode): - """Recursively collapse all nodes in the branch. + def scrollTo(self, index, hint=QAbstractItemView.EnsureVisible): + """Scroll the view to make node at index visible. + Overriden to stop autoScroll from horizontally jumping when selecting + nodes. Arguments: - parentNode -- the top of the branch to collapse + index -- the node to be made visible + hint -- where the visible item should be """ - self.collapse(parentNode.index()) - for node in parentNode.childList: - if node.childList: - self.collapseBranch(node) + horizPos = self.horizontalScrollBar().value() + super().scrollTo(index, hint) + self.horizontalScrollBar().setValue(horizPos) def endEditing(self): """Stop the editing of any item being renamed. """ + delegate = self.itemDelegate() + if delegate.editor: + delegate.commitData.emit(delegate.editor) self.closePersistentEditor(self.selectionModel().currentIndex()) + def incremSearchStart(self): + """Start an incremental title search. + """ + self.incremSearchMode = True + self.incremSearchString = '' + globalref.mainControl.currentStatusBar().showMessage(_('Search for:')) + + def incremSearchRun(self): + """Perform an incremental title search. + """ + msg = _('Search for: {0}').format(self.incremSearchString) + globalref.mainControl.currentStatusBar().showMessage(msg) + if (self.incremSearchString and not + self.selectionModel().selectTitleMatch(self.incremSearchString, + True, True)): + msg = _('Search for: {0} (not found)').format(self. + incremSearchString) + globalref.mainControl.currentStatusBar().showMessage(msg) + + def incremSearchNext(self): + """Go to the next match in an incremental title search. + """ + if self.incremSearchString: + if self.selectionModel().selectTitleMatch(self.incremSearchString): + msg = _('Next: {0}').format(self.incremSearchString) + else: + msg = _('Next: {0} (not found)').format(self. + incremSearchString) + globalref.mainControl.currentStatusBar().showMessage(msg) + + def incremSearchPrev(self): + """Go to the previous match in an incremental title search. + """ + if self.incremSearchString: + if self.selectionModel().selectTitleMatch(self.incremSearchString, + False): + msg = _('Next: {0}').format(self.incremSearchString) + else: + msg = _('Next: {0} (not found)').format(self. + incremSearchString) + globalref.mainControl.currentStatusBar().showMessage(msg) + + def incremSearchStop(self): + """End an incremental title search. + """ + self.incremSearchMode = False + self.incremSearchString = '' + globalref.mainControl.currentStatusBar().clearMessage() + + def showTypeMenu(self, menu): + """Show a popup menu for setting the item type. + """ + index = self.selectionModel().currentIndex() + self.scrollTo(index) + rect = self.visualRect(index) + pt = self.mapToGlobal(QPoint(rect.center().x(), rect.bottom())) + menu.popup(pt) + def contextMenu(self): """Return the context menu, creating it if necessary. """ - if not self.menu: - self.menu = QtGui.QMenu(self) - self.menu.addAction(self.allActions['EditCut']) - self.menu.addAction(self.allActions['EditCopy']) - self.menu.addAction(self.allActions['EditPaste']) - self.menu.addAction(self.allActions['NodeRename']) - self.menu.addSeparator() - self.menu.addAction(self.allActions['NodeInsertBefore']) - self.menu.addAction(self.allActions['NodeInsertAfter']) - self.menu.addAction(self.allActions['NodeAddChild']) - self.menu.addSeparator() - self.menu.addAction(self.allActions['NodeDelete']) - self.menu.addAction(self.allActions['NodeIndent']) - self.menu.addAction(self.allActions['NodeUnindent']) - self.menu.addSeparator() - self.menu.addAction(self.allActions['NodeMoveUp']) - self.menu.addAction(self.allActions['NodeMoveDown']) - self.menu.addSeparator() - self.menu.addMenu(self.allActions['DataNodeType'].parent()) - return self.menu + menu = QMenu(self) + menu.addAction(self.allActions['EditCut']) + menu.addAction(self.allActions['EditCopy']) + menu.addAction(self.allActions['EditPaste']) + menu.addAction(self.allActions['NodeRename']) + menu.addSeparator() + menu.addAction(self.allActions['NodeInsertBefore']) + menu.addAction(self.allActions['NodeInsertAfter']) + menu.addAction(self.allActions['NodeAddChild']) + menu.addSeparator() + menu.addAction(self.allActions['NodeDelete']) + menu.addAction(self.allActions['NodeIndent']) + menu.addAction(self.allActions['NodeUnindent']) + menu.addSeparator() + menu.addAction(self.allActions['NodeMoveUp']) + menu.addAction(self.allActions['NodeMoveDown']) + menu.addSeparator() + menu.addMenu(self.allActions['DataNodeType'].parent()) + menu.addSeparator() + menu.addAction(self.allActions['ViewExpandBranch']) + menu.addAction(self.allActions['ViewCollapseBranch']) + return menu def contextMenuEvent(self, event): """Show popup context menu on mouse click or menu key. @@ -150,71 +280,58 @@ Arguments: event -- the context menu event """ - if event.reason() == QtGui.QContextMenuEvent.Mouse: - clickedItem = self.indexAt(event.pos()).internalPointer() - if not clickedItem: + if event.reason() == QContextMenuEvent.Mouse: + clickedSpot = self.indexAt(event.pos()).internalPointer() + if not clickedSpot: event.ignore() return - if clickedItem not in self.selectionModel().selectedNodes(): - self.selectionModel().selectNode(clickedItem) + if clickedSpot not in self.selectionModel().selectedSpots(): + self.selectionModel().selectSpots([clickedSpot]) pos = event.globalPos() else: # shown for menu key or other reason - selectList = self.selectionModel().selectedNodes() + selectList = self.selectionModel().selectedSpots() if not selectList: event.ignore() return - currentNode = self.selectionModel().currentNode() - if currentNode in selectList: - selectList.insert(0, currentNode) - posList = [] - for node in selectList: - rect = self.visualRect(node.index()) - pt = QtCore.QPoint(rect.center().x(), rect.bottom()) + currentSpot = self.selectionModel().currentSpot() + if currentSpot in selectList: + selectList.insert(0, currentSpot) + position = None + for spot in selectList: + rect = self.visualRect(spot.index(self.model())) + pt = QPoint(rect.center().x(), rect.bottom()) if self.rect().contains(pt): - posList.append(pt) - if not posList: - self.scrollTo(selectList[0].index()) - rect = self.visualRect(selectList[0].index()) - posList = [QtCore.QPoint(rect.center().x(), rect.bottom())] - pos = self.mapToGlobal(posList[0]) + position = pt + break + if not position: + self.scrollTo(selectList[0].index(self.model())) + rect = self.visualRect(selectList[0].index(self.model())) + position = QPoint(rect.center().x(), rect.bottom()) + pos = self.mapToGlobal(position) self.contextMenu().popup(pos) event.accept() - def scrollTo(self, index, hint=QtGui.QAbstractItemView.EnsureVisible): - """Scroll the view to make node at index visible. - - Overriden to stop autoScroll from horizontally jumping when selecting - nodes. - Arguments: - index -- the node to be made visible - hint -- where the visible item should be - """ - horizPos = self.horizontalScrollBar().value() - super().scrollTo(index, hint) - self.horizontalScrollBar().setValue(horizPos) - - def nodeAtTop(self): - """Return the node at the top of the view as currently scrolled. - """ - return self.indexAt(QtCore.QPoint(0, 0)).internalPointer() - def dropEvent(self, event): """Event handler for view drop actions. - Selects parent node at destination and avoids node removal after - invalid moves. + Selects parent node at destination. Arguments: event -- the drop event """ - clickedNode = self.indexAt(event.pos()).internalPointer() - if clickedNode: + clickedSpot = self.indexAt(event.pos()).internalPointer() + # clear selection to avoid invalid multiple selection bug + self.selectionModel().selectSpots([], False) + if clickedSpot: super().dropEvent(event) - self.expand(clickedNode.index()) - self.selectionModel().selectNode(clickedNode) + self.selectionModel().selectSpots([clickedSpot], False) + self.scheduleDelayedItemsLayout() # reqd before expand + self.expandSpot(clickedSpot) else: - # avoid removal of "moved" items - event.setDropAction(QtCore.Qt.CopyAction) - event.ignore() + super().dropEvent(event) + self.selectionModel().selectSpots([]) + self.scheduleDelayedItemsLayout() + if event.isAccepted(): + self.model().treeModified.emit(True) def toggleNoMouseSelectMode(self, active=True): """Set noMouseSelectMode to active or inactive. @@ -226,25 +343,113 @@ """ self.noMouseSelectMode = active + def clearHover(self): + """Post a mouse move event to clear the mouse hover indication. + + Needed to avoid crash when deleting nodes with hovered child nodes. + """ + event = QMouseEvent(QEvent.MouseMove, + QPointF(0.0, self.viewport().width()), + Qt.NoButton, Qt.NoButton, Qt.NoModifier) + QApplication.postEvent(self.viewport(), event) + QApplication.processEvents() + def mousePressEvent(self, event): - """Skip unselecting click on blank spaces and if in noMouseSelectMode. + """Skip unselecting click if in noMouseSelectMode. If in noMouseSelectMode, signal which node is under the mouse. Arguments: event -- the mouse click event """ - clickedNode = self.indexAt(event.pos()).internalPointer() - if not clickedNode: - event.ignore() - return + if self.incremSearchMode: + self.incremSearchStop() + self.prevSelSpot = None + clickedIndex = self.indexAt(event.pos()) + clickedSpot = clickedIndex.internalPointer() + selectModel = self.selectionModel() if self.noMouseSelectMode: - self.skippedMouseSelect.emit(clickedNode) + if clickedSpot and event.button() == Qt.LeftButton: + self.skippedMouseSelect.emit(clickedSpot.nodeRef) event.ignore() return + if (event.button() == Qt.LeftButton and + not self.mouseFocusNoEditMode and + selectModel.selectedCount() == 1 and + selectModel.currentSpot() == selectModel.selectedSpots()[0] and + event.pos().x() > self.visualRect(clickedIndex).left() and + globalref.genOptions['ClickRename']): + # set for edit if single select and not an expand/collapse click + self.prevSelSpot = selectModel.selectedSpots()[0] + self.mouseFocusNoEditMode = False super().mousePressEvent(event) + def mouseReleaseEvent(self, event): + """Initiate editing if clicking on a single selected node. + + Arguments: + event -- the mouse click event + """ + clickedIndex = self.indexAt(event.pos()) + clickedSpot = clickedIndex.internalPointer() + if (event.button() == Qt.LeftButton and + self.prevSelSpot and clickedSpot == self.prevSelSpot): + self.edit(clickedIndex) + event.ignore() + return + self.prevSelSpot = None + super().mouseReleaseEvent(event) + + def keyPressEvent(self, event): + """Record characters if in incremental search mode. + + Arguments: + event -- the key event + """ + if self.incremSearchMode: + if event.key() in (Qt.Key_Return, Qt.Key_Enter, Qt.Key_Escape): + self.incremSearchStop() + elif event.key() == Qt.Key_Backspace and self.incremSearchString: + self.incremSearchString = self.incremSearchString[:-1] + self.incremSearchRun() + elif event.text() and unicodedata.category(event.text()) != 'Cc': + # unicode category excludes control characters + self.incremSearchString += event.text() + self.incremSearchRun() + event.accept() + elif (event.key() in (Qt.Key_Return, Qt.Key_Enter) and + not self.itemDelegate().editor): + # enter key selects current item if not selected + selectModel = self.selectionModel() + if selectModel.currentSpot() not in selectModel.selectedSpots(): + selectModel.selectSpots([selectModel.currentSpot()]) + event.accept() + else: + super().keyPressEvent(event) + else: + super().keyPressEvent(event) + + def focusInEvent(self, event): + """Avoid editing a tree item with a get-focus click. + + Arguments: + event -- the focus in event + """ + if event.reason() == Qt.MouseFocusReason: + self.mouseFocusNoEditMode = True + super().focusInEvent(event) + + def focusOutEvent(self, event): + """Stop incremental search on focus loss. + + Arguments: + event -- the focus out event + """ + if self.incremSearchMode: + self.incremSearchStop() + super().focusOutEvent(event) -class TreeEditDelegate(QtGui.QStyledItemDelegate): + +class TreeEditDelegate(QStyledItemDelegate): """Class override for editing tree items to capture shortcut keys. """ def __init__(self, parent=None): @@ -254,6 +459,7 @@ parent -- the parent view """ super().__init__(parent) + self.editor = None def createEditor(self, parent, styleOption, modelIndex): """Return a new text editor for an item. @@ -263,8 +469,18 @@ styleOption -- the data for styles and geometry modelIndex -- the index of the item to be edited """ - editor = super().createEditor(parent, styleOption, modelIndex) - return editor + self.editor = super().createEditor(parent, styleOption, modelIndex) + return self.editor + + def destroyEditor(self, editor, index): + """Reset editor storage after editing ends. + + Arguments: + editor -- the editor that is ending + index -- the index of the edited item + """ + self.editor = None + super().destroyEditor(editor, index) def eventFilter(self, editor, event): """Override to handle shortcut control keys. @@ -273,73 +489,80 @@ editor -- the editor that Qt installed a filter on event -- the key press event """ - if (event.type() == QtCore.QEvent.KeyPress and - event.modifiers() == QtCore.Qt.ControlModifier and - QtCore.Qt.Key_A <= event.key() <= QtCore.Qt.Key_Z): - key = QtGui.QKeySequence(event.modifiers() | event.key()) + if (event.type() == QEvent.KeyPress and + event.modifiers() == Qt.ControlModifier and + Qt.Key_A <= event.key() <= Qt.Key_Z): + key = QKeySequence(event.modifiers() | event.key()) self.parent().shortcutEntered.emit(key) return True return super().eventFilter(editor, event) -class TreeFilterViewItem(QtGui.QListWidgetItem): +class TreeFilterViewItem(QListWidgetItem): """Item container for the flat list of filtered nodes. """ - def __init__(self, node, viewParent=None): + def __init__(self, spot, viewParent=None): """Initialize the list view item. Arguments: - node -- the node item to reference for content + spot -- the spot to reference for content viewParent -- the parent list view """ super().__init__(viewParent) - self.node = node - self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEditable | - QtCore.Qt.ItemIsEnabled) + self.spot = spot + self.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEditable | + Qt.ItemIsEnabled) self.update() def update(self): """Update title and icon from the stored node. """ - self.setText(self.node.title()) - if globalref.genOptions.getValue('ShowTreeIcons'): - self.setIcon(globalref.treeIcons.getIcon(self.node.nodeFormat(). - iconName, True)) + node = self.spot.nodeRef + self.setText(node.title()) + if globalref.genOptions['ShowTreeIcons']: + self.setIcon(globalref.treeIcons.getIcon(node.formatRef.iconName, + True)) -class TreeFilterView(QtGui.QListWidget): +class TreeFilterView(QListWidget): """View to show flat list of filtered nodes. """ - skippedMouseSelect = QtCore.pyqtSignal(treenode.TreeNode) - shortcutEntered = QtCore.pyqtSignal(QtGui.QKeySequence) - def __init__(self, model, selectionModel, allActions, parent=None): + skippedMouseSelect = pyqtSignal(treenode.TreeNode) + shortcutEntered = pyqtSignal(QKeySequence) + def __init__(self, treeViewRef, allActions, parent=None): """Initialize the list view. Arguments: - model -- the initial model for view data - selectionModel -- the selection model for this view + treeViewRef -- a ref to the tree view for data allActions -- a dictionary of control actions for popup menus parent -- the parent main window """ super().__init__(parent) - self.model = model - self.selectionModel = selectionModel + self.structure = treeViewRef.model().treeStructure + self.selectionModel = treeViewRef.selectionModel() + self.treeModel = treeViewRef.model() self.allActions = allActions self.menu = None self.noMouseSelectMode = False + self.mouseFocusNoEditMode = False + self.prevSelSpot = None # temp, to check for edit at mouse release self.drivingSelectionChange = False self.conditionalFilter = None - self.filterWhat = miscdialogs.FindFilterDialog.fullData - self.filterHow = miscdialogs.FindFilterDialog.keyWords + self.messageLabel = None + self.filterWhat = miscdialogs.FindScope.fullData + self.filterHow = miscdialogs.FindType.keyWords self.filterStr = '' - self.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) + self.setSelectionMode(QAbstractItemView.ExtendedSelection) self.setItemDelegate(TreeEditDelegate(self)) - if globalref.genOptions.getValue('ClickRename'): - self.setEditTriggers(QtGui.QAbstractItemView.SelectedClicked) - else: - self.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers) + # use mouse event for editing to avoid with multiple select + self.setEditTriggers(QAbstractItemView.NoEditTriggers) self.itemSelectionChanged.connect(self.updateSelectionModel) self.itemChanged.connect(self.changeTitle) + treeFont = QTextDocument().defaultFont() + treeFontName = globalref.miscOptions['TreeFont'] + if treeFontName: + treeFont.fromString(treeFontName) + self.setFont(treeFont) def updateItem(self, node): """Update the item corresponding to the given node. @@ -348,91 +571,101 @@ node -- the node to be updated """ for row in range(self.count()): - if self.item(row).node == node: + if self.item(row).spot.nodeRef == node: self.blockSignals(True) self.item(row).update() self.blockSignals(False) return def updateContents(self): - """Update filtered contents from current model and filter criteria. + """Update filtered contents from current structure and filter criteria. """ if self.conditionalFilter: self.conditionalUpdate() return - QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) - if self.filterHow == miscdialogs.FindFilterDialog.regExp: + QApplication.setOverrideCursor(Qt.WaitCursor) + if self.filterHow == miscdialogs.FindType.regExp: criteria = [re.compile(self.filterStr)] useRegExpFilter = True - elif self.filterHow == miscdialogs.FindFilterDialog.fullWords: + elif self.filterHow == miscdialogs.FindType.fullWords: criteria = [] for word in self.filterStr.lower().split(): criteria.append(re.compile(r'(?i)\b{}\b'. format(re.escape(word)))) useRegExpFilter = True - elif self.filterHow == miscdialogs.FindFilterDialog.keyWords: + elif self.filterHow == miscdialogs.FindType.keyWords: criteria = self.filterStr.lower().split() useRegExpFilter = False else: # full phrase criteria = [self.filterStr.lower().strip()] useRegExpFilter = False - titlesOnly = self.filterWhat == miscdialogs.FindFilterDialog.titlesOnly + titlesOnly = self.filterWhat == miscdialogs.FindScope.titlesOnly self.blockSignals(True) self.clear() if useRegExpFilter: - for node in self.model.root.descendantGen(): - if node.regExpSearch(criteria, titlesOnly): - item = TreeFilterViewItem(node, self) + for rootSpot in self.structure.rootSpots(): + for spot in rootSpot.spotDescendantGen(): + if spot.nodeRef.regExpSearch(criteria, titlesOnly): + item = TreeFilterViewItem(spot, self) else: - for node in self.model.root.descendantGen(): - if node.wordSearch(criteria, titlesOnly): - item = TreeFilterViewItem(node, self) + for rootSpot in self.structure.rootSpots(): + for spot in rootSpot.spotDescendantGen(): + if spot.nodeRef.wordSearch(criteria, titlesOnly): + item = TreeFilterViewItem(spot, self) self.blockSignals(False) - self.selectItems(self.selectionModel.selectedNodes(), True) + self.selectItems(self.selectionModel.selectedSpots(), True) if self.count() and not self.selectedItems(): self.item(0).setSelected(True) + if not self.messageLabel: + self.messageLabel = QLabel() + globalref.mainControl.currentStatusBar().addWidget(self. + messageLabel) message = _('Filtering by "{0}", found {1} nodes').format(self. filterStr, self.count()) - globalref.mainControl.currentStatusBar().showMessage(message) - QtGui.QApplication.restoreOverrideCursor() + self.messageLabel.setText(message) + QApplication.restoreOverrideCursor() def conditionalUpdate(self): - """Update filtered contents from model and conditional criteria. + """Update filtered contents from structure and conditional criteria. """ - QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) + QApplication.setOverrideCursor(Qt.WaitCursor) self.blockSignals(True) self.clear() - for node in self.model.root.descendantGen(): - if self.conditionalFilter.evaluate(node): - item = TreeFilterViewItem(node, self) + for rootSpot in self.structure.rootSpots(): + for spot in rootSpot.spotDescendantGen(): + if self.conditionalFilter.evaluate(spot.nodeRef): + item = TreeFilterViewItem(spot, self) self.blockSignals(False) - self.selectItems(self.selectionModel.selectedNodes(), True) + self.selectItems(self.selectionModel.selectedSpots(), True) if self.count() and not self.selectedItems(): self.item(0).setSelected(True) + if not self.messageLabel: + self.messageLabel = QLabel() + globalref.mainControl.currentStatusBar().addWidget(self. + messageLabel) message = _('Conditional filtering, found {0} nodes').format(self. count()) - globalref.mainControl.currentStatusBar().showMessage(message) - QtGui.QApplication.restoreOverrideCursor() + self.messageLabel.setText(message) + QApplication.restoreOverrideCursor() - def selectItems(self, nodes, signalModel=False): + def selectItems(self, spots, signalModel=False): """Select items matching given nodes if in filtered view. Arguments: - nodes -- the node list to select + spots -- the spot list to select signalModel -- signal to update the tree selection model if True """ - selectNodes = set(nodes) + selectSpots = set(spots) if not signalModel: self.blockSignals(True) for item in self.selectedItems(): item.setSelected(False) for row in range(self.count()): - if self.item(row).node in selectNodes: + if self.item(row).spot in selectSpots: self.item(row).setSelected(True) self.setCurrentItem(self.item(row)) - if not signalModel: - self.blockSignals(False) + self.blockSignals(False) def updateFromSelectionModel(self): """Select items selected in the tree selection model. @@ -440,13 +673,13 @@ Called from a signal that the tree selection model is changing. """ if self.count() and not self.drivingSelectionChange: - self.selectItems(self.selectionModel.selectedNodes()) + self.selectItems(self.selectionModel.selectedSpots()) def updateSelectionModel(self): """Change the selection model based on a filter list selection signal. """ self.drivingSelectionChange = True - self.selectionModel.selectNodes([item.node for item in + self.selectionModel.selectSpots([item.spot for item in self.selectedItems()]) self.drivingSelectionChange = False @@ -457,21 +690,22 @@ Arguments: item -- the filter view item that changed """ - if not self.model.setData(item.node.index(), item.text()): + if not self.treeModel.setData(item.spot.index(self.treeModel), + item.text()): self.blockSignals(True) item.setText(item.node.title()) self.blockSignals(False) - def nextPrevNode(self, node, forward=True): - """Return the next or previous node in this filter list view. + def nextPrevSpot(self, spot, forward=True): + """Return the next or previous spot in this filter list view. - Wraps around ends. Return None if view doesn't have node. + Wraps around ends. Return None if view doesn't have spot. Arguments: - node -- the starting node + spot -- the starting spot forward -- next if True, previous if False """ for row in range(self.count()): - if self.item(row).node == node: + if self.item(row).spot == spot: if forward: row += 1 if row >= self.count(): @@ -480,14 +714,14 @@ row -= 1 if row < 0: row = self.count() - 1 - return self.item(row).node + return self.item(row).spot return None def contextMenu(self): """Return the context menu, creating it if necessary. """ if not self.menu: - self.menu = QtGui.QMenu(self) + self.menu = QMenu(self) self.menu.addAction(self.allActions['EditCut']) self.menu.addAction(self.allActions['EditCopy']) self.menu.addAction(self.allActions['NodeRename']) @@ -503,13 +737,13 @@ Arguments: event -- the context menu event """ - if event.reason() == QtGui.QContextMenuEvent.Mouse: + if event.reason() == QContextMenuEvent.Mouse: clickedItem = self.itemAt(event.pos()) if not clickedItem: event.ignore() return - if clickedItem.node not in self.selectionModel.selectedNodes(): - self.selectionModel().selectNode(clickedItem.node) + if clickedItem.spot not in self.selectionModel.selectedSpots(): + self.selectionModel.selectSpots([clickedItem.spot]) pos = event.globalPos() else: # shown for menu key or other reason selectList = self.selectedItems() @@ -522,13 +756,13 @@ posList = [] for item in selectList: rect = self.visualItemRect(item) - pt = QtCore.QPoint(rect.center().x(), rect.bottom()) + pt = QPoint(rect.center().x(), rect.bottom()) if self.rect().contains(pt): posList.append(pt) if not posList: self.scrollTo(self.indexFromItem(selectList[0])) rect = self.visualItemRect(selectList[0]) - posList = [QtCore.QPoint(rect.center().x(), rect.bottom())] + posList = [QPoint(rect.center().x(), rect.bottom())] pos = self.mapToGlobal(posList[0]) self.contextMenu().popup(pos) event.accept() @@ -549,12 +783,45 @@ Arguments: event -- the mouse click event """ + self.prevSelSpot = None clickedItem = self.itemAt(event.pos()) if not clickedItem: event.ignore() return if self.noMouseSelectMode: - self.skippedMouseSelect.emit(clickedItem.node) + if event.button() == Qt.LeftButton: + self.skippedMouseSelect.emit(clickedItem.spot.nodeRef) event.ignore() return + if (event.button() == Qt.LeftButton and + not self.mouseFocusNoEditMode and + self.selectionModel.selectedCount() == 1 and + globalref.genOptions['ClickRename']): + self.prevSelSpot = self.selectionModel.selectedSpots()[0] + self.mouseFocusNoEditMode = False super().mousePressEvent(event) + + def mouseReleaseEvent(self, event): + """Initiate editing if clicking on a single selected node. + + Arguments: + event -- the mouse click event + """ + clickedItem = self.itemAt(event.pos()) + if (event.button() == Qt.LeftButton and clickedItem and + self.prevSelSpot and clickedItem.spot == self.prevSelSpot): + self.editItem(clickedItem) + event.ignore() + return + self.prevSelSpot = None + super().mouseReleaseEvent(event) + + def focusInEvent(self, event): + """Avoid editing a tree item with a get-focus click. + + Arguments: + event -- the focus in event + """ + if event.reason() == Qt.MouseFocusReason: + self.mouseFocusNoEditMode = True + super().focusInEvent(event) diff -Nru treeline-2.0.2/source/treewindow.py treeline-3.0.1/source/treewindow.py --- treeline-2.0.2/source/treewindow.py 2015-02-21 22:50:06.000000000 +0000 +++ treeline-3.0.1/source/treewindow.py 2018-04-08 19:19:50.000000000 +0000 @@ -4,7 +4,7 @@ # treewindow.py, provides a class for the main window and controls # # TreeLine, an information storage program -# Copyright (C) 2015, Douglas W. Bell +# Copyright (C) 2018, 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 @@ -12,27 +12,31 @@ # but WITTHOUT ANY WARRANTY. See the included LICENSE file for details. #****************************************************************************** -import os.path +import pathlib import base64 -from PyQt4 import QtCore, QtGui +from PyQt5.QtCore import QEvent, QRect, QSize, Qt, pyqtSignal +from PyQt5.QtGui import QGuiApplication, QTextDocument +from PyQt5.QtWidgets import (QAction, QActionGroup, QApplication, QMainWindow, + QSplitter, QStackedWidget, QStatusBar, QTabWidget) import treeview +import breadcrumbview import outputview -import titlelistview import dataeditview +import titlelistview import treenode import globalref -class TreeWindow(QtGui.QMainWindow): +class TreeWindow(QMainWindow): """Class override for the main window. Contains main window views and controls. """ - selectChanged = QtCore.pyqtSignal() - nodeModified = QtCore.pyqtSignal(treenode.TreeNode) - treeModified = QtCore.pyqtSignal() - winActivated = QtCore.pyqtSignal(QtGui.QMainWindow) - winClosing = QtCore.pyqtSignal(QtGui.QMainWindow) + selectChanged = pyqtSignal() + nodeModified = pyqtSignal(treenode.TreeNode) + treeModified = pyqtSignal() + winActivated = pyqtSignal(QMainWindow) + winClosing = pyqtSignal(QMainWindow) def __init__(self, model, allActions, parent=None): """Initialize the main window. @@ -44,186 +48,265 @@ super().__init__(parent) self.allActions = allActions.copy() self.allowCloseFlag = True + self.winActions = {} self.toolbars = [] self.rightTabActList = [] - self.setAttribute(QtCore.Qt.WA_DeleteOnClose) + self.setAttribute(Qt.WA_DeleteOnClose) self.setAcceptDrops(True) - self.setStatusBar(QtGui.QStatusBar()) + self.setStatusBar(QStatusBar()) self.setCaption() self.setupActions() self.setupMenus() self.setupToolbars() self.restoreToolbarPosition() - self.mainSplitter = QtGui.QSplitter() - self.setCentralWidget(self.mainSplitter) - self.treeStack = QtGui.QStackedWidget() - self.mainSplitter.addWidget(self.treeStack) self.treeView = treeview.TreeView(model, self.allActions) - self.treeView.shortcutEntered.connect(self.execShortcut) + self.breadcrumbSplitter = QSplitter(Qt.Vertical) + self.setCentralWidget(self.breadcrumbSplitter) + self.breadcrumbView = breadcrumbview.BreadcrumbView(self.treeView) + self.breadcrumbSplitter.addWidget(self.breadcrumbView) + self.breadcrumbView.setVisible(globalref. + genOptions['InitShowBreadcrumb']) + + self.treeSplitter = QSplitter() + self.breadcrumbSplitter.addWidget(self.treeSplitter) + self.treeStack = QStackedWidget() + self.treeSplitter.addWidget(self.treeStack) self.treeStack.addWidget(self.treeView) - self.treeView.selectionModel().selectionChanged.connect(self. - selectChanged) + self.treeView.shortcutEntered.connect(self.execShortcut) self.treeView.selectionModel().selectionChanged.connect(self. updateRightViews) - self.treeFilterView = treeview.TreeFilterView(model, self.treeView. - selectionModel(), - self.allActions) - self.treeFilterView.shortcutEntered.connect(self.execShortcut) - self.treeStack.addWidget(self.treeFilterView) - self.treeView.selectionModel().selectionChanged.connect(self. - treeFilterView. - updateFromSelectionModel) - self.treeStack.setCurrentWidget(self.treeView) + self.treeFilterView = None - self.rightTabs = QtGui.QTabWidget() - self.mainSplitter.addWidget(self.rightTabs) - self.rightTabs.setTabPosition(QtGui.QTabWidget.South) - self.rightTabs.tabBar().setFocusPolicy(QtCore.Qt.NoFocus) + self.rightTabs = QTabWidget() + self.treeSplitter.addWidget(self.rightTabs) + self.rightTabs.setTabPosition(QTabWidget.South) + self.rightTabs.tabBar().setFocusPolicy(Qt.NoFocus) - self.outputSplitter = QtGui.QSplitter(QtCore.Qt.Vertical) + self.outputSplitter = QSplitter(Qt.Vertical) self.rightTabs.addTab(self.outputSplitter, _('Data Output')) - parentOutputView = outputview.OutputView(self.treeView. - selectionModel(), False) + parentOutputView = outputview.OutputView(self.treeView, False) parentOutputView.highlighted[str].connect(self.statusBar().showMessage) self.outputSplitter.addWidget(parentOutputView) - childOutputView = outputview.OutputView(self.treeView.selectionModel(), - True) + childOutputView = outputview.OutputView(self.treeView, True) childOutputView.highlighted[str].connect(self.statusBar().showMessage) self.outputSplitter.addWidget(childOutputView) - self.editorSplitter = QtGui.QSplitter(QtCore.Qt.Vertical) + self.editorSplitter = QSplitter(Qt.Vertical) self.rightTabs.addTab(self.editorSplitter, _('Data Edit')) - parentEditView = dataeditview.DataEditView(self.treeView. - selectionModel(), + parentEditView = dataeditview.DataEditView(self.treeView, self.allActions, False) - parentEditView.nodeModified.connect(self.nodeModified) + parentEditView.shortcutEntered.connect(self.execShortcut) + parentEditView.focusOtherView.connect(self.focusNextView) parentEditView.inLinkSelectMode.connect(self.treeView. toggleNoMouseSelectMode) - parentEditView.inLinkSelectMode.connect(self.treeFilterView. - toggleNoMouseSelectMode) - parentEditView.focusOtherView.connect(self.focusNextView) - parentEditView.shortcutEntered.connect(self.execShortcut) self.treeView.skippedMouseSelect.connect(parentEditView. internalLinkSelected) - self.treeFilterView.skippedMouseSelect.connect(parentEditView. - internalLinkSelected) self.editorSplitter.addWidget(parentEditView) - childEditView = dataeditview.DataEditView(self.treeView. - selectionModel(), + childEditView = dataeditview.DataEditView(self.treeView, self.allActions, True) - childEditView.nodeModified.connect(self.nodeModified) + childEditView.shortcutEntered.connect(self.execShortcut) + childEditView.focusOtherView.connect(self.focusNextView) childEditView.inLinkSelectMode.connect(self.treeView. toggleNoMouseSelectMode) - childEditView.inLinkSelectMode.connect(self.treeFilterView. - toggleNoMouseSelectMode) - childEditView.focusOtherView.connect(self.focusNextView) - childEditView.shortcutEntered.connect(self.execShortcut) self.treeView.skippedMouseSelect.connect(childEditView. internalLinkSelected) - self.treeFilterView.skippedMouseSelect.connect(childEditView. - internalLinkSelected) + parentEditView.hoverFocusActive.connect(childEditView.endEditor) + childEditView.hoverFocusActive.connect(parentEditView.endEditor) + parentEditView.inLinkSelectMode.connect(childEditView. + updateInLinkSelectMode) + childEditView.inLinkSelectMode.connect(parentEditView. + updateInLinkSelectMode) self.editorSplitter.addWidget(childEditView) - self.titleSplitter = QtGui.QSplitter(QtCore.Qt.Vertical) + self.titleSplitter = QSplitter(Qt.Vertical) self.rightTabs.addTab(self.titleSplitter, _('Title List')) - parentTitleView = titlelistview.TitleListView(self.treeView. - selectionModel(), False) - parentTitleView.nodeModified.connect(self.nodeModified) - parentTitleView.treeModified.connect(self.treeModified) + parentTitleView = titlelistview.TitleListView(self.treeView, False) parentTitleView.shortcutEntered.connect(self.execShortcut) self.titleSplitter.addWidget(parentTitleView) - childTitleView = titlelistview.TitleListView(self.treeView. - selectionModel(), True) - childTitleView.nodeModified.connect(self.nodeModified) - childTitleView.treeModified.connect(self.treeModified) + childTitleView = titlelistview.TitleListView(self.treeView, True) childTitleView.shortcutEntered.connect(self.execShortcut) self.titleSplitter.addWidget(childTitleView) self.rightTabs.currentChanged.connect(self.updateRightViews) - self.rightViewList = [parentOutputView, childOutputView, - parentEditView, childEditView, - parentTitleView, childTitleView] self.updateFonts() + def setExternalSignals(self): + """Connect widow object signals to signals in this object. + + In a separate method to refresh after local control change. + """ + self.treeView.selectionModel().selectionChanged.connect(self. + selectChanged) + for i in range(2): + self.editorSplitter.widget(i).nodeModified.connect(self. + nodeModified) + self.titleSplitter.widget(i).nodeModified.connect(self. + nodeModified) + self.titleSplitter.widget(i).treeModified.connect(self. + treeModified) + + def updateActions(self, allActions): + """Use new actions for menus, etc. when the local control changes. + + Arguments: + allActions -- a dict containing the upper level actions + """ + # remove submenu actions that are children of the window + self.removeAction(self.allActions['DataNodeType']) + self.removeAction(self.allActions['FormatFontSize']) + self.allActions = allActions.copy() + self.allActions.update(self.winActions) + self.menuBar().clear() + self.setupMenus() + self.addToolbarCommands() + self.treeView.allActions = self.allActions + for i in range(2): + self.editorSplitter.widget(i).allActions = self.allActions + def updateTreeNode(self, node): - """Update the given node in the active tree view. + """Update all spots for the given node in the tree view. Arguments: node -- the node to be updated """ - self.treeView.update(node.index()) + for spot in node.spotRefs: + self.treeView.update(spot.index(self.treeView.model())) self.treeView.resizeColumnToContents(0) + self.breadcrumbView.updateContents() def updateTree(self): - """Update the full tree in the active tree view. + """Update the full tree view. """ self.treeView.scheduleDelayedItemsLayout() + self.breadcrumbView.updateContents() def updateRightViews(self, *args, outputOnly=False): - """Update all right-hand views (or all right-hand output views). + """Update all right-hand views and breadcrumb view. - Called for initial setup, selection change or data chamge. - The views may decide not to update if hidden. Arguments: *args -- dummy arguments to collect args from signals outputOnly -- only update output views (not edit views) """ - self.rightTabActList[self.rightTabs.currentIndex()].setChecked(True) - for view in self.rightViewList: - if not outputOnly or isinstance(view, outputview.OutputView): - view.updateContents() + if globalref.mainControl.activeControl: + self.rightTabActList[self.rightTabs. + currentIndex()].setChecked(True) + self.breadcrumbView.updateContents() + splitter = self.rightTabs.currentWidget() + if not outputOnly or isinstance(splitter.widget(0), + outputview.OutputView): + for i in range(2): + splitter.widget(i).updateContents() def refreshDataEditViews(self): """Refresh the data in non-selected cells in curreent data edit views. """ - views = [self.rightParentView(), self.rightChildView()] - for view in views: - if view and isinstance(view, dataeditview.DataEditView): - view.updateUnselectedCells() + splitter = self.rightTabs.currentWidget() + if isinstance(splitter.widget(0), dataeditview.DataEditView): + for i in range(2): + splitter.widget(i).updateUnselectedCells() def updateCommandsAvail(self): """Set window commands available based on node selections. """ - selNodes = self.treeView.selectionModel().selectedNodes() - numChildren = sum([len(node.childList) for node in selNodes]) - self.allActions['ViewExpandBranch'].setEnabled(numChildren) - self.allActions['ViewCollapseBranch'].setEnabled(numChildren) self.allActions['ViewPrevSelect'].setEnabled(len(self.treeView. selectionModel(). - previousNodes) > 1) + prevSpots) > 1) self.allActions['ViewNextSelect'].setEnabled(len(self.treeView. selectionModel(). - nextNodes) > 0) + nextSpots) > 0) + + def updateWinGenOptions(self): + """Update tree and data edit windows based on general option changes. + """ + self.treeView.updateTreeGenOptions() + for i in range(2): + self.editorSplitter.widget(i).setMouseTracking(globalref. + genOptions['EditorOnHover']) def updateFonts(self): """Update custom fonts in views. """ - treeFont = QtGui.QTextDocument().defaultFont() - treeFontName = globalref.miscOptions.getValue('TreeFont') + treeFont = QTextDocument().defaultFont() + treeFontName = globalref.miscOptions['TreeFont'] if treeFontName: treeFont.fromString(treeFontName) self.treeView.setFont(treeFont) self.treeView.updateTreeGenOptions() - self.treeFilterView.setFont(treeFont) - ouputFont = QtGui.QTextDocument().defaultFont() - ouputFontName = globalref.miscOptions.getValue('OutputFont') + if self.treeFilterView: + self.treeFilterView.setFont(treeFont) + ouputFont = QTextDocument().defaultFont() + ouputFontName = globalref.miscOptions['OutputFont'] if ouputFontName: ouputFont.fromString(ouputFontName) - editorFont = QtGui.QTextDocument().defaultFont() - editorFontName = globalref.miscOptions.getValue('EditorFont') + editorFont = QTextDocument().defaultFont() + editorFontName = globalref.miscOptions['EditorFont'] if editorFontName: editorFont.fromString(editorFontName) - for view in self.rightViewList: - if isinstance(view, outputview.OutputView): - view.setFont(ouputFont) - else: - view.setFont(editorFont) + for i in range(2): + self.outputSplitter.widget(i).setFont(ouputFont) + self.editorSplitter.widget(i).setFont(editorFont) + self.titleSplitter.widget(i).setFont(editorFont) - def isFiltering(self): - """Return True if tree filter view is active. + def resetTreeModel(self, model): + """Change the model assigned to the tree view. + + Arguments: + model -- the new model to assign """ - return self.treeStack.currentWidget() == self.treeFilterView + self.treeView.resetModel(model) + self.treeView.selectionModel().selectionChanged.connect(self. + updateRightViews) + + def activateAndRaise(self): + """Activate this window and raise it to the front. + """ + self.activateWindow() + self.raise_() + + def setCaption(self, pathObj=None): + """Change the window caption title based on the file name and path. + + Arguments: + pathObj - a path object for the current file + """ + if pathObj: + caption = '{0} [{1}] - TreeLine'.format(str(pathObj.name), + str(pathObj.parent)) + else: + caption = '- TreeLine' + self.setWindowTitle(caption) + + def filterView(self): + """Create, show and return a filter view. + """ + self.removeFilterView() + self.treeFilterView = treeview.TreeFilterView(self.treeView, + self.allActions) + self.treeFilterView.shortcutEntered.connect(self.execShortcut) + self.treeView.selectionModel().selectionChanged.connect(self. + treeFilterView. + updateFromSelectionModel) + for i in range(2): + editView = self.editorSplitter.widget(i) + editView.inLinkSelectMode.connect(self.treeFilterView. + toggleNoMouseSelectMode) + self.treeFilterView.skippedMouseSelect.connect(editView. + internalLinkSelected) + self.treeStack.addWidget(self.treeFilterView) + self.treeStack.setCurrentWidget(self.treeFilterView) + return self.treeFilterView + + def removeFilterView(self): + """Hide and delete the current filter view. + """ + if self.treeFilterView != None: # check for None since False if empty + self.treeStack.removeWidget(self.treeFilterView) + globalref.mainControl.currentStatusBar().removeWidget(self. + treeFilterView. + messageLabel) + self.treeFilterView.messageLabel.deleteLater() + self.treeFilterView = None def rightParentView(self): """Return the current right-hand parent view if visible (or None). @@ -245,39 +328,22 @@ """Focus the next pane in the tab focus series. Called by a signal from the data edit views. - Tab sequences tended to skip views without this. + Tab sequences tend to skip views without this. Arguments: forward -- forward in tab series if True """ - reason = (QtCore.Qt.TabFocusReason if forward - else QtCore.Qt.BacktabFocusReason) + reason = (Qt.TabFocusReason if forward + else Qt.BacktabFocusReason) + rightParent = self.rightParentView() + rightChild = self.rightChildView() if (self.sender().isChildView == forward or - (forward and self.rightChildView() == None) or - (not forward and self.rightParentView() == None)): - self.treeStack.currentWidget().setFocus(reason) + (forward and rightChild == None) or + (not forward and rightParent == None)): + self.treeView.setFocus(reason) elif forward: - self.rightChildView().setFocus(reason) + rightChild.setFocus(reason) else: - self.rightParentView().setFocus(reason) - - def activateAndRaise(self): - """Activate this window and raise it to the front. - """ - self.activateWindow() - self.raise_() - - def setCaption(self, filePath=''): - """Change the window caption title based on the file name and path. - - Arguments: - filePath - the full path to the current file - """ - if filePath: - caption = '{0} [{1}] - TreeLine'.format(os.path.basename(filePath), - os.path.dirname(filePath)) - else: - caption = '- TreeLine' - self.setWindowTitle(caption) + rightParent.setFocus(reason) def execShortcut(self, key): """Execute an action based on a shortcut key signal from a view. @@ -300,75 +366,94 @@ These actions only affect an individual window, they're independent in multiple windows of the same file. """ - winActions = {} - - viewExpandBranchAct = QtGui.QAction(_('&Expand Full Branch'), self, + viewExpandBranchAct = QAction(_('&Expand Full Branch'), self, statusTip=_('Expand all children of the selected nodes')) viewExpandBranchAct.triggered.connect(self.viewExpandBranch) - winActions['ViewExpandBranch'] = viewExpandBranchAct + self.winActions['ViewExpandBranch'] = viewExpandBranchAct - viewCollapseBranchAct = QtGui.QAction(_('&Collapse Full Branch'), self, + viewCollapseBranchAct = QAction(_('&Collapse Full Branch'), self, statusTip=_('Collapse all children of the selected nodes')) viewCollapseBranchAct.triggered.connect(self.viewCollapseBranch) - winActions['ViewCollapseBranch'] = viewCollapseBranchAct + self.winActions['ViewCollapseBranch'] = viewCollapseBranchAct - viewPrevSelectAct = QtGui.QAction(_('&Previous Selection'), self, + viewPrevSelectAct = QAction(_('&Previous Selection'), self, statusTip=_('Return to the previous tree selection')) viewPrevSelectAct.triggered.connect(self.viewPrevSelect) - winActions['ViewPrevSelect'] = viewPrevSelectAct + self.winActions['ViewPrevSelect'] = viewPrevSelectAct - viewNextSelectAct = QtGui.QAction(_('&Next Selection'), self, + viewNextSelectAct = QAction(_('&Next Selection'), self, statusTip=_('Go to the next tree selection in history')) viewNextSelectAct.triggered.connect(self.viewNextSelect) - winActions['ViewNextSelect'] = viewNextSelectAct + self.winActions['ViewNextSelect'] = viewNextSelectAct - viewRightTabGrp = QtGui.QActionGroup(self) - viewOutputAct = QtGui.QAction(_('Show Data &Output'), viewRightTabGrp, + viewRightTabGrp = QActionGroup(self) + viewOutputAct = QAction(_('Show Data &Output'), viewRightTabGrp, statusTip=_('Show data output in right view'), checkable=True) - winActions['ViewDataOutput'] = viewOutputAct + self.winActions['ViewDataOutput'] = viewOutputAct - viewEditAct = QtGui.QAction(_('Show Data &Editor'), viewRightTabGrp, + viewEditAct = QAction(_('Show Data &Editor'), viewRightTabGrp, statusTip=_('Show data editor in right view'), checkable=True) - winActions['ViewDataEditor'] = viewEditAct + self.winActions['ViewDataEditor'] = viewEditAct - viewTitleAct = QtGui.QAction(_('Show &Title List'), viewRightTabGrp, + viewTitleAct = QAction(_('Show &Title List'), viewRightTabGrp, statusTip=_('Show title list in right view'), checkable=True) - winActions['ViewTitleList'] = viewTitleAct + self.winActions['ViewTitleList'] = viewTitleAct self.rightTabActList = [viewOutputAct, viewEditAct, viewTitleAct] viewRightTabGrp.triggered.connect(self.viewRightTab) - viewChildPaneAct = QtGui.QAction(_('&Show Child Pane'), self, + viewBreadcrumbAct = QAction(_('Show &Breadcrumb View'), self, + statusTip=_('Toggle showing breadcrumb ancestor view'), + checkable=True) + viewBreadcrumbAct.setChecked(globalref. + genOptions['InitShowBreadcrumb']) + viewBreadcrumbAct.triggered.connect(self.viewBreadcrumb) + self.winActions['ViewBreadcrumb'] = viewBreadcrumbAct + + viewChildPaneAct = QAction(_('&Show Child Pane'), self, statusTip=_('Toggle showing right-hand child views'), checkable=True) - viewChildPaneAct.setChecked(globalref.genOptions. - getValue('ShowChildPane')) + viewChildPaneAct.setChecked(globalref.genOptions['InitShowChildPane']) viewChildPaneAct.triggered.connect(self.viewShowChildPane) - winActions['ViewShowChildPane'] = viewChildPaneAct + self.winActions['ViewShowChildPane'] = viewChildPaneAct - viewDescendAct = QtGui.QAction(_('Show Output &Descedants'), self, + viewDescendAct = QAction(_('Show Output &Descendants'), self, statusTip=_('Toggle showing output view indented descendants'), checkable=True) - viewDescendAct.setChecked(globalref.genOptions. - getValue('ShowDescendants')) + viewDescendAct.setChecked(globalref.genOptions['InitShowDescendants']) viewDescendAct.triggered.connect(self.viewDescendants) - winActions['ViewShowDescend'] = viewDescendAct + self.winActions['ViewShowDescend'] = viewDescendAct - winCloseAct = QtGui.QAction(_('&Close Window'), self, + winCloseAct = QAction(_('&Close Window'), self, statusTip=_('Close this window')) winCloseAct.triggered.connect(self.close) - winActions['WinCloseWindow'] = winCloseAct + self.winActions['WinCloseWindow'] = winCloseAct - for name, action in winActions.items(): + incremSearchStartAct = QAction(_('Start Incremental Search'), self) + incremSearchStartAct.triggered.connect(self.incremSearchStart) + self.addAction(incremSearchStartAct) + self.winActions['IncremSearchStart'] = incremSearchStartAct + + incremSearchNextAct = QAction(_('Next Incremental Search'), self) + incremSearchNextAct.triggered.connect(self.incremSearchNext) + self.addAction(incremSearchNextAct) + self.winActions['IncremSearchNext'] = incremSearchNextAct + + incremSearchPrevAct = QAction(_('Previous Incremental Search'), self) + incremSearchPrevAct.triggered.connect(self.incremSearchPrev) + self.addAction(incremSearchPrevAct) + self.winActions['IncremSearchPrev'] = incremSearchPrevAct + + for name, action in self.winActions.items(): icon = globalref.toolIcons.getIcon(name.lower()) if icon: action.setIcon(icon) - key = globalref.keyboardOptions.getValue(name) + key = globalref.keyboardOptions[name] if not key.isEmpty(): action.setShortcut(key) - self.allActions.update(winActions) + self.allActions.update(self.winActions) def setupToolbars(self): """Add toolbars based on option settings. @@ -376,16 +461,24 @@ for toolbar in self.toolbars: self.removeToolBar(toolbar) self.toolbars = [] - numToolbars = globalref.toolbarOptions.getValue('ToolbarQuantity') - iconSize = globalref.toolbarOptions.getValue('ToolbarSize') + numToolbars = globalref.toolbarOptions['ToolbarQuantity'] + iconSize = globalref.toolbarOptions['ToolbarSize'] for num in range(numToolbars): name = 'Toolbar{:d}'.format(num) toolbar = self.addToolBar(name) toolbar.setObjectName(name) - toolbar.setIconSize(QtCore.QSize(iconSize, iconSize)) + toolbar.setIconSize(QSize(iconSize, iconSize)) self.toolbars.append(toolbar) - commandList = globalref.toolbarOptions.getValue(name).split(',') - for command in commandList: + self.addToolbarCommands() + + def addToolbarCommands(self): + """Add toolbar commands for current actions. + """ + for toolbar, commandList in zip(self.toolbars, + globalref. + toolbarOptions['ToolbarCommands']): + toolbar.clear() + for command in commandList.split(','): if command: try: toolbar.addAction(self.allActions[command]) @@ -394,6 +487,7 @@ else: toolbar.addSeparator() + def setupMenus(self): """Add menu items for actions. """ @@ -423,31 +517,24 @@ editMenu.addSeparator() editMenu.addAction(self.allActions['EditCut']) editMenu.addAction(self.allActions['EditCopy']) + editMenu.addSeparator() editMenu.addAction(self.allActions['EditPaste']) editMenu.addAction(self.allActions['EditPastePlain']) editMenu.addSeparator() - editMenu.addAction(self.allActions['EditBoldFont']) - editMenu.addAction(self.allActions['EditItalicFont']) - editMenu.addAction(self.allActions['EditUnderlineFont']) - editMenu.addSeparator() - # add action's parent to get the sub-menu - editMenu.addMenu(self.allActions['EditFontSize'].parent()) - # add the action to activate the shortcut key - self.addAction(self.allActions['EditFontSize']) - editMenu.addAction(self.allActions['EditFontColor']) - editMenu.addSeparator() - editMenu.addAction(self.allActions['EditExtLink']) - editMenu.addAction(self.allActions['EditIntLink']) + editMenu.addAction(self.allActions['EditPasteChild']) + editMenu.addAction(self.allActions['EditPasteBefore']) + editMenu.addAction(self.allActions['EditPasteAfter']) editMenu.addSeparator() - editMenu.addAction(self.allActions['EditSelectAll']) - editMenu.addAction(self.allActions['EditClearFormat']) + editMenu.addAction(self.allActions['EditPasteCloneChild']) + editMenu.addAction(self.allActions['EditPasteCloneBefore']) + editMenu.addAction(self.allActions['EditPasteCloneAfter']) nodeMenu = self.menuBar().addMenu(_('&Node')) nodeMenu.addAction(self.allActions['NodeRename']) nodeMenu.addSeparator() + nodeMenu.addAction(self.allActions['NodeAddChild']) nodeMenu.addAction(self.allActions['NodeInsertBefore']) nodeMenu.addAction(self.allActions['NodeInsertAfter']) - nodeMenu.addAction(self.allActions['NodeAddChild']) nodeMenu.addSeparator() nodeMenu.addAction(self.allActions['NodeDelete']) nodeMenu.addAction(self.allActions['NodeIndent']) @@ -469,11 +556,12 @@ dataMenu.addAction(self.allActions['DataSortNodes']) dataMenu.addAction(self.allActions['DataNumbering']) dataMenu.addSeparator() + dataMenu.addAction(self.allActions['DataCloneMatches']) + dataMenu.addAction(self.allActions['DataDetachClones']) + dataMenu.addSeparator() dataMenu.addAction(self.allActions['DataFlatCategory']) dataMenu.addAction(self.allActions['DataAddCategory']) - dataMenu.addSeparator() - dataMenu.addAction(self.allActions['DataFlatLink']) - dataMenu.addAction(self.allActions['DataArrangeLink']) + dataMenu.addAction(self.allActions['DataSwapCategory']) toolsMenu = self.menuBar().addMenu(_('&Tools')) toolsMenu.addAction(self.allActions['ToolsFindText']) @@ -491,6 +579,23 @@ toolsMenu.addAction(self.allActions['ToolsToolbars']) toolsMenu.addAction(self.allActions['ToolsFonts']) + formatMenu = self.menuBar().addMenu(_('Fo&rmat')) + formatMenu.addAction(self.allActions['FormatBoldFont']) + formatMenu.addAction(self.allActions['FormatItalicFont']) + formatMenu.addAction(self.allActions['FormatUnderlineFont']) + formatMenu.addSeparator() + # add action's parent to get the sub-menu + formatMenu.addMenu(self.allActions['FormatFontSize'].parent()) + # add the action to activate the shortcut key + self.addAction(self.allActions['FormatFontSize']) + formatMenu.addAction(self.allActions['FormatFontColor']) + formatMenu.addSeparator() + formatMenu.addAction(self.allActions['FormatExtLink']) + formatMenu.addAction(self.allActions['FormatIntLink']) + formatMenu.addSeparator() + formatMenu.addAction(self.allActions['FormatSelectAll']) + formatMenu.addAction(self.allActions['FormatClearFormat']) + viewMenu = self.menuBar().addMenu(_('&View')) viewMenu.addAction(self.allActions['ViewExpandBranch']) viewMenu.addAction(self.allActions['ViewCollapseBranch']) @@ -502,6 +607,7 @@ viewMenu.addAction(self.allActions['ViewDataEditor']) viewMenu.addAction(self.allActions['ViewTitleList']) viewMenu.addSeparator() + viewMenu.addAction(self.allActions['ViewBreadcrumb']) viewMenu.addAction(self.allActions['ViewShowChildPane']) viewMenu.addAction(self.allActions['ViewShowDescend']) @@ -516,40 +622,28 @@ helpMenu.addAction(self.allActions['HelpFull']) helpMenu.addSeparator() helpMenu.addAction(self.allActions['HelpAbout']) - helpMenu.addAction(self.allActions['HelpPlugin']) - - def loadRecentMenu(self): - """Load recent file items to file menu before showing. - """ - for action in self.fileMenu.actions(): - text = action.text() - if len(text) > 1 and text[0] == '&' and '0' <= text[1] <= '9': - self.fileMenu.removeAction(action) - self.fileMenu.insertActions(self.recentFileSep, - globalref.mainControl.recentFiles. - getActions()) - - def loadWindowMenu(self): - """Load window list items to window menu before showing. - """ - for action in self.windowMenu.actions(): - text = action.text() - if len(text) > 1 and text[0] == '&' and '0' <= text[1] <= '9': - self.windowMenu.removeAction(action) - self.windowMenu.addActions(globalref.mainControl.windowActions()) def viewExpandBranch(self): - """Expand all children of the selected nodes. + """Expand all children of the selected spots. """ - QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) - self.treeView.expandSelectedBranches() - QtGui.QApplication.restoreOverrideCursor() + QApplication.setOverrideCursor(Qt.WaitCursor) + selectedSpots = self.treeView.selectionModel().selectedSpots() + if not selectedSpots: + selectedSpots = self.treeView.model().treeStructure.rootSpots() + for spot in selectedSpots: + self.treeView.expandBranch(spot) + QApplication.restoreOverrideCursor() def viewCollapseBranch(self): - """Collapse all children of the selected nodes. + """Collapse all children of the selected spots. """ - for node in self.treeView.selectionModel().selectedNodes(): - self.treeView.collapseBranch(node) + QApplication.setOverrideCursor(Qt.WaitCursor) + selectedSpots = self.treeView.selectionModel().selectedSpots() + if not selectedSpots: + selectedSpots = self.treeView.model().treeStructure.rootSpots() + for spot in selectedSpots: + self.treeView.collapseBranch(spot) + QApplication.restoreOverrideCursor() def viewPrevSelect(self): """Return to the previous tree selection. @@ -574,14 +668,15 @@ else: self.rightTabs.setCurrentWidget(self.titleSplitter) - def viewDescendants(self, checked): - """Set the output view to show indented descendants if checked. + def viewBreadcrumb(self, checked): + """Enable or disable the display of the breadcrumb view. Arguments: checked -- True if to be shown, False if to be hidden """ - self.rightViewList[1].showDescendants = checked - self.updateRightViews() + self.breadcrumbView.setVisible(checked) + if checked: + self.updateRightViews() def viewShowChildPane(self, checked): """Enable or disable the display of children in a split pane. @@ -589,10 +684,60 @@ Arguments: checked -- True if to be shown, False if to be hidden """ - for view in self.rightViewList: - view.hideChildView = not checked + for tabNum in range(3): + for splitNum in range(2): + view = self.rightTabs.widget(tabNum).widget(splitNum) + view.hideChildView = not checked + self.updateRightViews() + + def viewDescendants(self, checked): + """Set the output view to show indented descendants if checked. + + Arguments: + checked -- True if to be shown, False if to be hidden + """ + self.outputSplitter.widget(1).showDescendants = checked self.updateRightViews() + def incremSearchStart(self): + """Start an incremental title search. + """ + if not self.treeFilterView: + self.treeView.setFocus() + self.treeView.incremSearchStart() + + def incremSearchNext(self): + """Go to the next match in an incremental title search. + """ + if not self.treeFilterView: + self.treeView.incremSearchNext() + + def incremSearchPrev(self): + """Go to the previous match in an incremental title search. + """ + if not self.treeFilterView: + self.treeView.incremSearchPrev() + + def loadRecentMenu(self): + """Load recent file items to file menu before showing. + """ + for action in self.fileMenu.actions(): + text = action.text() + if len(text) > 1 and text[0] == '&' and '0' <= text[1] <= '9': + self.fileMenu.removeAction(action) + self.fileMenu.insertActions(self.recentFileSep, + globalref.mainControl.recentFiles. + getActions()) + + def loadWindowMenu(self): + """Load window list items to window menu before showing. + """ + for action in self.windowMenu.actions(): + text = action.text() + if len(text) > 1 and text[0] == '&' and '0' <= text[1] <= '9': + self.windowMenu.removeAction(action) + self.windowMenu.addActions(globalref.mainControl.windowActions()) + def saveWindowGeom(self): """Save window geometry parameters to history options. """ @@ -600,18 +745,29 @@ globalref.histOptions.changeValue('WindowYSize', self.height()) globalref.histOptions.changeValue('WindowXPos', self.geometry().x()) globalref.histOptions.changeValue('WindowYPos', self.geometry().y()) - leftWidth, rightWidth = self.mainSplitter.sizes() - treePercent = int(100 * leftWidth / (leftWidth + rightWidth)) - globalref.histOptions.changeValue('TreeSplitPercent', treePercent) - upperWidth, lowerWidth = self.outputSplitter.sizes() - outputPercent = int(100 * upperWidth / (upperWidth + lowerWidth)) - globalref.histOptions.changeValue('OutputSplitPercent', outputPercent) - upperWidth, lowerWidth = self.editorSplitter.sizes() - editorPercent = int(100 * upperWidth / (upperWidth + lowerWidth)) - globalref.histOptions.changeValue('EditorSplitPercent', editorPercent) - upperWidth, lowerWidth = self.titleSplitter.sizes() - titlePercent = int(100 * upperWidth / (upperWidth + lowerWidth)) - globalref.histOptions.changeValue('TitleSplitPercent', titlePercent) + try: + upperWidth, lowerWidth = self.breadcrumbSplitter.sizes() + crumbPercent = int(100 * upperWidth / (upperWidth + lowerWidth)) + globalref.histOptions.changeValue('CrumbSplitPercent', + crumbPercent) + + leftWidth, rightWidth = self.treeSplitter.sizes() + treePercent = int(100 * leftWidth / (leftWidth + rightWidth)) + globalref.histOptions.changeValue('TreeSplitPercent', treePercent) + upperWidth, lowerWidth = self.outputSplitter.sizes() + outputPercent = int(100 * upperWidth / (upperWidth + lowerWidth)) + globalref.histOptions.changeValue('OutputSplitPercent', + outputPercent) + upperWidth, lowerWidth = self.editorSplitter.sizes() + editorPercent = int(100 * upperWidth / (upperWidth + lowerWidth)) + globalref.histOptions.changeValue('EditorSplitPercent', + editorPercent) + upperWidth, lowerWidth = self.titleSplitter.sizes() + titlePercent = int(100 * upperWidth / (upperWidth + lowerWidth)) + globalref.histOptions.changeValue('TitleSplitPercent', + titlePercent) + except ZeroDivisionError: + pass # skip if splitter sizes were never set tabNum = self.rightTabs.currentIndex() globalref.histOptions.changeValue('ActiveRightView', tabNum) @@ -621,47 +777,60 @@ Arguments: offset -- number of pixels to offset window, down and to right """ - rect = QtCore.QRect(globalref.histOptions.getValue('WindowXPos'), - globalref.histOptions.getValue('WindowYPos'), - globalref.histOptions.getValue('WindowXSize'), - globalref.histOptions.getValue('WindowYSize')) + rect = QRect(globalref.histOptions['WindowXPos'], + globalref.histOptions['WindowYPos'], + globalref.histOptions['WindowXSize'], + globalref.histOptions['WindowYSize']) if rect.x() == -1000 and rect.y() == -1000: # let OS position window the first time self.resize(rect.size()) else: if offset: rect.adjust(offset, offset, offset, offset) - desktop = QtGui.QApplication.desktop() + desktop = QApplication.desktop() if desktop.isVirtualDesktop(): - availRect = desktop.screen().rect() + # availRect = desktop.screen().rect() # buggy in windows? + availRect = (QGuiApplication.screens()[0]. + availableVirtualGeometry()) else: availRect = desktop.availableGeometry(desktop.primaryScreen()) rect = rect.intersected(availRect) self.setGeometry(rect) - treeWidth = int(self.mainSplitter.width() / 100 * - globalref.histOptions.getValue('TreeSplitPercent')) - self.mainSplitter.setSizes([treeWidth, - self.mainSplitter.width() - treeWidth]) + crumbWidth = int(self.breadcrumbSplitter.width() / 100 * + globalref.histOptions['CrumbSplitPercent']) + self.breadcrumbSplitter.setSizes([crumbWidth, + self.breadcrumbSplitter.width() - + crumbWidth]) + treeWidth = int(self.treeSplitter.width() / 100 * + globalref.histOptions['TreeSplitPercent']) + self.treeSplitter.setSizes([treeWidth, + self.treeSplitter.width() - treeWidth]) outHeight = int(self.outputSplitter.height() / 100.0 * - globalref.histOptions.getValue('OutputSplitPercent')) + globalref.histOptions['OutputSplitPercent']) self.outputSplitter.setSizes([outHeight, self.outputSplitter.height() - outHeight]) editHeight = int(self.editorSplitter.height() / 100.0 * - globalref.histOptions.getValue('EditorSplitPercent')) + globalref.histOptions['EditorSplitPercent']) self.editorSplitter.setSizes([editHeight, self.editorSplitter.height() - editHeight]) titleHeight = int(self.titleSplitter.height() / 100.0 * - globalref.histOptions.getValue('TitleSplitPercent')) + globalref.histOptions['TitleSplitPercent']) self.titleSplitter.setSizes([titleHeight, self.titleSplitter.height() - titleHeight]) - self.rightTabs.setCurrentIndex(globalref.histOptions. - getValue('ActiveRightView')) + self.rightTabs.setCurrentIndex(globalref. + histOptions['ActiveRightView']) - def restoreDefaultWindowSize(self): - """Set the window size to the initial setting in history options. + def resetWindowGeom(self): + """Set all stored window geometry values back to default settings. """ - self.resize(globalref.histOptions.getDefaultValue('WindowXSize'), - globalref.histOptions.getDefaultValue('WindowYSize')) + globalref.histOptions.resetToDefaults(['WindowXPos', 'WindowYPos', + 'WindowXSize', 'WindowYSize', + 'CrumbSplitPercent', + 'TreeSplitPercent', + 'OutputSplitPercent', + 'EditorSplitPercent', + 'TitleSplitPercent', + 'ActiveRightView']) def saveToolbarPosition(self): """Save the toolbar position to the toolbar options. @@ -673,13 +842,13 @@ def restoreToolbarPosition(self): """Restore the toolbar position from the toolbar options. """ - toolbarPos = globalref.toolbarOptions.getValue('ToolbarPosition') + toolbarPos = globalref.toolbarOptions['ToolbarPosition'] if toolbarPos: self.restoreState(base64.b64decode(bytes(toolbarPos, 'ascii'))) def dragEnterEvent(self, event): """Accept drags of files to this window. - + Arguments: event -- the drag event object """ @@ -688,13 +857,14 @@ def dropEvent(self, event): """Open a file dropped onto this window. - + Arguments: event -- the drop event object """ fileList = event.mimeData().urls() if fileList: - globalref.mainControl.openFile(fileList[0].toLocalFile(), True) + path = pathlib.Path(fileList[0].toLocalFile()) + globalref.mainControl.openFile(path, checkModified=True) def changeEvent(self, event): """Detect an activation of the main window and emit a signal. @@ -703,8 +873,8 @@ event -- the change event object """ super().changeEvent(event) - if (event.type() == QtCore.QEvent.ActivationChange and - QtGui.QApplication.activeWindow() == self): + if (event.type() == QEvent.ActivationChange and + QApplication.activeWindow() == self): self.winActivated.emit(self) def closeEvent(self, event): diff -Nru treeline-2.0.2/source/undo.py treeline-3.0.1/source/undo.py --- treeline-2.0.2/source/undo.py 2015-09-12 21:30:35.000000000 +0000 +++ treeline-3.0.1/source/undo.py 2018-09-26 01:15:36.000000000 +0000 @@ -4,7 +4,7 @@ # undo.py, provides a classes to store and execute undo & redo operations # # TreeLine, an information storage program -# Copyright (C) 2015, Douglas W. Bell +# Copyright (C) 2018, 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 @@ -32,7 +32,7 @@ self.action = action self.action.setEnabled(False) self.localControlRef = localControlRef - self.levels = globalref.genOptions.getValue('UndoLevels') + self.levels = globalref.genOptions['UndoLevels'] self.altListRef = None # holds a ref to redo or undo list def addUndoObj(self, undoObject, clearRedo=True): @@ -56,6 +56,15 @@ del self[:] self.action.setEnabled(False) + def setNumLevels(self): + """Change number of stored undo levels to the stored option. + """ + self.levels = globalref.genOptions['UndoLevels'] + del self[:-self.levels] + if self.levels == 0: + del self[:] + self.action.setEnabled(len(self) > 0) + def removeLastUndo(self, undoObject): """Remove the last undo object if it matches the given object. @@ -72,10 +81,14 @@ Remove the last undo item from the list. Restore the previous selection and saved doc modified state. """ + # # clear selection to avoid crash due to invalid selection: + # self.localControlRef.currentSelectionModel().selectSpots([], False) item = self.pop() item.undo(self.altListRef) - self.localControlRef.currentSelectionModel().\ - selectNodes(item.selectedNodes, False) + selectSpots = [node.spotByNumber(num) for (node, num) in + item.selectedTuples] + self.localControlRef.currentSelectionModel().selectSpots(selectSpots, + False) self.localControlRef.setModified(item.modified) self.action.setEnabled(len(self) > 0) @@ -87,31 +100,36 @@ """Initialize data storage, selected nodes and doc modified status. Arguments: - selectedNodes -- list of currently selected nodes - modified -- the current doc modified status + localControlRef -- ref control class for selections, modified, etc. """ self.dataList = [] - self.selectedNodes = (localControlRef.currentSelectionModel(). - selectedNodes()) + self.treeStructRef = localControlRef.structure + self.selectedTuples = [(spot.nodeRef, spot.instanceNumber()) + for spot in + localControlRef.currentSelectionModel(). + selectedSpots()] self.modified = localControlRef.modified class DataUndo(UndoBase): """Info for undo/redo of tree node data changes. """ - def __init__(self, listRef, nodes, skipSame=False, fieldRef='', - clearRedo=True): + def __init__(self, listRef, nodes, addChildren=False, addBranch=False, + skipSame=False, fieldRef='', notRedo=True): """Create the data undo class and add it to the undoStore. + Can't use skipSame if addChildren or addBranch are True. Arguments: listRef -- a ref to the undo/redo list this gets added to nodes -- a node or a list of nodes to back up + addChildren -- if True, include child nodes + addBranch -- if True, include all branch nodes (ignores addChildren skipSame -- if true, don't add an undo that is similar to the last fieldRef -- optional field name ref to check for similar changes - clearRedo -- if true, clear redo list (can't redo after changes) + notRedo -- if True, clear redo list (after changes) """ super().__init__(listRef.localControlRef) - if isinstance(nodes, treenode.TreeNode): + if not isinstance(nodes, list): nodes = [nodes] if (skipSame and listRef and isinstance(listRef[-1], DataUndo) and len(listRef[-1].dataList) == 1 and len(nodes) == 1 and @@ -119,8 +137,15 @@ fieldRef == listRef[-1].dataList[0][2]): return for node in nodes: - self.dataList.append((node, node.data.copy(), fieldRef)) - listRef.addUndoObj(self, clearRedo) + if addBranch: + for child in node.descendantGen(): + self.dataList.append((child, child.data.copy(), '')) + else: + self.dataList.append((node, node.data.copy(), fieldRef)) + if addChildren: + for child in node.childList: + self.dataList.append((child, child.data.copy(), '')) + listRef.addUndoObj(self, notRedo) def undo(self, redoRef): """Save current state to redoRef and restore saved state. @@ -129,35 +154,47 @@ redoRef -- the redo list where the current state is saved """ if redoRef != None: - DataUndo(redoRef, [data[0] for data in self.dataList], - False, '', False) + DataUndo(redoRef, [data[0] for data in self.dataList], False, + False, False, '', False) for node, data, fieldRef in self.dataList: node.data = data - node.updateUniqueId() class ChildListUndo(UndoBase): """Info for undo/redo of tree node child lists. """ - def __init__(self, listRef, nodes, skipSame=False, clearRedo=True): - """Create the data undo class and add it to the undoStore. + def __init__(self, listRef, nodes, addBranch=False, treeFormats=None, + skipSame=False, notRedo=True): + """Create the child list undo class and add it to the undoStore. + Also stores data formats if given. + Can't use skipSame if addBranch is True. Arguments: listRef -- a ref to the undo/redo list this gets added to nodes -- a parent node or a list of parents to save children + addBranch -- if True, include all branch nodes + treeFormats -- the format data to store skipSame -- if true, don't add an undo that is similar to the last - clearRedo -- if true, clear redo list (can't redo after changes) + notRedo -- if True, clear redo list (after changes) """ super().__init__(listRef.localControlRef) - if isinstance(nodes, treenode.TreeNode): + if not isinstance(nodes, list): nodes = [nodes] if (skipSame and listRef and isinstance(listRef[-1], ChildListUndo) and len(listRef[-1].dataList) == 1 and len(nodes) == 1 and nodes[0] == listRef[-1].dataList[0][0]): return + self.addBranch = addBranch + self.treeFormats = None + if treeFormats: + self.treeFormats = copy.deepcopy(treeFormats) for node in nodes: - self.dataList.append((node, node.childList[:])) - listRef.addUndoObj(self, clearRedo) + if addBranch: + for child in node.descendantGen(): + self.dataList.append((child, child.childList[:])) + else: + self.dataList.append((node, node.childList[:])) + listRef.addUndoObj(self, notRedo) def undo(self, redoRef): """Save current state to redoRef and restore saved state. @@ -166,17 +203,103 @@ redoRef -- the redo list where the current state is saved """ if redoRef != None: - ChildListUndo(redoRef, [data[0] for data in self.dataList], - False, False) + formats = None + if self.treeFormats: + formats = self.treeStructRef.treeFormats + ChildListUndo(redoRef, [data[0] for data in self.dataList], False, + formats, False, False) + if self.treeFormats: + self.treeStructRef.configDialogFormats = self.treeFormats + self.treeStructRef.applyConfigDialogFormats(False) + globalref.mainControl.updateConfigDialog() + newNodes = set() + oldNodes = set() + for node, childList in self.dataList: + origChildren = set(node.childList) + children = set(childList) + newNodes = newNodes | (children - origChildren) + oldNodes = oldNodes | (origChildren - children) for node, childList in self.dataList: - for oldNode in node.childList: - if node not in childList: - node.removeUniqueId() node.childList = childList + self.treeStructRef.rebuildNodeDict() # slow but reliable + for oldNode in oldNodes: + oldNode.removeInvalidSpotRefs() + for node, childList in self.dataList: for child in childList: - child.parent = node - for grandchild in child.descendantGen(): - node.modelRef.nodeIdDict[grandchild.uniqueId] = grandchild + if child in newNodes: + child.addSpotRef(node) + + +class ChildDataUndo(UndoBase): + """Info for undo/redo of tree node child data and lists. + """ + def __init__(self, listRef, nodes, addBranch=False, treeFormats=None, + notRedo=True): + """Create the child data undo class and add it to the undoStore. + + Arguments: + listRef -- a ref to the undo/redo list this gets added to + nodes -- a parent node or a list of parents to save children + addBranch -- if True, include all branch nodes + treeFormats -- the format data to store + notRedo -- if True, clear redo list (after changes) + """ + super().__init__(listRef.localControlRef) + if not isinstance(nodes, list): + nodes = [nodes] + self.addBranch = addBranch + self.treeFormats = None + if treeFormats: + self.treeFormats = copy.deepcopy(treeFormats) + for parent in nodes: + if addBranch: + for node in parent.descendantGen(): + self.dataList.append((node, node.data.copy(), + node.childList[:])) + else: + self.dataList.append((parent, parent.data.copy(), + parent.childList[:])) + for node in parent.childList: + self.dataList.append((node, node.data.copy(), + node.childList[:])) + listRef.addUndoObj(self, notRedo) + + def undo(self, redoRef): + """Save current state to redoRef and restore saved state. + + Arguments: + redoRef -- the redo list where the current state is saved + """ + if redoRef != None: + formats = None + if self.treeFormats: + formats = self.treeStructRef.treeFormats + ChildDataUndo(redoRef, [data[0] for data in self.dataList], False, + formats, False) + if self.treeFormats: + self.treeStructRef.configDialogFormats = self.treeFormats + self.treeStructRef.applyConfigDialogFormats(False) + globalref.mainControl.updateConfigDialog() + newNodes = set() + oldNodes = set() + for node, data, childList in self.dataList: + origChildren = set(node.childList) + children = set(childList) + newNodes = newNodes | (children - origChildren) + oldNodes = oldNodes | (origChildren - children) + for node, data, childList in self.dataList: + node.childList = childList + node.data = data + self.treeStructRef.rebuildNodeDict() # slow but reliable + for newNode in newNodes.copy(): + for child in newNode.descendantGen(): + newNodes.add(child) + for oldNode in oldNodes: + oldNode.removeInvalidSpotRefs() + for node, data, childList in self.dataList: + for child in childList: + if child in newNodes: + child.addSpotRef(node, not self.addBranch) class TypeUndo(UndoBase): @@ -185,20 +308,20 @@ Also saves node data to cover blank node title replacement and initial data settings. """ - def __init__(self, listRef, nodes, clearRedo=True): + def __init__(self, listRef, nodes, notRedo=True): """Create the data undo class and add it to the undoStore. Arguments: listRef -- a ref to the undo/redo list this gets added to nodes -- a node or a list of nodes to back up - clearRedo -- if true, clear redo list (can't redo after changes) + notRedo -- if True, add clones and clear redo list (after changes) """ super().__init__(listRef.localControlRef) - if isinstance(nodes, treenode.TreeNode): + if not isinstance(nodes, list): nodes = [nodes] for node in nodes: - self.dataList.append((node, node.formatName, node.data.copy())) - listRef.addUndoObj(self, clearRedo) + self.dataList.append((node, node.formatRef.name, node.data.copy())) + listRef.addUndoObj(self, notRedo) def undo(self, redoRef): """Save current state to redoRef and restore saved state. @@ -209,27 +332,25 @@ if redoRef != None: TypeUndo(redoRef, [data[0] for data in self.dataList], False) for node, formatName, data in self.dataList: - node.formatName = formatName + node.formatRef = self.treeStructRef.treeFormats[formatName] node.data = data - node.updateUniqueId() class FormatUndo(UndoBase): """Info for undo/redo of tree node type format changes. """ def __init__(self, listRef, origTreeFormats, newTreeFormats, - clearRedo=True): + notRedo=True): """Create the data undo class and add it to the undoStore. Arguments: listRef -- a ref to the undo/redo list this gets added to origTreeFormats -- the format data to store newTreeFormats -- the replacement format, contains rename dicts - clearRedo -- if true, clear redo list (can't redo after changes) + notRedo -- if True, clear redo list (after changes) """ super().__init__(listRef.localControlRef) self.treeFormats = copy.deepcopy(origTreeFormats) - self.modelRef = listRef.localControlRef.model self.treeFormats.fieldRenameDict = {} for typeName, fieldDict in newTreeFormats.fieldRenameDict.items(): self.treeFormats.fieldRenameDict[typeName] = {} @@ -242,53 +363,7 @@ self.treeFormats.fieldRenameDict[oldName] = (self.treeFormats. fieldRenameDict[newName]) del self.treeFormats.fieldRenameDict[newName] - self.treeFormats.changedIdFieldTypes = set() - for typeObj in newTreeFormats.changedIdFieldTypes: - try: - self.treeFormats.changedIdFieldTypes.add(self. - treeFormats[typeObj. - name]) - except KeyError: # not needed for new tree formats - pass - listRef.addUndoObj(self, clearRedo) - - def undo(self, redoRef): - """Save current state to redoRef and restore saved state. - - Arguments: - redoRef -- the redo list where the current state is saved - """ - if redoRef != None: - FormatUndo(redoRef, self.modelRef.formats, self.treeFormats, False) - self.modelRef.configDialogFormats = self.treeFormats - self.modelRef.applyConfigDialogFormats(False) - dialog = globalref.mainControl.configDialog - if dialog and dialog.isVisible(): - dialog.reset() - - -class BranchUndo(UndoBase): - """Info for undo/redo of full tree branches. - - Includes all node data and child lists. - """ - def __init__(self, listRef, nodes, clearRedo=True): - """Create the data undo class and add it to the undoStore. - - Arguments: - listRef -- a ref to the undo/redo list this gets added to - nodes -- a node or a list of nodes to save children - clearRedo -- if true, clear redo list (can't redo after changes) - """ - super().__init__(listRef.localControlRef) - if isinstance(nodes, treenode.TreeNode): - nodes = [nodes] - self.modelRef = listRef.localControlRef.model - for parent in nodes: - for node in parent.descendantGen(): - self.dataList.append((node, node.data.copy(), - node.childList[:])) - listRef.addUndoObj(self, clearRedo) + listRef.addUndoObj(self, notRedo) def undo(self, redoRef): """Save current state to redoRef and restore saved state. @@ -297,88 +372,29 @@ redoRef -- the redo list where the current state is saved """ if redoRef != None: - BranchUndo(redoRef, [data[0] for data in self.dataList], False) - for node, data, childList in self.dataList: - for oldNode in node.childList: - if node not in childList: - node.removeUniqueId() - node.data = data - node.childList = childList - for child in childList: - child.parent = node - for grandchild in child.descendantGen(): - node.modelRef.nodeIdDict[grandchild.uniqueId] = grandchild - node.updateUniqueId() - - -class BranchFormatUndo(UndoBase): - """Info for undo/redo of full tree branches and formats. - - Includes all node data, child lists, and full tree format. - """ - def __init__(self, listRef, nodes, treeFormats, clearRedo=True): - """Create the data undo class and add it to the undoStore. - - Arguments: - listRef -- a ref to the undo/redo list this gets added to - nodes -- a node or a list of nodes to save children - treeFormats -- the format data to store - clearRedo -- if true, clear redo list (can't redo after changes) - """ - super().__init__(listRef.localControlRef) - if isinstance(nodes, treenode.TreeNode): - nodes = [nodes] - self.treeFormats = copy.deepcopy(treeFormats) - self.modelRef = listRef.localControlRef.model - for parent in nodes: - for node in parent.descendantGen(): - self.dataList.append((node, node.data.copy(), - node.childList[:])) - listRef.addUndoObj(self, clearRedo) - - def undo(self, redoRef): - """Save current state to redoRef and restore saved state. - - Arguments: - redoRef -- the redo list where the current state is saved - """ - if redoRef != None: - BranchFormatUndo(redoRef, [data[0] for data in self.dataList], - self.modelRef.formats, False) - self.modelRef.formats = self.treeFormats - self.modelRef.getConfigDialogFormats(True) - dialog = globalref.mainControl.configDialog - if dialog and dialog.isVisible(): - dialog.reset() - for node, data, childList in self.dataList: - for oldNode in node.childList: - if node not in childList: - node.removeUniqueId() - node.data = data - node.childList = childList - for child in childList: - child.parent = node - for grandchild in child.descendantGen(): - node.modelRef.nodeIdDict[grandchild.uniqueId] = grandchild - node.updateUniqueId() + FormatUndo(redoRef, self.treeStructRef.treeFormats, + self.treeFormats, False) + self.treeStructRef.configDialogFormats = self.treeFormats + self.treeStructRef.applyConfigDialogFormats(False) + globalref.mainControl.updateConfigDialog() class ParamUndo(UndoBase): """Info for undo/redo of any variable parameter. """ - def __init__(self, listRef, varList, clearRedo=True): + def __init__(self, listRef, varList, notRedo=True): """Create the data undo class and add it to the undoStore. Arguments: listRef -- a ref to the undo/redo list this gets added to varList - list of tuples, variable's owner and variable's name - clearRedo -- if true, clear redo list (can't redo after changes) + notRedo -- if True, clear redo list (after changes) """ super().__init__(listRef.localControlRef) for varOwner, varName in varList: value = varOwner.__dict__[varName] self.dataList.append((varOwner, varName, value)) - listRef.addUndoObj(self, clearRedo) + listRef.addUndoObj(self, notRedo) def undo(self, redoRef): """Save current state to redoRef and restore saved state. @@ -395,20 +411,20 @@ class StateSettingUndo(UndoBase): """Info for undo/redo of objects with get/set functions for attributes. """ - def __init__(self, listRef, getFunction, setFunction, clearRedo=True): + def __init__(self, listRef, getFunction, setFunction, notRedo=True): """Create the data undo class and add it to the undoStore. Arguments: listRef -- a ref to the undo/redo list this gets added to getFunction -- a function ref that returns a state variable setFunction -- a function ref that restores from the state varible - clearRedo -- if true, clear redo list (can't redo after changes) + notRedo -- if True, clear redo list (after changes) """ super().__init__(listRef.localControlRef) self.getFunction = getFunction self.setFunction = setFunction self.data = getFunction() - listRef.addUndoObj(self, clearRedo) + listRef.addUndoObj(self, notRedo) def undo(self, redoRef): """Save current state to redoRef and restore saved state. diff -Nru treeline-2.0.2/source/urltools.py treeline-3.0.1/source/urltools.py --- treeline-2.0.2/source/urltools.py 2014-12-13 19:00:48.000000000 +0000 +++ treeline-3.0.1/source/urltools.py 2018-04-08 19:19:50.000000000 +0000 @@ -4,7 +4,7 @@ # urltools.py, provides functions for parsing and modifying URLs. # # TreeLine, an information storage program -# Copyright (C) 2011, Douglas W. Bell +# Copyright (C) 2018, 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 @@ -14,7 +14,8 @@ import re -import os +import sys +import os.path _urlRegExp = re.compile(r'([a-z]{2,}://)?(?:/?([a-z]:))?(.*)', re.IGNORECASE) @@ -125,7 +126,28 @@ """ scheme, drive, address = splitUrl(url) if drive or address.startswith('/'): - url = os.path.relpath(drive + address, refPath) + try: + url = os.path.relpath(drive + address, refPath) + except ValueError: + pass if os.sep == '\\': url = url.replace('\\', '/') return url + +def which(fileName): + """Return the full path if the fileName is found somewhere in the PATH. + + If not found, return an empty string. + Similar to the Linux which command. + Arguments: + fileName -- the name to search for + """ + extList = [''] + if sys.platform.startswith('win'): + extList.extend(os.getenv('PATHEXT', '').split(os.pathsep)) + for path in os.get_exec_path(): + for ext in extList: + fullPath = os.path.join(path, fileName + ext) + if os.access(fullPath, os.X_OK): + return fullPath + return '' diff -Nru treeline-2.0.2/templates/110en_Long_Text.trl treeline-3.0.1/templates/110en_Long_Text.trl --- treeline-2.0.2/templates/110en_Long_Text.trl 2013-01-24 11:35:38.000000000 +0000 +++ treeline-3.0.1/templates/110en_Long_Text.trl 1970-01-01 00:00:00.000000000 +0000 @@ -1,9 +0,0 @@ - - -Parent -Parent text - -Child -Child text - - diff -Nru treeline-2.0.2/templates/110en_Long_Text.trln treeline-3.0.1/templates/110en_Long_Text.trln --- treeline-2.0.2/templates/110en_Long_Text.trln 1970-01-01 00:00:00.000000000 +0000 +++ treeline-3.0.1/templates/110en_Long_Text.trln 2018-04-08 19:19:50.000000000 +0000 @@ -0,0 +1,51 @@ +{ + "formats": [ + { + "fields": [ + { + "fieldname": "Name", + "fieldtype": "Text" + }, + { + "fieldname": "Text", + "fieldtype": "Text", + "lines": 8 + } + ], + "formatname": "LONG_TEXT", + "outputlines": [ + "{*Name*}", + "{*Text*}" + ], + "titleline": "{*Name*}" + } + ], + "nodes": [ + { + "children": [ + "d8ef9244959111e7a8357054d2175f18" + ], + "data": { + "Name": "Parent", + "Text": "Parent text" + }, + "format": "LONG_TEXT", + "uid": "d8ef8eb6959111e7a8357054d2175f18" + }, + { + "children": [], + "data": { + "Name": "Child", + "Text": "Child text" + }, + "format": "LONG_TEXT", + "uid": "d8ef9244959111e7a8357054d2175f18" + } + ], + "properties": { + "tlversion": "2.9.0", + "topnodes": [ + "d8ef8eb6959111e7a8357054d2175f18" + ] + } +} \ No newline at end of file diff -Nru treeline-2.0.2/templates/210en_Contact_List.trl treeline-3.0.1/templates/210en_Contact_List.trl --- treeline-2.0.2/templates/210en_Contact_List.trl 2013-02-06 02:24:28.000000000 +0000 +++ treeline-3.0.1/templates/210en_Contact_List.trl 1970-01-01 00:00:00.000000000 +0000 @@ -1,33 +0,0 @@ - - -Main - -Friends - -John -Doe -1492 Columbus Lane -Atlantis -CA -98765 -(123) 555-4567 -(123) 555-9876 - - -john.doe@nowhere.com - - - -Family - -Jane -Roe -1812 War Lane -Britania -NM -87560 -(123) 490-4909 -1955-02-08 - - - diff -Nru treeline-2.0.2/templates/210en_Contact_List.trln treeline-3.0.1/templates/210en_Contact_List.trln --- treeline-2.0.2/templates/210en_Contact_List.trln 1970-01-01 00:00:00.000000000 +0000 +++ treeline-3.0.1/templates/210en_Contact_List.trln 2018-04-08 19:19:50.000000000 +0000 @@ -0,0 +1,151 @@ +{ + "formats": [ + { + "childtype": "PERSON", + "fields": [ + { + "fieldname": "Type", + "fieldtype": "Text" + } + ], + "formatname": "CATEGORY", + "outputlines": [ + "{*Type*}" + ], + "titleline": "{*Type*}" + }, + { + "fields": [ + { + "fieldname": "FirstName", + "fieldtype": "Text", + "sortkeynum": 2 + }, + { + "fieldname": "LastName", + "fieldtype": "Text", + "sortkeynum": 1 + }, + { + "fieldname": "Street", + "fieldtype": "Text" + }, + { + "fieldname": "City", + "fieldtype": "Text" + }, + { + "fieldname": "State", + "fieldtype": "Text" + }, + { + "fieldname": "Zip", + "fieldtype": "Text" + }, + { + "fieldname": "HomePhone", + "fieldtype": "Text" + }, + { + "fieldname": "WorkPhone", + "fieldtype": "Text" + }, + { + "fieldname": "MobilePhone", + "fieldtype": "Text" + }, + { + "fieldname": "Birthday", + "fieldtype": "Date", + "format": "%B %-d, %Y" + }, + { + "fieldname": "Email", + "fieldtype": "Text" + } + ], + "formatname": "PERSON", + "outputlines": [ + "{*FirstName*} {*LastName*}", + "{*Street*}", + "{*City*}, {*State*} {*Zip*}", + "{*HomePhone*} (H)", + "{*WorkPhone*} (W)", + "{*MobilePhone*} (M)", + "DoB: {*Birthday*}", + "{*Email*}" + ], + "titleline": "{*FirstName*} {*LastName*}" + } + ], + "nodes": [ + { + "children": [ + "f402b796959111e7a8357054d2175f18", + "f402c33a959111e7a8357054d2175f18" + ], + "data": { + "Type": "Main" + }, + "format": "CATEGORY", + "uid": "f402b5ac959111e7a8357054d2175f18" + }, + { + "children": [ + "f402be26959111e7a8357054d2175f18" + ], + "data": { + "Type": "Friends" + }, + "format": "CATEGORY", + "uid": "f402b796959111e7a8357054d2175f18" + }, + { + "children": [], + "data": { + "City": "Atlantis", + "Email": "john.doe@nowhere.com", + "FirstName": "John", + "HomePhone": "(123) 555-4567", + "LastName": "Doe", + "State": "CA", + "Street": "1492 Columbus Lane", + "WorkPhone": "(123) 555-9876", + "Zip": "98765" + }, + "format": "PERSON", + "uid": "f402be26959111e7a8357054d2175f18" + }, + { + "children": [ + "f402c448959111e7a8357054d2175f18" + ], + "data": { + "Type": "Family" + }, + "format": "CATEGORY", + "uid": "f402c33a959111e7a8357054d2175f18" + }, + { + "children": [], + "data": { + "Birthday": "1955-02-08", + "City": "Britania", + "FirstName": "Jane", + "HomePhone": "(123) 490-4909", + "LastName": "Roe", + "State": "NM", + "Street": "1812 War Lane", + "Zip": "87560" + }, + "format": "PERSON", + "uid": "f402c448959111e7a8357054d2175f18" + } + ], + "properties": { + "tlversion": "2.9.0", + "topnodes": [ + "f402b5ac959111e7a8357054d2175f18" + ] + } +} \ No newline at end of file diff -Nru treeline-2.0.2/templates/220en_Book_List.trl treeline-3.0.1/templates/220en_Book_List.trl --- treeline-2.0.2/templates/220en_Book_List.trl 2013-02-06 02:24:28.000000000 +0000 +++ treeline-3.0.1/templates/220en_Book_List.trl 1970-01-01 00:00:00.000000000 +0000 @@ -1,23 +0,0 @@ - - -SF Books - -Orson Scott -Card - -Ender's Game -1985 -2007/04/30 -Young boy is taught fighting and leadership - - - -Isaac -Asimov - -Foundation -1951 -Psychohistory predicts the fall of empire - - - diff -Nru treeline-2.0.2/templates/220en_Book_List.trln treeline-3.0.1/templates/220en_Book_List.trln --- treeline-2.0.2/templates/220en_Book_List.trln 1970-01-01 00:00:00.000000000 +0000 +++ treeline-3.0.1/templates/220en_Book_List.trln 2018-04-08 19:19:50.000000000 +0000 @@ -0,0 +1,134 @@ +{ + "formats": [ + { + "childtype": "BOOK", + "fields": [ + { + "fieldname": "AuthorFirstName", + "fieldtype": "Text", + "sortkeynum": 2 + }, + { + "fieldname": "AuthorLastName", + "fieldtype": "Text", + "sortkeynum": 1 + } + ], + "formatname": "AUTHOR", + "icon": "book_3", + "outputlines": [ + "{*AuthorFirstName*} {*AuthorLastName*}" + ], + "titleline": "{*AuthorFirstName*} {*AuthorLastName*}" + }, + { + "fields": [ + { + "fieldname": "Title", + "fieldtype": "Text", + "sortkeynum": 2 + }, + { + "fieldname": "Copyright", + "fieldtype": "Number", + "format": "0000", + "sortkeynum": 1 + }, + { + "fieldname": "ReadDate", + "fieldtype": "Date", + "format": "%B, %Y" + }, + { + "fieldname": "Plot", + "fieldtype": "Text" + } + ], + "formatname": "BOOK", + "icon": "book_1", + "outputlines": [ + "\"{*Title*}\"", + "(c) {*Copyright*}", + "Last Read: {*ReadDate*}", + "{*Plot*}" + ], + "titleline": "\"{*Title*}\"" + }, + { + "childtype": "AUTHOR", + "fields": [ + { + "fieldname": "NAME", + "fieldtype": "Text" + } + ], + "formatname": "CATEGORY", + "outputlines": [ + "{*NAME*}" + ], + "titleline": "{*NAME*}" + } + ], + "nodes": [ + { + "children": [ + "0b7bffe0959211e7a8357054d2175f18", + "0b7c0850959211e7a8357054d2175f18" + ], + "data": { + "NAME": "SF Books" + }, + "format": "CATEGORY", + "uid": "0b7bfb76959211e7a8357054d2175f18" + }, + { + "children": [ + "0b7c0530959211e7a8357054d2175f18" + ], + "data": { + "AuthorFirstName": "Orson Scott", + "AuthorLastName": "Card" + }, + "format": "AUTHOR", + "uid": "0b7bffe0959211e7a8357054d2175f18" + }, + { + "children": [], + "data": { + "Copyright": "1985", + "Plot": "Young boy is taught fighting and leadership", + "ReadDate": "2007-04-30", + "Title": "Ender's Game" + }, + "format": "BOOK", + "uid": "0b7c0530959211e7a8357054d2175f18" + }, + { + "children": [ + "0b7c0968959211e7a8357054d2175f18" + ], + "data": { + "AuthorFirstName": "Isaac", + "AuthorLastName": "Asimov" + }, + "format": "AUTHOR", + "uid": "0b7c0850959211e7a8357054d2175f18" + }, + { + "children": [], + "data": { + "Copyright": "1951", + "Plot": "Psychohistory predicts the fall of empire", + "Title": "Foundation" + }, + "format": "BOOK", + "uid": "0b7c0968959211e7a8357054d2175f18" + } + ], + "properties": { + "tlversion": "2.9.0", + "topnodes": [ + "0b7bfb76959211e7a8357054d2175f18" + ] + } +} \ No newline at end of file diff -Nru treeline-2.0.2/templates/230en_ToDo_List.trl treeline-3.0.1/templates/230en_ToDo_List.trl --- treeline-2.0.2/templates/230en_ToDo_List.trl 2013-05-02 01:32:41.000000000 +0000 +++ treeline-3.0.1/templates/230en_ToDo_List.trl 1970-01-01 00:00:00.000000000 +0000 @@ -1,28 +0,0 @@ - - -Conditional Task List - -Home Tasks - -Mow lawn -false -false - - - -Work Tasks - -Write documents -false -false - - - - - - - - - - - diff -Nru treeline-2.0.2/templates/230en_ToDo_List.trln treeline-3.0.1/templates/230en_ToDo_List.trln --- treeline-2.0.2/templates/230en_ToDo_List.trln 1970-01-01 00:00:00.000000000 +0000 +++ treeline-3.0.1/templates/230en_ToDo_List.trln 2018-04-08 19:19:50.000000000 +0000 @@ -0,0 +1,163 @@ +{ + "formats": [ + { + "childtype": "TASK_UNDONE", + "fields": [ + { + "fieldname": "Name", + "fieldtype": "Text" + } + ], + "formathtml": true, + "formatname": "CATEGORY", + "outputlines": [ + "{*Name*}" + ], + "titleline": "{*Name*}" + }, + { + "condition": "Done == \"true\"", + "fields": [ + { + "fieldname": "Name", + "fieldtype": "Text" + }, + { + "fieldname": "Done", + "fieldtype": "Boolean", + "format": "yes/no", + "init": "false" + }, + { + "fieldname": "Urgent", + "fieldtype": "Boolean", + "format": "yes/no", + "init": "false" + } + ], + "formathtml": true, + "formatname": "TASK_DONE", + "icon": "smiley_4", + "outputlines": [ + "{*Name*}" + ], + "titleline": "{*Name*}" + }, + { + "fields": [ + { + "fieldname": "Name", + "fieldtype": "Text" + }, + { + "fieldname": "Done", + "fieldtype": "Boolean", + "format": "yes/no", + "init": "false" + }, + { + "fieldname": "Urgent", + "fieldtype": "Boolean", + "format": "yes/no", + "init": "false" + } + ], + "formathtml": true, + "formatname": "TASK_UNDONE", + "generic": "TASK_DONE", + "icon": "smiley_2", + "outputlines": [ + "{*Name*}" + ], + "titleline": "{*Name*}" + }, + { + "condition": "Done == \"false\" and Urgent == \"true\"", + "fields": [ + { + "fieldname": "Name", + "fieldtype": "Text" + }, + { + "fieldname": "Done", + "fieldtype": "Boolean", + "format": "yes/no", + "init": "false" + }, + { + "fieldname": "Urgent", + "fieldtype": "Boolean", + "format": "yes/no", + "init": "true" + } + ], + "formathtml": true, + "formatname": "TASK_UNDONE_URGENT", + "generic": "TASK_DONE", + "icon": "smiley_5", + "outputlines": [ + "{*Name*}" + ], + "titleline": "{*Name*}!!!" + } + ], + "nodes": [ + { + "children": [ + "1d1b7e56959211e7a8357054d2175f18", + "1d1b857c959211e7a8357054d2175f18" + ], + "data": { + "Name": "Conditional Task List" + }, + "format": "CATEGORY", + "uid": "1d1b7c9e959211e7a8357054d2175f18" + }, + { + "children": [ + "1d1b81b2959211e7a8357054d2175f18" + ], + "data": { + "Name": "Home Tasks" + }, + "format": "CATEGORY", + "uid": "1d1b7e56959211e7a8357054d2175f18" + }, + { + "children": [], + "data": { + "Done": "false", + "Name": "Mow lawn", + "Urgent": "false" + }, + "format": "TASK_UNDONE", + "uid": "1d1b81b2959211e7a8357054d2175f18" + }, + { + "children": [ + "1d1b868a959211e7a8357054d2175f18" + ], + "data": { + "Name": "Work Tasks" + }, + "format": "CATEGORY", + "uid": "1d1b857c959211e7a8357054d2175f18" + }, + { + "children": [], + "data": { + "Done": "false", + "Name": "Write documents", + "Urgent": "false" + }, + "format": "TASK_UNDONE", + "uid": "1d1b868a959211e7a8357054d2175f18" + } + ], + "properties": { + "tlversion": "2.9.0", + "topnodes": [ + "1d1b7c9e959211e7a8357054d2175f18" + ] + } +} \ No newline at end of file diff -Nru treeline-2.0.2/templates/exports/live_tree_export.css treeline-3.0.1/templates/exports/live_tree_export.css --- treeline-2.0.2/templates/exports/live_tree_export.css 1970-01-01 00:00:00.000000000 +0000 +++ treeline-3.0.1/templates/exports/live_tree_export.css 2018-04-08 19:19:50.000000000 +0000 @@ -0,0 +1,45 @@ +/* live_tree_export.css, provides css for a read-only TreeLine view */ + +body { + background-color: white; + color: black; +} + +#tree { + width: 40%; + height: 100%; + top: 0; + left: 0; + position: fixed; + overflow: scroll; +} + +#tree ul { + cursor: default; + list-style-type: none; +} + +#tree li { + text-indent: -1.3em; +} + +.marker { + font-size: 0.8em; + display: inline-block; + width: 1.3em; + text-align: center; + vertical-align: middle; + color: blue; +} + +.selected { + color: red; +} + +#output { + margin-left: 41%; +} + +#output div { + padding-left: 2em; +} diff -Nru treeline-2.0.2/templates/exports/live_tree_export.html treeline-3.0.1/templates/exports/live_tree_export.html --- treeline-2.0.2/templates/exports/live_tree_export.html 1970-01-01 00:00:00.000000000 +0000 +++ treeline-3.0.1/templates/exports/live_tree_export.html 2018-04-08 19:19:50.000000000 +0000 @@ -0,0 +1,32 @@ + + + + + + + + + + + TreeLine Export + + + +
    +
      +
      + +
      +
      + + + + + + + diff -Nru treeline-2.0.2/templates/exports/live_tree_export.js treeline-3.0.1/templates/exports/live_tree_export.js --- treeline-2.0.2/templates/exports/live_tree_export.js 1970-01-01 00:00:00.000000000 +0000 +++ treeline-3.0.1/templates/exports/live_tree_export.js 2018-04-08 19:19:50.000000000 +0000 @@ -0,0 +1,1088 @@ +// live_tree_export.js, provides javascript code for a read-only tree view +// Works with TreeLine, an information storage program +// Copyright (C) 2018, 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 +// version. This program is distributed in the hope that it will be useful, +// but WITTHOUT ANY WARRANTY. See the included LICENSE file for details. + +"use strict"; + +var spotDict = {}; +var rootSpots = []; +var treeFormats = {}; +var selectedSpot = null; +var openMarker = "\u2296"; +var closedMarker = "\u2295"; +var leafMarker = "\u25CB"; + +function main() { + if (dataFileName) { + if (dataFilePath) { + dataFileName = dataFilePath + "/" + dataFileName; + } + loadFile(dataFileName); + } else { + loadData(document.getElementById("json").innerHTML); + } +} + +function loadFile(filePath) { + // initial load from file link + var xhttp = new XMLHttpRequest(); + xhttp.overrideMimeType("application/json"); + xhttp.open("GET", filePath, true); + xhttp.onreadystatechange = function() { + if (this.readyState == 4 && this.status == 200) { + loadData(this.responseText); + } + } + xhttp.send(null); +} + +function loadData(textData) { + // initial load from file data + var fileData = JSON.parse(textData); + fileData.formats.forEach(function(formatData) { + var formatName = formatData.formatname; + treeFormats[formatName] = new NodeFormat(formatData); + }); + var node, spot; + fileData.nodes.forEach(function(nodeData) { + node = new TreeNode(treeFormats[nodeData.format], nodeData); + spot = new TreeSpot(node, nodeData.uid); + }); + rootSpots = fileData.properties.topnodes.map(function(id) { + return spotDict[id]; + }); + rootSpots.forEach(function(rootSpot) { + rootSpot.nodeRef.assignRefs(null); + }); + var rootElement = document.getElementById("rootlist"); + rootSpots.forEach(function(rootSpot) { + if (rootSpot.nodeRef.childList.length > 0) rootSpot.open = true; + rootSpot.outputElement(rootElement); + }); +} + +function TreeSpot(nodeRef, uId) { + // class to store node positions (unique even for cloned nodes) + this.nodeRef = nodeRef; + this.uId = uId; + this.parentSpot; + this.open = false; + spotDict[uId] = this; + nodeRef.spotRefs.push(this); +} +TreeSpot.prototype.childSpots = function() { + // return an array of child spots + return this.nodeRef.childList.map(function(node) { + return node.matchedSpot(this); + }, this); +} +TreeSpot.prototype.outputElement = function(parentElement) { + // recursively output html tree elements + var node = this.nodeRef; + var element = document.createElement("li"); + var markerSpan = document.createElement("span"); + var markerText = leafMarker; + if (node.childList.length > 0) { + markerText = this.open ? openMarker : closedMarker; + } + markerSpan.appendChild(document.createTextNode(markerText)); + markerSpan.className = "marker"; + element.appendChild(markerSpan); + var textSpan = document.createElement("span"); + textSpan.appendChild(document.createTextNode(node.formatRef. + formatTitle(this))); + textSpan.className = "nodetext"; + element.appendChild(textSpan); + element.setAttribute("id", this.uId); + parentElement.appendChild(element); + if (this.open && node.childList.length > 0) this.openChildren(element); +} +TreeSpot.prototype.openChildren = function(parentElement) { + // output children of this node + var listElement = document.createElement("ul"); + parentElement.appendChild(listElement); + this.childSpots().forEach(function(childSpot) { + childSpot.outputElement(listElement); + }); +} +TreeSpot.prototype.toggleOpen = function() { + // toggle this spot's opened/closed state + if (this.nodeRef.childList.length == 0) return; + this.open = !this.open; + var element = document.getElementById(this.uId); + if (this.open) { + element.childNodes[0].innerHTML = openMarker; + this.openChildren(element); + } else { + element.childNodes[0].innerHTML = closedMarker; + var elementList = element.childNodes; + for (var i = 0; i < elementList.length; i++) { + if (elementList[i].tagName == "UL") { + element.removeChild(elementList[i]); + } + } + } +} +TreeSpot.prototype.openParents = function() { + // open all parent spots of this spot + var ancestors = []; + var spot = this.parentSpot; + var element; + while (spot) { + ancestors.unshift(spot); + spot = spot.parentSpot; + } + ancestors.forEach(function(ancestor) { + if (!ancestor.open) { + ancestor.open = true; + element = document.getElementById(ancestor.uId); + element.childNodes[0].innerHTML = openMarker; + ancestor.openChildren(element); + } + }); +} +TreeSpot.prototype.select = function() { + // change selection to this + var prevSpot = selectedSpot; + selectedSpot = this; + if (prevSpot) { + var prevElem = document.getElementById(prevSpot.uId); + if (prevElem) prevElem.childNodes[1].classList.remove("selected"); + } + var element = document.getElementById(this.uId); + element.childNodes[1].classList.add("selected"); + var outputGroup = new OutputGroup(); + document.getElementById("output").innerHTML = outputGroup.getText(); +} +TreeSpot.prototype.prevTreeSpot = function() { + // return the previous open spot in tree order + var pos, node, sibling; + if (this.parentSpot) { + pos = this.parentSpot.nodeRef.childList.indexOf(this.nodeRef); + if (pos <= 0) return this.parentSpot; + node = this.parentSpot.nodeRef.childList[pos - 1]; + sibling = node.matchedSpot(this.parentSpot); + } else { + pos = rootSpots.indexOf(this); + if (pos <= 0) return null; + sibling = rootSpots[pos - 1]; + } + while (sibling.open) { + node = sibling.nodeRef.childList[sibling.nodeRef.childList. + length - 1]; + sibling = node.matchedSpot(sibling); + } + return sibling; +} +TreeSpot.prototype.nextTreeSpot = function() { + // return the next open spot in tree order + if (this.open) { + return this.nodeRef.childList[0].matchedSpot(this); + } + var pos, sibling; + var ancestor = this; + while (ancestor.parentSpot) { + pos = ancestor.parentSpot.nodeRef.childList.indexOf(ancestor.nodeRef); + sibling = ancestor.parentSpot.nodeRef.childList[pos + 1]; + if (sibling) { + return sibling.matchedSpot(ancestor.parentSpot); + } + ancestor = ancestor.parentSpot; + } + pos = rootSpots.indexOf(ancestor); + sibling = rootSpots[pos + 1]; + if (sibling) return sibling; + return null; +} + +function TreeNode(formatRef, fileData) { + // class to store nodes + this.formatRef = formatRef; + this.data = fileData.data; + this.tmpChildRefs = fileData.children; + this.spotRefs = []; + this.childList = []; +} +TreeNode.prototype.assignRefs = function(parentSpot) { + // recursively add actual refs to child nodes and parent spots + var spot = this.spotRefs[0]; + if (spot.parentSpot !== undefined) { // cloned node + var id = spot.uId; + var num = 1; + do { + id = id + "_" + num; + num += 1; + } while (id in spotDict); + spot = new TreeSpot(this, id); + } + spot.parentSpot = parentSpot; + this.childList.forEach(function(child) { // for clones (2nd time thru) + child.assignRefs(spot); + }); + var childNode; + this.tmpChildRefs.forEach(function(childId) { // for first time thru + childNode = spotDict[childId].nodeRef; + this.childList.push(childNode); + childNode.assignRefs(spot); + }, this); + this.tmpChildRefs = []; +} +TreeNode.prototype.matchedSpot = function(parentSpot) { + // return the spot for this node that matches the given parent spot + for (var i = 0; i < this.spotRefs.length; i++) { + if (this.spotRefs[i].parentSpot === parentSpot) { + return this.spotRefs[i]; + } + } + return null; +} + +function NodeFormat(formatData) { + // class to store node format data and format output + this.fieldDict = {}; + formatData.fields.forEach(function(fieldData) { + this.fieldDict[fieldData.fieldname] = new FieldFormat(fieldData); + }, this); + this.spaceBetween = valueOrDefault(formatData, "spacebetween", true); + this.formatHtml = valueOrDefault(formatData, "formathtml", false); + this.outputSeparator = valueOrDefault(formatData, "outputsep", ", "); + this.siblingPrefix = ""; + this.siblingSuffix = ""; + this.titleLine = this.parseLine(formatData.titleline); + var lines = formatData.outputlines; + this.useBullets = valueOrDefault(formatData, "bullets", false); + if (this.useBullets) { + this.siblingPrefix = "
        "; + this.siblingSuffix = "
      "; + if (lines != [""]) { + lines[0] = "
    • " + lines[0]; + lines[lines.length - 1] += "
    • "; + } + } + this.useTables = valueOrDefault(formatData, "tables", false); + if (this.useTables) { + lines = lines.filter(String); + var newLines = []; + var headings = []; + var head, firstPart, parts; + lines.forEach(function(line) { + head = ""; + firstPart = this.parseLine(line)[0]; + if (typeof firstPart == "string" && firstPart.indexOf(":") >= 0) { + parts = line.split(":"); + head = parts.shift(); + line = parts.join(""); + } + newLines.push(line.trim()); + headings.push(head.trim()); + }, this); + this.siblingPrefix = ''; + if (headings.filter(String).length > 0) { + this.siblingPrefix += ""; + headings.forEach(function(hd) { + this.siblingPrefix += ""; + }, this); + this.siblingPrefix += ""; + } + this.siblingSuffix = "
      " + hd + "
      "; + lines = newLines.map(function(line) { + return "" + line + ""; + }); + lines[0] = "" + lines[0]; + lines[lines.length - 1] += ""; + } + this.outputLines = lines.map(this.parseLine, this); +} +NodeFormat.prototype.parseLine = function(text) { + // parse text with embedded fields, return list of fields and text + var segments = text.split(/({\*(?:\**|\?|!|&|#)[\w_\-.]+\*})/g); + return segments.map(this.parseField, this).filter(String); +} +NodeFormat.prototype.parseField = function(text) { + // parse text field, return field type or plain text if not a field + var field; + var match = /{\*(\**|\?|!|&|#)([\w_\-.]+)\*}/g.exec(text); + if (match) { + var modifier = match[1]; + var fieldName = match[2]; + if (modifier == "" && fieldName in this.fieldDict) { + return this.fieldDict[fieldName]; + } else if (modifier.match(/^\*+$/)) { + field = new FieldFormat({"fieldname": fieldName, + "fieldtype": "AncestorLevel"}); + field.ancestorLevel = modifier.length; + field.placeholder = true; + return field; + } else if (modifier == "?") { + field = new FieldFormat({"fieldname": fieldName, + "fieldtype": "AnyAncestor"}); + field.placeholder = true; + return field; + } else if (modifier == "&") { + field = new FieldFormat({"fieldname": fieldName, + "fieldtype": "ChildList"}); + field.placeholder = true; + return field; + } else if (modifier == "#") { + match = /[^0-9]+([0-9]+)$/.exec(fieldName); + if (match && match[1] != "0") { + field = new FieldFormat({"fieldname": fieldName, + "fieldtype": "DescendantCount"}); + field.descendantLevel = Number(match[1]); + field.placeholder = true; + return field; + } + } else if (modifier == "!") { + field = new FieldFormat({"fieldname": fieldName, + "fieldtype": "StaticFileInfo"}); + if (fieldName == "File_Name") { + field.staticInfo = dataFileName; + } else if (fieldName == "File_Path") { + field.staticInfo = dataFilePath; + } + field.placeholder = true; + return field; + } + } + return text; +} +NodeFormat.prototype.formatTitle = function(spot) { + // return a string with formatted title data + var result = this.titleLine.map(function(part) { + if (typeof part.outputText === "function") { + return part.outputText(spot, true, this.formatHtml); + } + return part; + }, this); + return result.join("").trim().split("\n", 1)[0]; +} +NodeFormat.prototype.formatOutput = function(spot, keepBlanks) { + // return a list of formatted text output lines + var line, numEmptyFields, numFullFields, text, match; + var result = []; + this.outputLines.forEach(function(lineData) { + line = ""; + numEmptyFields = 0; + numFullFields = 0; + lineData.forEach(function(part) { + if (typeof part.outputText === "function") { + text = part.outputText(spot, false, this.formatHtml); + if (text) { + numFullFields += 1; + } else { + numEmptyFields += 1; + } + line += text; + } else { + if (!this.formatHtml) { + part = escapeHtml(part); + } + line += part; + } + }, this); + if (keepBlanks || numFullFields > 0 || numEmptyFields == 0) { + result.push(line); + } else if (this.formatHtml && result.length > 0) { + match = /.*(|)$/gi.exec(line); + if (match) { + result[result.length - 1] += match[1]; + } + } + }, this); + return result; +} + +function FieldFormat(fieldData) { + // class to store field format data and format field output + this.name = fieldData.fieldname; + this.fieldType = fieldData.fieldtype; + this.format = valueOrDefault(fieldData, "format", ""); + this.prefix = valueOrDefault(fieldData, "prefix", ""); + this.suffix = valueOrDefault(fieldData, "suffix", ""); + this.mathResultType = valueOrDefault(fieldData, "resulttype", "number"); + this.placeholder = false; + this.ancestorLevel = 0; + this.descendantLevel = 0; + this.staticInfo = ""; + if (this.fieldType == "Numbering") { + this.numberingFormats = initNumbering(this.format); + } + if (this.fieldType == "Choice" || this.fieldType == "Combination") { + var formatText = this.format.replace(/\/\//g, "\0"); + if (valueOrDefault(fieldData, "evalhtml", false)) { + formatText = escapeHtml(formatText); + } + this.choiceList = formatText.split("/").map(function(text) { + return text.replace(/\0/g, "/"); + }); + } +} +FieldFormat.prototype.outputText = function(spot, titleMode, formatHtml) { + // return formatted output text for this field in this node + var splitValue, outputSep, selections, result, match, i, field; + var newNodes, prevNodes; + var value = valueOrDefault(spot.nodeRef.data, this.name, ""); + if (!value && !this.placeholder) return ""; + switch (this.fieldType) { + case "OneLineText": + value = value.split("
      ", 1)[0]; + break; + case "SpacedText": + value = "
      " + value + "
      "; + break; + case "Number": + var num = Number(value); + value = formatNumber(num, this.format); + break; + case "Math": + if (this.mathResultType == "number") { + var num = Number(value); + value = formatNumber(num, this.format); + } else if (this.mathResultType == "date") { + value = formatDate(value, this.format); + } else if (this.mathResultType == "time") { + value = formatTime(value, this.format); + } else if (this.mathResultType == "boolean") { + value = formatBoolean(value, this.format); + } + break; + case "Numbering": + value = formatNumbering(value, this.numberingFormats); + break; + case "Date": + value = formatDate(value, this.format); + break; + case "Time": + value = formatTime(value, this.format); + break; + case "DateTime": + splitValue = value.split(" "); + value = formatDate(splitValue[0], this.format); + value = formatTime(splitValue[1], value); + break; + case "Choice": + if (this.choiceList.indexOf(value) < 0) value = "#####"; + break; + case "Combination": + outputSep = spot.nodeRef.formatRef.outputSeparator; + value = value.replace(/\/\//g, "\0"); + selections = value.split("/").map(function(text) { + return text.replace(/\0/g, "/"); + }); + result = this.choiceList.filter(function(text) { + return selections.indexOf(text) >= 0; + }); + if (result.length == selections.length) { + value = result.join(outputSep); + } else { + value = "#####"; + } + break; + case "AutoCombination": + outputSep = spot.nodeRef.formatRef.outputSeparator; + value = value.replace(/\/\//g, "\0"); + selections = value.split("/").map(function(text) { + return text.replace(/\0/g, "/"); + }); + value = selections.join(outputSep); + break; + case "Boolean": + value = formatBoolean(value, this.format); + break; + case "ExternalLink": + case "InternalLink": + if (titleMode) { + match = /
      ]*href="([^"]+)"[^>]*>([\S\s]*?)<\/a>/i. + exec(value); + if (match) { + value = match[2].trim(); + if (!value) { + value = match[1]; + if (value.startsWith("#")) value = value.substr(1); + } + } + } + break; + case "Picture": + if (titleMode) { + match = /]*src="([^"]+)"[^>]*>/i.exec(value); + if (match) value = match(1).trim(); + } + break; + case "RegularExpression": + match = new RegExp(this.format).exec(unescapeHtml(value)); + if (!match || match[0] != unescapeHtml(value)) { + value = "#####"; + } + break; + case "AncestorLevel": + value = ""; + for (i = 0; i < this.ancestorLevel; i++) { + spot = spot.parentSpot; + } + if (spot) { + field = spot.nodeRef.formatRef.fieldDict[this.name]; + if (field) { + value = field.outputText(spot, titleMode, formatHtml); + } + } + break; + case "AnyAncestor": + value = ""; + while (spot.parentSpot) { + spot = spot.parentSpot; + field = spot.nodeRef.formatRef.fieldDict[this.name]; + if (field) { + value = field.outputText(spot, titleMode, formatHtml); + break; + } + } + break; + case "ChildList": + result = []; + spot.childSpots().forEach(function(childSpot) { + field = childSpot.nodeRef.formatRef.fieldDict[this.name]; + if (field) { + result.push(field.outputText(childSpot, titleMode, + formatHtml)); + } + }, this); + outputSep = spot.nodeRef.formatRef.outputSeparator; + value = result.join(outputSep); + break; + case "DescendantCount": + newNodes = [childSpot.nodeRef]; + for (i = 0; i < this.descendantLevel; i++) { + prevNodes = newNodes; + newNodes = []; + prevNodes.forEach(function(child) { + newNodes = newNodes.concat(child.childList); + }); + } + value = newNodes.length.toString(); + break; + case "StaticFileInfo": + value = this.staticInfo; + break; + } + var prefix = this.prefix; + var suffix = this.suffix; + if (titleMode) { + value = removeMarkup(value); + if (formatHtml) { + prefix = removeMarkup(prefix); + suffix = removeMarkup(suffix); + } + } else if (!formatHtml) { + prefix = escapeHtml(prefix); + suffix = escapeHtml(suffix); + } + return prefix + value + suffix; +} + +function OutputItem(spot, level) { + // class to store output for a single node + var format = spot.nodeRef.formatRef; + if (format.useTables) { + this.textLines = format.formatOutput(spot, true); + } else { + this.textLines = format.formatOutput(spot, false). + map(function(line) { + return line + "
      "; + }); + } + this.level = level; + this.uId = spot.uId; + this.addSpace = format.spaceBetween; + this.siblingPrefix = format.siblingPrefix; + this.siblingSuffix = format.siblingSuffix; + if (format.useBullets && this.textLines.length > 0) { + this.textLines[this.textLines.length - 1] = + this.textLines[this.textLines.length - 1].slice(0, -6); + } +} +OutputItem.prototype.addIndent = function(prevLevel, nextLevel) { + // add
      tags to define indent levels in the output + var i; + for (i = 0; i < this.level - prevLevel; i++) { + this.textLines[0] = "
      " + this.textLines[0]; + } + for (i = 0; i < this.level - nextLevel; i++) { + this.textLines[this.textLines.length - 1] += "
      "; + } +} +OutputItem.prototype.addSiblingPrefix = function() { + // add the sibling prefix before this output + if (this.siblingPrefix) { + this.textLines[0] = this.siblingPrefix + this.textLines[0]; + } +} +OutputItem.prototype.addSiblingSuffix = function() { + // add the sibling suffix after this output + if (this.siblingSuffix) { + this.textLines[this.textLines.length - 1] += this.siblingSuffix; + } +} +OutputItem.prototype.equalPrefix = function(otherItem) { + // return true if sibling prefixes and suffixes are equal + return (this.siblingPrefix == otherItem.siblingPrefix && + this.siblingSuffix == otherItem.siblingSuffix); +} + +function OutputGroup() { + // class to store and modify output lines + this.itemList = []; + if (selectedSpot) { + this.itemList.push(new OutputItem(selectedSpot, 0)); + this.addChildren(selectedSpot, 0); + if (this.hasPrefixes()) this.combineAllSiblings(); + this.addBlanksBetween(); + this.addIndents(); + } +} +OutputGroup.prototype.addChildren = function(spot, level) { + // recursively add output items for descendants + spot.childSpots().forEach(function(childSpot) { + this.itemList.push(new OutputItem(childSpot, level + 1)); + this.addChildren(childSpot, level + 1); + }, this); +} +OutputGroup.prototype.addBlanksBetween = function() { + // add blank lines between items based on node format + for (var i = 0; i < this.itemList.length - 1; i++) { + if (this.itemList[i].addSpace || this.itemList[i + 1].addSpace) { + var lines = this.itemList[i].textLines; + lines[lines.length - 1] += "
      " + } + } +} +OutputGroup.prototype.addIndents = function() { + // add nested
      elements to define indentations in the output + var prevLevel = 0; + var nextLevel; + for (var i = 0; i < this.itemList.length; i++) { + if (i + 1 < this.itemList.length) { + nextLevel = this.itemList[i + 1].level; + } else { + nextLevel = 0; + } + this.itemList[i].addIndent(prevLevel, nextLevel); + prevLevel = this.itemList[i].level; + } +} +OutputGroup.prototype.hasPrefixes = function() { + // return true if sibling prefixes or suffixes are found + var items = this.itemList.filter(function(item) { + return item.siblingPrefix || item.siblingSuffix; + }); + return items.length > 0; +} +OutputGroup.prototype.combineAllSiblings = function() { + // group all sibling items with the same prefix into single items + // also add sibling prefixes and suffixes and spaces in between + var newItems = []; + var prevItem = null; + this.itemList.forEach(function(item) { + if (prevItem) { + if (item.level == prevItem.level && item.equalPrefix(prevItem)) { + if (item.addSpace || prevItem.addSpace) { + prevItem.textLines[prevItem.textLines.length - 1] += + "
      "; + } + prevItem.textLines = prevItem.textLines.concat(item.textLines); + } else { + prevItem.addSiblingSuffix(); + newItems.push(prevItem); + item.addSiblingPrefix(); + prevItem = item; + } + } else { + item.addSiblingPrefix(); + prevItem = item; + } + }); + prevItem.addSiblingSuffix(); + newItems.push(prevItem); + this.itemList = newItems; +} +OutputGroup.prototype.getText = function() { + // return a text string for all output + if (this.itemList.length == 0) return ""; + var lines = []; + this.itemList.forEach(function(item) { + lines = lines.concat(item.textLines); + }); + return lines.join("\n"); +} + +window.onclick = function(event) { + // handle mouse clicks for open/close and selection + var spot; + if (event.target.tagName == "SPAN") { + var elemId = event.target.parentElement.getAttribute("id"); + spot = spotDict[elemId]; + if (spot) { + if (event.target.classList.contains("marker")) { + spot.toggleOpen(); + } else if (event.target.classList.contains("nodetext")) { + spot.select(); + } + } + } else if (event.target.tagName == "A") { + var addr = event.target.getAttribute("href"); + if (addr.startsWith("#")) { + event.preventDefault(); + spot = spotDict[addr.substr(1)]; + if (spot) { + spot.openParents(); + spot.select(); + } + } + } +} + +window.onkeydown = function(event) { + // handle arrow keys for selection management + var spot; + switch (event.which) { + case 38: // up arrow + if (selectedSpot) { + spot = selectedSpot.prevTreeSpot(); + } else { + spot = rootSpots[0]; + } + if (spot) spot.select(); + event.preventDefault(); + break; + case 40: // down arrow + if (selectedSpot) { + spot = selectedSpot.nextTreeSpot(); + } else { + spot = rootSpots[0]; + } + if (spot) spot.select(); + event.preventDefault(); + break; + case 37: // left arrow + if (selectedSpot && selectedSpot.open) selectedSpot.toggleOpen(); + event.preventDefault(); + break; + case 39: // right arrow + if (selectedSpot && !selectedSpot.open) selectedSpot.toggleOpen(); + event.preventDefault(); + break; + } +} + +function valueOrDefault(object, name, dflt) { + // return the value of the named property or the default value + var value = object[name]; + if (value !== undefined) return value; + return dflt; +} + +function escapeHtml(text) { + // return the given string with &, <, > escaped + return text.replace(/&/g, '&').replace(//g, + '>'); +} + +function unescapeHtml(text) { + // return the given string with &, <, > unescaped + return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, + ">"); +} + +function removeMarkup(text) { + // return text with all HTML Markup removed and entities unescaped + return text.replace(/<.*?>/g, "").replace(/&/g, "&"). + replace(/</g, "<").replace(/>/g, ">"); +} + +function formatNumber(num, format) { + // return a formttted string for the given number + var formatParts = format.split(/e/i); + if (formatParts.length < 2) return formatBasicNumber(num, format); + var formatMain = formatParts[0]; + var formatExp = formatParts[1]; + var exp = Math.floor(Math.log(Math.abs(num)) / Math.LN10); + num = num / Math.pow(10, exp); + var totalPlcs = (formatMain.match(/[#0]/g) || []).length; + if (totalPlcs < 1) totalPlcs = 1; + num = Number(num.toFixed(totalPlcs - 1)); + var radix = "."; + if (format.indexOf("\\,") < 0 && (format.indexOf("\\.") >= 0 || + (format.indexOf(",") >= 0 && format.indexOf(".") < 0))) { + radix = ","; + } + var formatWhole = formatMain.split(radix)[0]; + var wholePlcs = (formatWhole.match(/[#0]/g) || []).length; + var expChg = wholePlcs - Math.floor(Math.log(Math.abs(num)) / Math.LN10) - + 1; + num = num * Math.pow(10, expChg); + exp -= expChg; + var c = format.indexOf("e") >= 0 ? "e" : "E"; + return formatBasicNumber(num, formatMain) + c + + formatBasicNumber(exp, formatExp) +} + +function formatBasicNumber(num, format) { + // return a formatted string for the given number without an exponent + var radix; + if (format.indexOf("\\,") < 0 && (format.indexOf("\\.") >= 0 || + (format.indexOf(",") >= 0 && format.indexOf(".") < 0))) { + radix = ","; + format.replace(/\\./g, "."); + } else { + radix = "."; + format.replace(/\\,/g, ","); + } + var formatParts = format.split(radix); + var formatWhole = formatParts[0].split(""); + var formatFract = formatParts.length > 1 ? formatParts[1] : ""; + var decPlcs = (formatFract.match(/[#0]/g) || []).length; + formatFract = formatFract.split(""); + var numParts = num.toFixed(decPlcs).split("."); + var numWhole = numParts[0].split(""); + var numFract = numParts.length > 1 ? numParts[1] : ""; + numFract = numFract.replace(/0+$/g, "").split(""); + var sign = "+"; + if (numWhole[0] == "-") sign = numWhole.shift(); + var c; + var result = []; + while (numWhole.length || formatWhole.length) { + c = formatWhole.length ? formatWhole.pop() : ""; + if (c && "#0 +-".indexOf(c) < 0) { + if (numWhole.length || formatWhole.indexOf("0") >= 0) { + result.unshift(c); + } + } else if (numWhole.length && c != " ") { + result.unshift(numWhole.pop()); + if (c && "+-".indexOf(c) >= 0) { + formatWhole.push(c); + } + } else if ("0 ".indexOf(c) >= 0) { + result.unshift(c); + } else if ("+-".indexOf(c) >= 0) { + if (sign == "-" || c == "+") { + result.unshift(sign); + } + sign = ""; + } + } + if (sign == "-") { + if (result[0] == " ") { + result = [result.join("").replace(/\s(?!\s)/, "-")]; + } else { + result.unshift("-"); + } + } + if (formatFract.length || (format.length && + format.charAt(format.length - 1) == radix)) { + result.push(radix); + } + while (formatFract.length) { + c = formatFract.shift(); + if ("#0 ".indexOf(c) < 0) { + if (numFract.length || formatFract.indexOf("0") >= 0) { + result.push(c); + } + } else if (numFract.length) { + result.push(numFract.shift()); + } else if ("0 ".indexOf(c) >= 0) { + result.push("0"); + } + } + return result.join(""); +} + +function initNumbering(format) { + // return an array of basic numbering formats + var sectionStyle = false; + var tmpFormat = format.replace(/\.\./g, ".").replace(/\/\//g, "\0"); + var delim = "/"; + var formats = tmpFormat.split(delim); + if (formats.length < 2) { + tmpFormat = format.replace(/\/\//g, "/").replace(/\.\./g, "\0"); + delim = "."; + formats = tmpFormat.split(delim); + if (formats.length > 1) sectionStyle = true; + } + formats = formats.map(function(text) { + return new NumberingFormat(text.replace(/\0/g, delim), sectionStyle); + }); + return formats; +} + +function NumberingFormat(formatStr, sectionStyle) { + // class to store basic formatting for an element of numbering fields + this.romanDict = {0: "", 1: "I", 2: "II", 3: "III", 4: "IV", 5: "V", + 6: "VI", 7: "VII", 8: "VIII", 9: "IX", 10: "X", + 20: "XX", 30: "XXX", 40: "XL", 50: "L", 60: "LX", + 70: "LXX", 80: "LXXX", 90: "XC", 100: "C", 200: "CC", + 300: "CCC", 400: "CD", 500: "D", 600: "DC", + 700: "DCC", 800: "DCCC", 900: "CM", 1000: "M", + 2000: "MM", 3000: "MMM"}; + this.sectionStyle = sectionStyle; + var match = /(.*)([1AaIi])(.*)/.exec(formatStr); + if (match) { + this.prefix = match[1]; + this.format = match[2]; + this.suffix = match[3]; + } else { + this.prefix = formatStr; + this.format = "1"; + this.suffix = ""; + } +} +NumberingFormat.prototype.numString = function(num) { + var result = ""; + var digit; + var factor = 1000; + if (num > 0) { + if (this.format == "1") { + result = num.toString(); + } else if (this.format == "A" || this.format == "a") { + while (num) { + digit = (num - 1) % 26; + result = String.fromCharCode(digit + "A".charCodeAt(0)) + + result; + num = Math.floor((num - digit - 1) / 26); + } + if (this.format == "a") result = result.toLowerCase(); + } else if (num < 4000) { + while (num) { + digit = num - (num % factor); + result += this.romanDict[digit]; + factor = Math.floor(factor / 10); + num -= digit; + } + if (this.format == "i") result = result.toLowerCase(); + } + } + return this.prefix + result + this.suffix; +} + +function formatNumbering(value, numFormats) { + // return a formatted string for a numbering field + var inputNums = value.split(".").map(function(num) { + return Number(num); + }); + if (numFormats[0].sectionStyle) { + numFormats = numFormats.slice(); + while (numFormats.length < inputNums.length) { + numFormats.push(numFormats[numFormats.length - 1]); + } + var results = inputNums.map(function(num, i) { + return numFormats[i].numString(num); + }); + return results.join("."); + } else { + var numFormat = numFormats[inputNums.length - 1]; + if (!numFormat) numFormat = numFormats[numFormats.length - 1]; + return numFormat.numString(inputNums[inputNums.length - 1]); + } +} + +function formatDate(storedText, format) { + // return a formatted date string + var monthNames = ["", "January", "February", "March", "April", "May", + "June", "July", "August", "September", "October", + "November", "December"]; + var dateArray = storedText.split("-"); + var year = dateArray[0]; + var month = dateArray[1]; + var day = dateArray[2]; + var yearNum = Number(year); + var monthNum = Number(month); + var dayNum = Number(day); + format = format.replace(/%-d/g, dayNum).replace(/%d/g, day); + format = format.replace(/%a/g, weekday(yearNum, monthNum, + dayNum).substr(0, 3)); + format = format.replace(/%A/g, weekday(yearNum, monthNum, dayNum)); + format = format.replace(/%-m/g, monthNum).replace(/%m/g, month); + format = format.replace(/%b/g, monthNames[monthNum].substr(0, 3)); + format = format.replace(/%B/g, monthNames[monthNum]); + format = format.replace(/%y/g, year.slice(-2)).replace(/%Y/g, year); + format = format.replace(/%-U/g, weekNumber(yearNum, monthNum, dayNum)); + format = format.replace(/%-j/g, dayOfYear(yearNum, monthNum, dayNum)); + return format; +} + +function dayOfYear(year, month, day) { + // return the day of year (1 to 366) + var daysInMonths = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]; + var day = daysInMonths[month - 1] + day; + if (month > 2 && year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) { + day += 1; + } + return day; +} + +function firstWeekday(year) { + // return a number for the weekday of Jan. 1st (0=Sun., 6=Sat.) + var y = year - 1; + return (y + Math.floor(y / 4) - Math.floor(y / 100) + Math.floor(y / 400) + + 1) % 7; +} + +function weekday(year, month, day) { + // return the weekday name for the given date + var weekdays = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", + "Friday", "Saturday"]; + var day = (firstWeekday(year) + dayOfYear(year, month, day) - 1) % 7; + return weekdays[day]; +} + +function weekNumber(year, month, day) { + // return the week number for the given date + return Math.floor((dayOfYear(year, month, day) + firstWeekday(year) - 1) + / 7); +} + +function formatTime(storedText, format) { + // return a formatted time string + var timeArray = storedText.split(":"); + var hour = timeArray[0]; + var minute = timeArray[1]; + var second = timeArray[2].split(".")[0]; + var microSecond = timeArray[2].split(".")[1]; + var hourNum = Number(hour); + var minuteNum = Number(minute); + var secondNum = Number(second); + format = format.replace(/%-H/g, hourNum).replace(/%H/g, hour); + var ampm = "AM"; + if (hourNum == 0) { + hourNum = 12; + hour = "12"; + } else if (hourNum > 11) { + ampm = "PM"; + if (hourNum > 12) { + hourNum -= 12; + hour = hourNum.toString(); + if (hourNum < 10) hour = "0" + hour; + } + } + format = format.replace(/%-I/g, hourNum).replace(/%I/g, hour); + format = format.replace(/%-M/g, minuteNum).replace(/%M/g, minute); + format = format.replace(/%-S/g, secondNum).replace(/%S/g, second); + format = format.replace(/%f/g, microSecond).replace(/%p/g, ampm); + return format; +} + +function formatBoolean(storedText, format) { + // return a formatted boolean string + var boolDict = {"true": 0, "false": 1, "t": 0, "f": 1, + "yes": 0, "no": 1, "y": 0, "n": 1}; + var valueNum = boolDict[storedText.toLowerCase()]; + var value = format.split("/")[valueNum]; + if (value == undefined) value = "#####"; + return value; +} + +main(); Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/translations/qt_de.qm and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/translations/qt_de.qm differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/translations/qt_es.qm and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/translations/qt_es.qm differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/translations/qt_pt.qm and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/translations/qt_pt.qm differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/translations/treeline_de.qm and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/translations/treeline_de.qm differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/translations/treeline_es.qm and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/translations/treeline_es.qm differ Binary files /tmp/tmpuEHNLX/vYlZzmsPjP/treeline-2.0.2/translations/treeline_pt.qm and /tmp/tmpuEHNLX/2199R5ZNaT/treeline-3.0.1/translations/treeline_pt.qm differ diff -Nru treeline-2.0.2/treeline.desktop treeline-3.0.1/treeline.desktop --- treeline-2.0.2/treeline.desktop 1970-01-01 00:00:00.000000000 +0000 +++ treeline-3.0.1/treeline.desktop 2018-04-08 19:19:50.000000000 +0000 @@ -0,0 +1,11 @@ +[Desktop Entry] +Type=Application +Version=1.1 +Name=TreeLine +GenericName=Outliner +Comment=Organize text information in a tree structure +Exec=treeline %f +Icon=treeline-icon +StartupNotify=true +Terminal=false +Categories=Office;TextTools; diff -Nru treeline-2.0.2/uninstall.py treeline-3.0.1/uninstall.py --- treeline-2.0.2/uninstall.py 2014-03-07 01:42:07.000000000 +0000 +++ treeline-3.0.1/uninstall.py 2018-04-08 19:19:50.000000000 +0000 @@ -4,7 +4,7 @@ **************************************************************************** uninstall.py, Linux uninstall script for TreeLine - Copyright (C) 2013, Douglas W. Bell + Copyright (C) 2018, 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 @@ -74,6 +74,12 @@ removeAll(os.path.join(prefixDir, 'share', 'doc', progName)) removeAll(os.path.join(prefixDir, 'share', progName)) removeAll(os.path.join(prefixDir, 'share', 'icons', progName)) + removeAll(os.path.join(prefixDir, 'share', 'icons', 'hicolor', '48x48', + 'apps', progName + '-icon.png')) + removeAll(os.path.join(prefixDir, 'share', 'icons', 'hicolor', 'scalable', + 'apps', progName + '-icon.svg')) + removeAll(os.path.join(prefixDir, 'share', 'applications', + progName + '.desktop')) removeAll(os.path.join(prefixDir, 'bin', progName)) print('Uninstall complete.')