File: titlelistview.py

package info (click to toggle)
treeline 3.1.5-1.1
  • links: PTS
  • area: main
  • in suites: trixie
  • size: 6,508 kB
  • sloc: python: 20,489; javascript: 998; makefile: 54
file content (234 lines) | stat: -rw-r--r-- 9,905 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
#!/usr/bin/env python3

#******************************************************************************
# titlelistview.py, provides a class for the title list view
#
# TreeLine, an information storage program
# Copyright (C) 2019, Douglas W. Bell
#
# This is free software; you can redistribute it and/or modify it under the
# terms of the GNU General Public License, either Version 2 or any later
# version.  This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY.  See the included LICENSE file for details.
#******************************************************************************

from PyQt5.QtCore import Qt, pyqtSignal
from PyQt5.QtGui import QKeySequence, QPalette, QTextCursor
from PyQt5.QtWidgets import QAction, QTextEdit
import treenode
import undo
import globalref


class TitleListView(QTextEdit):
    """Class override for the title list view.
    
    Sets view defaults and updates the content.
    """
    nodeModified = pyqtSignal(treenode.TreeNode)
    treeModified = pyqtSignal()
    shortcutEntered = pyqtSignal(QKeySequence)
    def __init__(self, treeView, isChildView=True, parent=None):
        """Initialize the title list view.

        Arguments:
            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.treeView = treeView
        self.isChildView = isChildView
        self.hideChildView = not globalref.genOptions['InitShowChildPane']
        self.setAcceptRichText(False)
        self.setLineWrapMode(QTextEdit.NoWrap)
        self.setTabChangesFocus(True)
        self.setUndoRedoEnabled(False)
        self.treeSelectAction = QAction(_('Select in Tree'), self)
        self.treeSelectAction.triggered.connect(self.selectLineInTree)
        self.textChanged.connect(self.readChange)

    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.
        """
        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()
            return
        self.show()
        if not self.isVisible() or self.height() == 0 or self.width() == 0:
            return
        if self.isChildView:
            selSpots = selSpots[0].childSpots()
        self.blockSignals(True)
        if selSpots:
            self.setPlainText('\n'.join(spot.nodeRef.title(spot) for spot in
                                        selSpots))
        else:
            self.clear()
        self.blockSignals(False)

    def readChange(self):
        """Update nodes after edited by user.
        """
        textList = [' '.join(text.split()) for text in self.toPlainText().
                    split('\n') if text.strip()]
        selSpots = self.treeView.selectionModel().selectedSpots()
        treeStructure = globalref.mainControl.activeControl.structure
        if self.isChildView:
            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

    def selectLineInTree(self):
        """Select the node for the current line in the tree view.
        """
        selSpots = self.treeView.selectionModel().selectedSpots()
        if not selSpots:
            selSpots = [treeStructure.structSpot()]
        spotList = selSpots[0].childSpots()
        cursor = self.textCursor()
        blockStart = cursor.block().position()
        # check for selection all on one line
        if (cursor.selectionStart() >= blockStart and
            cursor.selectionEnd() < blockStart + cursor.block().length()):
            lineNum = cursor.blockNumber()
            if len(spotList) > lineNum:
                self.treeView.selectionModel().selectSpots([spotList[lineNum]],
                                                           True, True)

    def hasSelectedText(self):
        """Return True if text is selected.
        """
        return self.textCursor().hasSelection()

    def highlightSearch(self, wordList=None, regExpList=None):
        """Highlight any found search terms.

        Arguments:
            wordList -- list of words to highlight
            regExpList -- a list of regular expression objects to highlight
        """
        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:
            regExpList = []
        for regExp in regExpList:
            for match in regExp.finditer(self.toPlainText()):
                matchText = match.group()
                if matchText not in wordList:
                    wordList.append(matchText)
        selections = []
        for word in wordList:
            while self.find(word):
                extraSel = QTextEdit.ExtraSelection()
                extraSel.cursor = self.textCursor()
                extraSel.format.setBackground(backColor)
                extraSel.format.setForeground(foreColor)
                selections.append(extraSel)
        cursor = QTextCursor(self.document())
        self.setTextCursor(cursor)  # reset main cursor/selection
        self.setExtraSelections(selections)

    def focusInEvent(self, event):
        """Handle focus-in to put cursor at end for tab-based focus.

        Arguments:
            event -- the focus in event
        """
        if event.reason() in (Qt.TabFocusReason,
                              Qt.BacktabFocusReason):
            self.moveCursor(QTextCursor.End)
        super().focusInEvent(event)

    def contextMenuEvent(self, event):
        """Override popup menu to remove local undo.

        Arguments:
            event -- the menu event
        """
        menu = self.createStandardContextMenu()
        menu.removeAction(menu.actions()[0])
        menu.removeAction(menu.actions()[0])
        menu.insertSeparator(menu.actions()[0])
        menu.insertAction(menu.actions()[0], self.treeSelectAction)
        self.treeSelectAction.setEnabled(self.isChildView and
                                         len(self.toPlainText().strip()) > 0)
        menu.exec_(event.globalPos())

    def keyPressEvent(self, event):
        """Customize handling of return and control keys.

        Ignore return key if not in show children mode and
        emit a signal for app to handle control keys.
        Arguments:
            event -- the key press event
        """
        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 (Qt.Key_Enter,
                                                    Qt.Key_Return):
            super().keyPressEvent(event)

    def resizeEvent(self, event):
        """Update view if it 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)