manuskript/manuskript/ui/editors/editorWidget.py

489 lines
18 KiB
Python
Raw Normal View History

2015-06-06 11:13:27 +12:00
#!/usr/bin/env python
2016-02-07 00:34:22 +13:00
# --!-- coding: utf8 --!--
from PyQt5.QtCore import pyqtSignal, QModelIndex
from PyQt5.QtGui import QPalette
from PyQt5.QtWidgets import QWidget, QFrame, QSpacerItem, QSizePolicy
from PyQt5.QtWidgets import QVBoxLayout, qApp, QStyle
2016-02-07 00:34:22 +13:00
from manuskript import settings
from manuskript.functions import AUC, mainWindow
from manuskript.ui.editors.editorWidget_ui import Ui_editorWidget_ui
2017-11-28 03:00:07 +13:00
from manuskript.ui.views.MDEditView import MDEditView
2017-11-11 04:26:23 +13:00
from manuskript.ui.tools.splitDialog import splitDialog
2016-02-07 00:34:22 +13:00
2015-06-06 11:13:27 +12:00
class editorWidget(QWidget, Ui_editorWidget_ui):
"""
`editorWidget` is a class responsible for displaying and editing one
`outlineItem`. This item can be a folder or a text.
It has four views (see `self.setView`)
- For folders: "text", "outline" or "cork" (set in `self.folderView`)
Text: displays a list of `textEditView` in a scroll area
Outline: displays an outline, using an `outlineView`
Cork: displays flash cards, using a `corkView`
- For text: item is simply displayed in a `textEditView`
All those views are contained in `editorWidget` single widget: `self.stack`.
`editorWidget` are managed in `tabSplitted` (that allow to open several
`outlineItem`s, either in Tabs or in split views.
`tabSplitted` are in turn managed by the `mainEditor`, which is unique and
gives UI buttons to manage all those views.
"""
2015-06-07 05:10:44 +12:00
toggledSpellcheck = pyqtSignal(bool)
dictChanged = pyqtSignal(str)
2016-02-07 00:34:22 +13:00
_maxTabTitleLength = 24
2015-06-28 00:06:35 +12:00
def __init__(self, parent):
2015-06-06 11:13:27 +12:00
QWidget.__init__(self, parent)
self.setupUi(self)
2015-06-11 05:45:42 +12:00
self.currentIndex = QModelIndex()
self.currentID = None
2015-06-06 11:13:27 +12:00
self.txtEdits = []
self.scroll.setBackgroundRole(QPalette.Base)
self.toggledSpellcheck.connect(self.txtRedacText.toggleSpellcheck, AUC)
self.dictChanged.connect(self.txtRedacText.setDict, AUC)
self.txtRedacText.setHighlighting(True)
2015-06-07 05:10:44 +12:00
self.currentDict = ""
self.spellcheck = settings.spellcheck
self.folderView = "cork"
2015-06-30 00:21:57 +12:00
self.mw = mainWindow()
self._tabWidget = None # set by mainEditor on creation
2016-02-07 00:34:22 +13:00
2017-11-08 02:25:47 +13:00
self._model = None
# Capture textEdit scrollbar, so that we can put it outside the margins.
self.txtEditScrollBar = self.txtRedacText.verticalScrollBar()
self.txtEditScrollBar.setParent(self)
self.stack.currentChanged.connect(self.setScrollBarVisibility)
2016-02-07 00:34:22 +13:00
# def setModel(self, model):
# self._model = model
# self.setView()
def resizeEvent(self, event):
"""
textEdit's scrollBar has been reparented to self. So we need to
update it's geometry when self is resized, and put it where we want it
to be.
"""
# Update scrollbar geometry
r = self.geometry()
w = 10 # Cf. style.mainEditorTabSS
r.setWidth(w)
r.moveRight(self.geometry().width())
self.txtEditScrollBar.setGeometry(r)
QWidget.resizeEvent(self, event)
def setScrollBarVisibility(self):
"""
Since the texteEdit scrollBar has been reparented to self, it is not
hidden when stack changes. We have to do it manually.
"""
self.txtEditScrollBar.setVisible(self.stack.currentIndex() == 0)
def setFolderView(self, v):
oldV = self.folderView
if v == "cork":
self.folderView = "cork"
2015-06-09 22:32:43 +12:00
elif v == "outline":
self.folderView = "outline"
else:
self.folderView = "text"
2016-02-07 00:34:22 +13:00
# Saving value
settings.folderView = self.folderView
2016-02-07 00:34:22 +13:00
if oldV != self.folderView and self.currentIndex:
self.setCurrentModelIndex(self.currentIndex)
2016-02-07 00:34:22 +13:00
def setCorkSizeFactor(self, v):
self.corkView.itemDelegate().setCorkSizeFactor(v)
2015-06-16 09:15:10 +12:00
self.redrawCorkItems()
2016-02-07 00:34:22 +13:00
2015-06-16 09:15:10 +12:00
def redrawCorkItems(self):
r = self.corkView.rootIndex()
2016-02-07 00:34:22 +13:00
2015-06-11 05:45:42 +12:00
if r.isValid():
count = r.internalPointer().childCount()
2017-11-08 02:25:47 +13:00
elif self._model:
count = self._model.rootItem.childCount()
2015-06-11 05:45:42 +12:00
else:
2017-11-08 02:25:47 +13:00
count = 0
2016-02-07 00:34:22 +13:00
2015-06-11 05:45:42 +12:00
for c in range(count):
self.corkView.itemDelegate().sizeHintChanged.emit(r.child(c, 0))
2016-02-07 00:34:22 +13:00
def updateTabTitle(self):
"""
`editorWidget` belongs to a `QTabWidget` in a `tabSplitter`. We update
the tab title to reflect that of current item.
"""
# `self._tabWidget` is set by mainEditor when creating tab and `editorWidget`.
# if `editorWidget` is ever used out of `mainEditor`, this could throw
# an error.
if not self._tabWidget:
return
if self.currentIndex.isValid():
item = self.currentIndex.internalPointer()
2017-11-08 02:25:47 +13:00
elif self._model:
item = self._model.rootItem
else:
2017-11-08 02:25:47 +13:00
return
i = self._tabWidget.indexOf(self)
self._tabWidget.setTabText(i, self.ellidedTitle(item.title()))
self._tabWidget.setTabToolTip(i, item.title())
def ellidedTitle(self, title):
if len(title) > self._maxTabTitleLength:
return "{}".format(title[:self._maxTabTitleLength])
else:
return title
2015-06-11 05:45:42 +12:00
def setView(self):
2016-02-07 00:34:22 +13:00
# index = mainWindow().treeRedacOutline.currentIndex()
# Counting the number of other selected items
2016-02-07 00:34:22 +13:00
# sel = []
# for i in mainWindow().treeRedacOutline.selectionModel().selection().indexes():
# if i.column() != 0: continue
# if i not in sel: sel.append(i)
# if len(sel) != 0:
# item = index.internalPointer()
# else:
# index = QModelIndex()
# item = self.mw.mdlOutline.rootItem
# self.currentIndex = index
2015-06-30 00:21:57 +12:00
if self.currentIndex.isValid():
item = self.currentIndex.internalPointer()
else:
item = self.mw.mdlOutline.rootItem
2016-02-07 00:34:22 +13:00
self.updateTabTitle()
2015-06-11 05:45:42 +12:00
def addTitle(itm):
2017-11-28 03:00:07 +13:00
edt = MDEditView(self, html="<h{l}>{t}</h{l}>".format(l=min(itm.level() + 1, 5), t=itm.title()),
2016-02-07 00:34:22 +13:00
autoResize=True)
2015-06-11 05:45:42 +12:00
edt.setFrameShape(QFrame.NoFrame)
self.txtEdits.append(edt)
l.addWidget(edt)
2016-02-07 00:34:22 +13:00
2015-06-11 05:45:42 +12:00
def addLine():
2015-06-16 01:46:31 +12:00
line = QFrame(self.text)
2015-06-11 05:45:42 +12:00
line.setFrameShape(QFrame.HLine)
line.setFrameShadow(QFrame.Sunken)
l.addWidget(line)
2016-02-07 00:34:22 +13:00
2015-06-16 01:46:31 +12:00
def addText(itm):
2017-11-28 03:00:07 +13:00
edt = MDEditView(self,
2016-02-07 00:34:22 +13:00
index=itm.index(),
spellcheck=self.spellcheck,
2015-06-30 00:21:57 +12:00
dict=settings.dict,
highlighting=True,
autoResize=True)
2015-06-11 05:45:42 +12:00
edt.setFrameShape(QFrame.NoFrame)
2016-03-30 22:00:27 +13:00
edt.setStatusTip("{}".format(itm.path()))
self.toggledSpellcheck.connect(edt.toggleSpellcheck, AUC)
self.dictChanged.connect(edt.setDict, AUC)
2016-02-07 00:34:22 +13:00
# edt.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
2015-06-11 05:45:42 +12:00
self.txtEdits.append(edt)
l.addWidget(edt)
2016-02-07 00:34:22 +13:00
2015-06-11 05:45:42 +12:00
def addChildren(itm):
for c in range(itm.childCount()):
child = itm.child(c)
2016-02-07 00:34:22 +13:00
2015-06-11 05:45:42 +12:00
if child.isFolder():
addTitle(child)
addChildren(child)
2016-02-07 00:34:22 +13:00
2015-06-11 05:45:42 +12:00
else:
2015-06-16 01:46:31 +12:00
addText(child)
2015-06-09 22:32:43 +12:00
addLine()
2016-02-07 00:34:22 +13:00
2015-06-11 05:45:42 +12:00
def addSpacer():
l.addItem(QSpacerItem(10, 1000, QSizePolicy.Minimum, QSizePolicy.Expanding))
2016-02-07 00:34:22 +13:00
# Display multiple selected items
# if len(sel) > 1 and False: # Buggy and not very useful, skip
# self.stack.setCurrentIndex(1)
# w = QWidget()
# l = QVBoxLayout(w)
# self.txtEdits = []
# for idx in sel:
# sItem = idx.internalPointer()
# addTitle(sItem)
# if sItem.isFolder():
# addChildren(sItem)
# else:
# addText(sItem)
# addLine()
# addSpacer()
# self.scroll.setWidget(w)
2015-06-30 00:21:57 +12:00
if item and item.isFolder() and self.folderView == "text":
2015-06-11 05:45:42 +12:00
self.stack.setCurrentIndex(1)
w = QWidget()
w.setObjectName("editorWidgetFolderText")
2015-06-11 05:45:42 +12:00
l = QVBoxLayout(w)
opt = settings.textEditor
background = (opt["background"] if not opt["backgroundTransparent"]
else "transparent")
w.setStyleSheet("background: {};".format(background))
self.stack.widget(1).setStyleSheet("background: {}"
.format(background))
2016-02-07 00:34:22 +13:00
# self.scroll.setWidgetResizable(False)
2015-06-11 05:45:42 +12:00
self.txtEdits = []
2016-02-07 00:34:22 +13:00
2017-11-08 02:25:47 +13:00
if item != self._model.rootItem:
addTitle(item)
2016-02-07 00:34:22 +13:00
2015-06-11 05:45:42 +12:00
addChildren(item)
addSpacer()
self.scroll.setWidget(w)
2016-02-07 00:34:22 +13:00
2015-06-15 22:38:38 +12:00
elif item and item.isFolder() and self.folderView == "cork":
2015-06-11 05:45:42 +12:00
self.stack.setCurrentIndex(2)
2017-11-08 02:25:47 +13:00
self.corkView.setModel(self._model)
2015-06-30 00:21:57 +12:00
self.corkView.setRootIndex(self.currentIndex)
2016-04-12 01:14:24 +12:00
try:
self.corkView.selectionModel().selectionChanged.connect(mainWindow().redacMetadata.selectionChanged, AUC)
self.corkView.clicked.connect(mainWindow().redacMetadata.selectionChanged, AUC)
self.corkView.clicked.connect(mainWindow().mainEditor.updateTargets, AUC)
except TypeError:
pass
2016-02-07 00:34:22 +13:00
2015-06-15 22:38:38 +12:00
elif item and item.isFolder() and self.folderView == "outline":
2015-06-11 05:45:42 +12:00
self.stack.setCurrentIndex(3)
self.outlineView.setModelCharacters(mainWindow().mdlCharacter)
2015-06-11 05:45:42 +12:00
self.outlineView.setModelLabels(mainWindow().mdlLabels)
self.outlineView.setModelStatus(mainWindow().mdlStatus)
2017-11-08 02:25:47 +13:00
self.outlineView.setModel(self._model)
2015-06-30 00:21:57 +12:00
self.outlineView.setRootIndex(self.currentIndex)
2016-04-12 01:14:24 +12:00
try:
self.outlineView.selectionModel().selectionChanged.connect(mainWindow().redacMetadata.selectionChanged, AUC)
self.outlineView.clicked.connect(mainWindow().redacMetadata.selectionChanged, AUC)
self.outlineView.clicked.connect(mainWindow().mainEditor.updateTargets, AUC)
except TypeError:
pass
2016-02-07 00:34:22 +13:00
if item and item.isText():
2015-06-28 00:06:35 +12:00
self.txtRedacText.setCurrentModelIndex(self.currentIndex)
2016-02-07 00:34:22 +13:00
self.stack.setCurrentIndex(0) # Single text item
else:
self.txtRedacText.setCurrentModelIndex(QModelIndex())
2016-02-07 00:34:22 +13:00
try:
2017-11-08 02:25:47 +13:00
self._model.dataChanged.connect(self.modelDataChanged, AUC)
Fix occasional crashes when (re)moving items Describing all the rabbitholes that I and kakaroto have gone through while debugging this one until dawn can frankly not do enough justice to the crazy amount of rubberducking that went on while trying to fix this. This bug would be triggered whenever you had a document open in the editor and then moved an ancestor object downwards (visually) in the tree. Or when you simply deleted the ancestor. Depending on the exact method that caused the opened item to be removed from the internal model, the exact nature of the bug would vary, which means this commit fixes a few different bits of code that lead to what appears to be the same bug. In order of appearance, the bugs that ruined our sleep were: 1) The editor widget was trying to handle the removed item at too late a stage. 2) The editor widget tried to fix its view after a move by searching for the new item with the same ID, but in the case of moving an object down it came across its own old item, ruining the attempt. 3) The editor widget did not properly account for the hierarchical nature of the model. Upon fixing these the next day, it was revealed that: 4) The outlineItem.updateWordCount(emit=False) flag is broken. This function would call setData() in several spots which would still cause emits to bubble through the system despite emit=False, and we simply got lucky that it stopped enough of them until now. This last one was caused by a small mistake in the fixes for the first three bugs, but it has led to a couple of extra changes to make any future bug hunts slightly less arduous and frustrating: a) When calling item.removeChild(c), it now resets the associated parent and model to mirror item.insertChild(c). This has also led to an extra check in model.parent() to check for its validity. b) The outlineItem.updateWordCount(emit=) flag has been removed entirely and it now emits away with reckless abandon. I have been unable to reproduce the crashes the code warned about, so I consider this a code quality fix to prevent mysterious future issues where things sometimes do not properly update right. Worthy of note is that the original code clearly showed the intention to close tabs for items that were removed. Reworking the editor to support closing a tab is unfortunately way out of scope, so this intention was left in and the new fix was structured to make it trivial to implement such a change when the time comes. An existing FIXME regarding unrelated buggy editor behaviour was left in, too. Many thanks to Kakaroto for burning the midnight oil with me to get to the bottom of this. (I learned a lot that night!) Issues #479, #516 and #559 are fixed by this commit. And maybe some others, too.
2019-05-03 07:45:12 +12:00
self._model.rowsAboutToBeRemoved.connect(self.rowsAboutToBeRemoved, AUC)
except TypeError:
pass
2016-02-07 00:34:22 +13:00
2015-06-11 05:45:42 +12:00
self.updateStatusBar()
2016-02-07 00:34:22 +13:00
2015-06-11 05:45:42 +12:00
def setCurrentModelIndex(self, index=None):
Fix occasional crashes when (re)moving items Describing all the rabbitholes that I and kakaroto have gone through while debugging this one until dawn can frankly not do enough justice to the crazy amount of rubberducking that went on while trying to fix this. This bug would be triggered whenever you had a document open in the editor and then moved an ancestor object downwards (visually) in the tree. Or when you simply deleted the ancestor. Depending on the exact method that caused the opened item to be removed from the internal model, the exact nature of the bug would vary, which means this commit fixes a few different bits of code that lead to what appears to be the same bug. In order of appearance, the bugs that ruined our sleep were: 1) The editor widget was trying to handle the removed item at too late a stage. 2) The editor widget tried to fix its view after a move by searching for the new item with the same ID, but in the case of moving an object down it came across its own old item, ruining the attempt. 3) The editor widget did not properly account for the hierarchical nature of the model. Upon fixing these the next day, it was revealed that: 4) The outlineItem.updateWordCount(emit=False) flag is broken. This function would call setData() in several spots which would still cause emits to bubble through the system despite emit=False, and we simply got lucky that it stopped enough of them until now. This last one was caused by a small mistake in the fixes for the first three bugs, but it has led to a couple of extra changes to make any future bug hunts slightly less arduous and frustrating: a) When calling item.removeChild(c), it now resets the associated parent and model to mirror item.insertChild(c). This has also led to an extra check in model.parent() to check for its validity. b) The outlineItem.updateWordCount(emit=) flag has been removed entirely and it now emits away with reckless abandon. I have been unable to reproduce the crashes the code warned about, so I consider this a code quality fix to prevent mysterious future issues where things sometimes do not properly update right. Worthy of note is that the original code clearly showed the intention to close tabs for items that were removed. Reworking the editor to support closing a tab is unfortunately way out of scope, so this intention was left in and the new fix was structured to make it trivial to implement such a change when the time comes. An existing FIXME regarding unrelated buggy editor behaviour was left in, too. Many thanks to Kakaroto for burning the midnight oil with me to get to the bottom of this. (I learned a lot that night!) Issues #479, #516 and #559 are fixed by this commit. And maybe some others, too.
2019-05-03 07:45:12 +12:00
if index and index.isValid():
2015-06-11 05:45:42 +12:00
self.currentIndex = index
2017-11-08 02:25:47 +13:00
self._model = index.model()
self.currentID = self._model.ID(index)
2015-06-11 05:45:42 +12:00
else:
self.currentIndex = QModelIndex()
self.currentID = None
2016-02-07 00:34:22 +13:00
if self._model:
self.setView()
2016-02-07 00:34:22 +13:00
Fix occasional crashes when (re)moving items Describing all the rabbitholes that I and kakaroto have gone through while debugging this one until dawn can frankly not do enough justice to the crazy amount of rubberducking that went on while trying to fix this. This bug would be triggered whenever you had a document open in the editor and then moved an ancestor object downwards (visually) in the tree. Or when you simply deleted the ancestor. Depending on the exact method that caused the opened item to be removed from the internal model, the exact nature of the bug would vary, which means this commit fixes a few different bits of code that lead to what appears to be the same bug. In order of appearance, the bugs that ruined our sleep were: 1) The editor widget was trying to handle the removed item at too late a stage. 2) The editor widget tried to fix its view after a move by searching for the new item with the same ID, but in the case of moving an object down it came across its own old item, ruining the attempt. 3) The editor widget did not properly account for the hierarchical nature of the model. Upon fixing these the next day, it was revealed that: 4) The outlineItem.updateWordCount(emit=False) flag is broken. This function would call setData() in several spots which would still cause emits to bubble through the system despite emit=False, and we simply got lucky that it stopped enough of them until now. This last one was caused by a small mistake in the fixes for the first three bugs, but it has led to a couple of extra changes to make any future bug hunts slightly less arduous and frustrating: a) When calling item.removeChild(c), it now resets the associated parent and model to mirror item.insertChild(c). This has also led to an extra check in model.parent() to check for its validity. b) The outlineItem.updateWordCount(emit=) flag has been removed entirely and it now emits away with reckless abandon. I have been unable to reproduce the crashes the code warned about, so I consider this a code quality fix to prevent mysterious future issues where things sometimes do not properly update right. Worthy of note is that the original code clearly showed the intention to close tabs for items that were removed. Reworking the editor to support closing a tab is unfortunately way out of scope, so this intention was left in and the new fix was structured to make it trivial to implement such a change when the time comes. An existing FIXME regarding unrelated buggy editor behaviour was left in, too. Many thanks to Kakaroto for burning the midnight oil with me to get to the bottom of this. (I learned a lot that night!) Issues #479, #516 and #559 are fixed by this commit. And maybe some others, too.
2019-05-03 07:45:12 +12:00
def updateIndexFromID(self, fallback=None, ignore=None):
"""
Index might have changed (through drag an drop), so we keep current
item's ID and update index. Item might have been deleted too.
Fix occasional crashes when (re)moving items Describing all the rabbitholes that I and kakaroto have gone through while debugging this one until dawn can frankly not do enough justice to the crazy amount of rubberducking that went on while trying to fix this. This bug would be triggered whenever you had a document open in the editor and then moved an ancestor object downwards (visually) in the tree. Or when you simply deleted the ancestor. Depending on the exact method that caused the opened item to be removed from the internal model, the exact nature of the bug would vary, which means this commit fixes a few different bits of code that lead to what appears to be the same bug. In order of appearance, the bugs that ruined our sleep were: 1) The editor widget was trying to handle the removed item at too late a stage. 2) The editor widget tried to fix its view after a move by searching for the new item with the same ID, but in the case of moving an object down it came across its own old item, ruining the attempt. 3) The editor widget did not properly account for the hierarchical nature of the model. Upon fixing these the next day, it was revealed that: 4) The outlineItem.updateWordCount(emit=False) flag is broken. This function would call setData() in several spots which would still cause emits to bubble through the system despite emit=False, and we simply got lucky that it stopped enough of them until now. This last one was caused by a small mistake in the fixes for the first three bugs, but it has led to a couple of extra changes to make any future bug hunts slightly less arduous and frustrating: a) When calling item.removeChild(c), it now resets the associated parent and model to mirror item.insertChild(c). This has also led to an extra check in model.parent() to check for its validity. b) The outlineItem.updateWordCount(emit=) flag has been removed entirely and it now emits away with reckless abandon. I have been unable to reproduce the crashes the code warned about, so I consider this a code quality fix to prevent mysterious future issues where things sometimes do not properly update right. Worthy of note is that the original code clearly showed the intention to close tabs for items that were removed. Reworking the editor to support closing a tab is unfortunately way out of scope, so this intention was left in and the new fix was structured to make it trivial to implement such a change when the time comes. An existing FIXME regarding unrelated buggy editor behaviour was left in, too. Many thanks to Kakaroto for burning the midnight oil with me to get to the bottom of this. (I learned a lot that night!) Issues #479, #516 and #559 are fixed by this commit. And maybe some others, too.
2019-05-03 07:45:12 +12:00
It will ignore the passed model item to avoid ambiguity during times
of inconsistent state.
"""
Fix occasional crashes when (re)moving items Describing all the rabbitholes that I and kakaroto have gone through while debugging this one until dawn can frankly not do enough justice to the crazy amount of rubberducking that went on while trying to fix this. This bug would be triggered whenever you had a document open in the editor and then moved an ancestor object downwards (visually) in the tree. Or when you simply deleted the ancestor. Depending on the exact method that caused the opened item to be removed from the internal model, the exact nature of the bug would vary, which means this commit fixes a few different bits of code that lead to what appears to be the same bug. In order of appearance, the bugs that ruined our sleep were: 1) The editor widget was trying to handle the removed item at too late a stage. 2) The editor widget tried to fix its view after a move by searching for the new item with the same ID, but in the case of moving an object down it came across its own old item, ruining the attempt. 3) The editor widget did not properly account for the hierarchical nature of the model. Upon fixing these the next day, it was revealed that: 4) The outlineItem.updateWordCount(emit=False) flag is broken. This function would call setData() in several spots which would still cause emits to bubble through the system despite emit=False, and we simply got lucky that it stopped enough of them until now. This last one was caused by a small mistake in the fixes for the first three bugs, but it has led to a couple of extra changes to make any future bug hunts slightly less arduous and frustrating: a) When calling item.removeChild(c), it now resets the associated parent and model to mirror item.insertChild(c). This has also led to an extra check in model.parent() to check for its validity. b) The outlineItem.updateWordCount(emit=) flag has been removed entirely and it now emits away with reckless abandon. I have been unable to reproduce the crashes the code warned about, so I consider this a code quality fix to prevent mysterious future issues where things sometimes do not properly update right. Worthy of note is that the original code clearly showed the intention to close tabs for items that were removed. Reworking the editor to support closing a tab is unfortunately way out of scope, so this intention was left in and the new fix was structured to make it trivial to implement such a change when the time comes. An existing FIXME regarding unrelated buggy editor behaviour was left in, too. Many thanks to Kakaroto for burning the midnight oil with me to get to the bottom of this. (I learned a lot that night!) Issues #479, #516 and #559 are fixed by this commit. And maybe some others, too.
2019-05-03 07:45:12 +12:00
idx = self._model.getIndexByID(self.currentID, ignore=ignore)
Fix occasional crashes when (re)moving items Describing all the rabbitholes that I and kakaroto have gone through while debugging this one until dawn can frankly not do enough justice to the crazy amount of rubberducking that went on while trying to fix this. This bug would be triggered whenever you had a document open in the editor and then moved an ancestor object downwards (visually) in the tree. Or when you simply deleted the ancestor. Depending on the exact method that caused the opened item to be removed from the internal model, the exact nature of the bug would vary, which means this commit fixes a few different bits of code that lead to what appears to be the same bug. In order of appearance, the bugs that ruined our sleep were: 1) The editor widget was trying to handle the removed item at too late a stage. 2) The editor widget tried to fix its view after a move by searching for the new item with the same ID, but in the case of moving an object down it came across its own old item, ruining the attempt. 3) The editor widget did not properly account for the hierarchical nature of the model. Upon fixing these the next day, it was revealed that: 4) The outlineItem.updateWordCount(emit=False) flag is broken. This function would call setData() in several spots which would still cause emits to bubble through the system despite emit=False, and we simply got lucky that it stopped enough of them until now. This last one was caused by a small mistake in the fixes for the first three bugs, but it has led to a couple of extra changes to make any future bug hunts slightly less arduous and frustrating: a) When calling item.removeChild(c), it now resets the associated parent and model to mirror item.insertChild(c). This has also led to an extra check in model.parent() to check for its validity. b) The outlineItem.updateWordCount(emit=) flag has been removed entirely and it now emits away with reckless abandon. I have been unable to reproduce the crashes the code warned about, so I consider this a code quality fix to prevent mysterious future issues where things sometimes do not properly update right. Worthy of note is that the original code clearly showed the intention to close tabs for items that were removed. Reworking the editor to support closing a tab is unfortunately way out of scope, so this intention was left in and the new fix was structured to make it trivial to implement such a change when the time comes. An existing FIXME regarding unrelated buggy editor behaviour was left in, too. Many thanks to Kakaroto for burning the midnight oil with me to get to the bottom of this. (I learned a lot that night!) Issues #479, #516 and #559 are fixed by this commit. And maybe some others, too.
2019-05-03 07:45:12 +12:00
# If we have an ID but the ID does not exist, it has been deleted.
if self.currentID and idx == QModelIndex():
Fix occasional crashes when (re)moving items Describing all the rabbitholes that I and kakaroto have gone through while debugging this one until dawn can frankly not do enough justice to the crazy amount of rubberducking that went on while trying to fix this. This bug would be triggered whenever you had a document open in the editor and then moved an ancestor object downwards (visually) in the tree. Or when you simply deleted the ancestor. Depending on the exact method that caused the opened item to be removed from the internal model, the exact nature of the bug would vary, which means this commit fixes a few different bits of code that lead to what appears to be the same bug. In order of appearance, the bugs that ruined our sleep were: 1) The editor widget was trying to handle the removed item at too late a stage. 2) The editor widget tried to fix its view after a move by searching for the new item with the same ID, but in the case of moving an object down it came across its own old item, ruining the attempt. 3) The editor widget did not properly account for the hierarchical nature of the model. Upon fixing these the next day, it was revealed that: 4) The outlineItem.updateWordCount(emit=False) flag is broken. This function would call setData() in several spots which would still cause emits to bubble through the system despite emit=False, and we simply got lucky that it stopped enough of them until now. This last one was caused by a small mistake in the fixes for the first three bugs, but it has led to a couple of extra changes to make any future bug hunts slightly less arduous and frustrating: a) When calling item.removeChild(c), it now resets the associated parent and model to mirror item.insertChild(c). This has also led to an extra check in model.parent() to check for its validity. b) The outlineItem.updateWordCount(emit=) flag has been removed entirely and it now emits away with reckless abandon. I have been unable to reproduce the crashes the code warned about, so I consider this a code quality fix to prevent mysterious future issues where things sometimes do not properly update right. Worthy of note is that the original code clearly showed the intention to close tabs for items that were removed. Reworking the editor to support closing a tab is unfortunately way out of scope, so this intention was left in and the new fix was structured to make it trivial to implement such a change when the time comes. An existing FIXME regarding unrelated buggy editor behaviour was left in, too. Many thanks to Kakaroto for burning the midnight oil with me to get to the bottom of this. (I learned a lot that night!) Issues #479, #516 and #559 are fixed by this commit. And maybe some others, too.
2019-05-03 07:45:12 +12:00
# If we are given a fallback item to display, do so.
if fallback:
self.setCurrentModelIndex(fallback)
else:
# After tab closing is implemented, any calls to `updateIndexFromID`
# should be re-evaluated to match the desired behaviour.
raise NotImplementedError("implement tab closing")
# FIXME: selection in self.mw.treeRedacOutline is not updated
# but we cannot simply setCurrentIndex through treeRedacOutline
# because this might be a tab in the background / out of focus
# Also the UI of mainEditor is not updated (so the folder icons
# are not display, button "up" doesn't work, etc.).
# Item has been moved
elif idx != self.currentIndex:
# We update the index
self.currentIndex = idx
self.setView()
2016-02-07 00:34:22 +13:00
def modelDataChanged(self, topLeft, bottomRight):
Fix occasional crashes when (re)moving items Describing all the rabbitholes that I and kakaroto have gone through while debugging this one until dawn can frankly not do enough justice to the crazy amount of rubberducking that went on while trying to fix this. This bug would be triggered whenever you had a document open in the editor and then moved an ancestor object downwards (visually) in the tree. Or when you simply deleted the ancestor. Depending on the exact method that caused the opened item to be removed from the internal model, the exact nature of the bug would vary, which means this commit fixes a few different bits of code that lead to what appears to be the same bug. In order of appearance, the bugs that ruined our sleep were: 1) The editor widget was trying to handle the removed item at too late a stage. 2) The editor widget tried to fix its view after a move by searching for the new item with the same ID, but in the case of moving an object down it came across its own old item, ruining the attempt. 3) The editor widget did not properly account for the hierarchical nature of the model. Upon fixing these the next day, it was revealed that: 4) The outlineItem.updateWordCount(emit=False) flag is broken. This function would call setData() in several spots which would still cause emits to bubble through the system despite emit=False, and we simply got lucky that it stopped enough of them until now. This last one was caused by a small mistake in the fixes for the first three bugs, but it has led to a couple of extra changes to make any future bug hunts slightly less arduous and frustrating: a) When calling item.removeChild(c), it now resets the associated parent and model to mirror item.insertChild(c). This has also led to an extra check in model.parent() to check for its validity. b) The outlineItem.updateWordCount(emit=) flag has been removed entirely and it now emits away with reckless abandon. I have been unable to reproduce the crashes the code warned about, so I consider this a code quality fix to prevent mysterious future issues where things sometimes do not properly update right. Worthy of note is that the original code clearly showed the intention to close tabs for items that were removed. Reworking the editor to support closing a tab is unfortunately way out of scope, so this intention was left in and the new fix was structured to make it trivial to implement such a change when the time comes. An existing FIXME regarding unrelated buggy editor behaviour was left in, too. Many thanks to Kakaroto for burning the midnight oil with me to get to the bottom of this. (I learned a lot that night!) Issues #479, #516 and #559 are fixed by this commit. And maybe some others, too.
2019-05-03 07:45:12 +12:00
if not self.currentIndex.isValid():
return # Just to be safe.
# We are only concerned with minor changes to the current index,
# so there is no need to call updateIndexFromID() nor setView().
if topLeft.row() <= self.currentIndex.row() <= bottomRight.row():
Fix occasional crashes when (re)moving items Describing all the rabbitholes that I and kakaroto have gone through while debugging this one until dawn can frankly not do enough justice to the crazy amount of rubberducking that went on while trying to fix this. This bug would be triggered whenever you had a document open in the editor and then moved an ancestor object downwards (visually) in the tree. Or when you simply deleted the ancestor. Depending on the exact method that caused the opened item to be removed from the internal model, the exact nature of the bug would vary, which means this commit fixes a few different bits of code that lead to what appears to be the same bug. In order of appearance, the bugs that ruined our sleep were: 1) The editor widget was trying to handle the removed item at too late a stage. 2) The editor widget tried to fix its view after a move by searching for the new item with the same ID, but in the case of moving an object down it came across its own old item, ruining the attempt. 3) The editor widget did not properly account for the hierarchical nature of the model. Upon fixing these the next day, it was revealed that: 4) The outlineItem.updateWordCount(emit=False) flag is broken. This function would call setData() in several spots which would still cause emits to bubble through the system despite emit=False, and we simply got lucky that it stopped enough of them until now. This last one was caused by a small mistake in the fixes for the first three bugs, but it has led to a couple of extra changes to make any future bug hunts slightly less arduous and frustrating: a) When calling item.removeChild(c), it now resets the associated parent and model to mirror item.insertChild(c). This has also led to an extra check in model.parent() to check for its validity. b) The outlineItem.updateWordCount(emit=) flag has been removed entirely and it now emits away with reckless abandon. I have been unable to reproduce the crashes the code warned about, so I consider this a code quality fix to prevent mysterious future issues where things sometimes do not properly update right. Worthy of note is that the original code clearly showed the intention to close tabs for items that were removed. Reworking the editor to support closing a tab is unfortunately way out of scope, so this intention was left in and the new fix was structured to make it trivial to implement such a change when the time comes. An existing FIXME regarding unrelated buggy editor behaviour was left in, too. Many thanks to Kakaroto for burning the midnight oil with me to get to the bottom of this. (I learned a lot that night!) Issues #479, #516 and #559 are fixed by this commit. And maybe some others, too.
2019-05-03 07:45:12 +12:00
self.updateTabTitle()
self.updateStatusBar()
2016-02-07 00:34:22 +13:00
Fix occasional crashes when (re)moving items Describing all the rabbitholes that I and kakaroto have gone through while debugging this one until dawn can frankly not do enough justice to the crazy amount of rubberducking that went on while trying to fix this. This bug would be triggered whenever you had a document open in the editor and then moved an ancestor object downwards (visually) in the tree. Or when you simply deleted the ancestor. Depending on the exact method that caused the opened item to be removed from the internal model, the exact nature of the bug would vary, which means this commit fixes a few different bits of code that lead to what appears to be the same bug. In order of appearance, the bugs that ruined our sleep were: 1) The editor widget was trying to handle the removed item at too late a stage. 2) The editor widget tried to fix its view after a move by searching for the new item with the same ID, but in the case of moving an object down it came across its own old item, ruining the attempt. 3) The editor widget did not properly account for the hierarchical nature of the model. Upon fixing these the next day, it was revealed that: 4) The outlineItem.updateWordCount(emit=False) flag is broken. This function would call setData() in several spots which would still cause emits to bubble through the system despite emit=False, and we simply got lucky that it stopped enough of them until now. This last one was caused by a small mistake in the fixes for the first three bugs, but it has led to a couple of extra changes to make any future bug hunts slightly less arduous and frustrating: a) When calling item.removeChild(c), it now resets the associated parent and model to mirror item.insertChild(c). This has also led to an extra check in model.parent() to check for its validity. b) The outlineItem.updateWordCount(emit=) flag has been removed entirely and it now emits away with reckless abandon. I have been unable to reproduce the crashes the code warned about, so I consider this a code quality fix to prevent mysterious future issues where things sometimes do not properly update right. Worthy of note is that the original code clearly showed the intention to close tabs for items that were removed. Reworking the editor to support closing a tab is unfortunately way out of scope, so this intention was left in and the new fix was structured to make it trivial to implement such a change when the time comes. An existing FIXME regarding unrelated buggy editor behaviour was left in, too. Many thanks to Kakaroto for burning the midnight oil with me to get to the bottom of this. (I learned a lot that night!) Issues #479, #516 and #559 are fixed by this commit. And maybe some others, too.
2019-05-03 07:45:12 +12:00
def rowsAboutToBeRemoved(self, parent, first, last):
if not self.currentIndex.isValid():
return # Just to be safe.
# Look for a common ancestor to verify whether the deleted rows include our index in their hierarchy.
childItem = self.currentIndex
ancestorCandidate = childItem.parent() # start at folder above current item
while (ancestorCandidate != parent):
childItem = ancestorCandidate
ancestorCandidate = childItem.parent()
if not ancestorCandidate.isValid():
return # we ran out of ancestors without finding the matching QModelIndex
# My sanity advocates a healthy dose of paranoia. (Just to be safe.)
if ancestorCandidate != parent:
return # we did not find our shared ancestor
# Verify our origins come from the relevant first..last range.
if first <= childItem.row() <= last:
# If the row in question was actually moved, there is a duplicate item
# already inserted elsewhere in the tree. Try to update this tab view,
# but make sure we exclude ourselves from the search for a replacement.
self.updateIndexFromID(fallback=parent, ignore=self.currentIndex.internalPointer())
def updateStatusBar(self):
# Update progress
2016-02-07 00:34:22 +13:00
# if self.currentIndex and self.currentIndex.isValid():
# if self._model:
2015-06-30 00:21:57 +12:00
mw = mainWindow()
2016-02-07 00:34:22 +13:00
if not mw:
return
Fix occasional crashes when (re)moving items Describing all the rabbitholes that I and kakaroto have gone through while debugging this one until dawn can frankly not do enough justice to the crazy amount of rubberducking that went on while trying to fix this. This bug would be triggered whenever you had a document open in the editor and then moved an ancestor object downwards (visually) in the tree. Or when you simply deleted the ancestor. Depending on the exact method that caused the opened item to be removed from the internal model, the exact nature of the bug would vary, which means this commit fixes a few different bits of code that lead to what appears to be the same bug. In order of appearance, the bugs that ruined our sleep were: 1) The editor widget was trying to handle the removed item at too late a stage. 2) The editor widget tried to fix its view after a move by searching for the new item with the same ID, but in the case of moving an object down it came across its own old item, ruining the attempt. 3) The editor widget did not properly account for the hierarchical nature of the model. Upon fixing these the next day, it was revealed that: 4) The outlineItem.updateWordCount(emit=False) flag is broken. This function would call setData() in several spots which would still cause emits to bubble through the system despite emit=False, and we simply got lucky that it stopped enough of them until now. This last one was caused by a small mistake in the fixes for the first three bugs, but it has led to a couple of extra changes to make any future bug hunts slightly less arduous and frustrating: a) When calling item.removeChild(c), it now resets the associated parent and model to mirror item.insertChild(c). This has also led to an extra check in model.parent() to check for its validity. b) The outlineItem.updateWordCount(emit=) flag has been removed entirely and it now emits away with reckless abandon. I have been unable to reproduce the crashes the code warned about, so I consider this a code quality fix to prevent mysterious future issues where things sometimes do not properly update right. Worthy of note is that the original code clearly showed the intention to close tabs for items that were removed. Reworking the editor to support closing a tab is unfortunately way out of scope, so this intention was left in and the new fix was structured to make it trivial to implement such a change when the time comes. An existing FIXME regarding unrelated buggy editor behaviour was left in, too. Many thanks to Kakaroto for burning the midnight oil with me to get to the bottom of this. (I learned a lot that night!) Issues #479, #516 and #559 are fixed by this commit. And maybe some others, too.
2019-05-03 07:45:12 +12:00
mw.mainEditor.tabChanged()
2016-02-07 00:34:22 +13:00
2015-06-07 05:10:44 +12:00
def toggleSpellcheck(self, v):
self.spellcheck = v
self.toggledSpellcheck.emit(v)
2016-02-07 00:34:22 +13:00
2015-06-07 05:10:44 +12:00
def setDict(self, dct):
self.currentDict = dct
2016-02-07 00:34:22 +13:00
self.dictChanged.emit(dct)
###############################################################################
# FUNCTIONS FOR MENU ACCESS
###############################################################################
def getCurrentItemView(self):
2017-11-11 05:21:02 +13:00
"""
Returns the current item view, between txtRedacText, outlineView and
corkView. If folder/text view, returns None. (Because handled
differently)
"""
2017-11-11 04:26:23 +13:00
if self.stack.currentIndex() == 0:
return self.txtRedacText
elif self.folderView == "outline":
return self.outlineView
elif self.folderView == "cork":
return self.corkView
else:
return None
def copy(self):
if self.getCurrentItemView(): self.getCurrentItemView().copy()
def cut(self):
if self.getCurrentItemView(): self.getCurrentItemView().cut()
def paste(self):
if self.getCurrentItemView(): self.getCurrentItemView().paste()
def rename(self):
if self.getCurrentItemView(): self.getCurrentItemView().rename()
def duplicate(self):
if self.getCurrentItemView(): self.getCurrentItemView().duplicate()
def delete(self):
if self.getCurrentItemView(): self.getCurrentItemView().delete()
def moveUp(self):
if self.getCurrentItemView(): self.getCurrentItemView().moveUp()
def moveDown(self):
if self.getCurrentItemView(): self.getCurrentItemView().moveDown()
2017-11-11 04:26:23 +13:00
def splitDialog(self):
"""
Opens a dialog to split selected items.
"""
if self.getCurrentItemView() == self.txtRedacText:
# Text editor
if not self.currentIndex.isValid():
return
sel = self.txtRedacText.textCursor().selectedText()
# selectedText uses \u2029 instead of \n, no idea why.
sel = sel.replace("\u2029", "\n")
splitDialog(self, [self.currentIndex], mark=sel)
elif self.getCurrentItemView():
2017-11-11 05:21:02 +13:00
# One of the views
2017-11-11 04:26:23 +13:00
self.getCurrentItemView().splitDialog()
def splitCursor(self):
"""
Splits items at cursor position. If there is a selection, that selection
becomes the new item's title.
Call context: Only works when editing a file.
"""
if not self.currentIndex.isValid():
return
if self.getCurrentItemView() == self.txtRedacText:
c = self.txtRedacText.textCursor()
title = c.selectedText()
# selection can be backward
pos = min(c.selectionStart(), c.selectionEnd())
item = self.currentIndex.internalPointer()
item.splitAt(pos, len(title))
2017-11-11 05:21:02 +13:00
def merge(self):
"""
Merges selected items together.
Call context: Multiple selection, same parent.
"""
if self.getCurrentItemView() == self.txtRedacText:
# Text editor, nothing to merge
pass
elif self.getCurrentItemView():
# One of the views
self.getCurrentItemView().merge()