2015-06-10 01:52:20 +12:00
|
|
|
#!/usr/bin/env python
|
2016-02-07 00:34:22 +13:00
|
|
|
# --!-- coding: utf8 --!--
|
2017-10-24 00:45:08 +13:00
|
|
|
from PyQt5.QtCore import Qt, QSignalMapper, QSize
|
2017-11-08 00:02:02 +13:00
|
|
|
from PyQt5.QtGui import QIcon, QCursor
|
2017-11-11 04:26:23 +13:00
|
|
|
from PyQt5.QtWidgets import QAbstractItemView, qApp, QMenu, QAction, \
|
|
|
|
QListWidget, QWidgetAction, QListWidgetItem, \
|
|
|
|
QLineEdit, QInputDialog, QMessageBox, QCheckBox
|
2016-02-07 00:34:22 +13:00
|
|
|
|
|
|
|
from manuskript import settings
|
|
|
|
from manuskript.enums import Outline
|
2017-11-11 05:21:02 +13:00
|
|
|
from manuskript.functions import mainWindow, statusMessage
|
2022-05-17 21:43:22 +12:00
|
|
|
from manuskript.functions import toInt, customIcons, safeTranslate
|
2017-11-16 08:33:27 +13:00
|
|
|
from manuskript.models import outlineItem
|
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-10 01:52:20 +12:00
|
|
|
|
|
|
|
class outlineBasics(QAbstractItemView):
|
|
|
|
def __init__(self, parent=None):
|
2017-11-08 00:02:02 +13:00
|
|
|
self._indexesToOpen = None
|
2017-11-30 11:16:01 +13:00
|
|
|
self.menuCustomIcons = None
|
2016-02-07 00:34:22 +13:00
|
|
|
|
2015-06-10 01:52:20 +12:00
|
|
|
def getSelection(self):
|
|
|
|
sel = []
|
|
|
|
for i in self.selectedIndexes():
|
2016-02-07 00:34:22 +13:00
|
|
|
if i.column() != 0:
|
|
|
|
continue
|
|
|
|
if not i in sel:
|
|
|
|
sel.append(i)
|
2015-06-10 01:52:20 +12:00
|
|
|
return sel
|
2016-02-07 00:34:22 +13:00
|
|
|
|
2015-06-10 01:52:20 +12:00
|
|
|
def mouseReleaseEvent(self, event):
|
|
|
|
if event.button() == Qt.RightButton:
|
2015-06-27 01:55:34 +12:00
|
|
|
self.menu = self.makePopupMenu()
|
2015-06-10 07:20:26 +12:00
|
|
|
self.menu.popup(event.globalPos())
|
2017-11-18 05:38:06 +13:00
|
|
|
# We don't call QAbstractItemView.mouseReleaseEvent because
|
|
|
|
# outlineBasics is never subclassed alone. So the others views
|
|
|
|
# (outlineView, corkView, treeView) that subclass outlineBasics
|
|
|
|
# call their respective mother class.
|
2016-02-07 00:34:22 +13:00
|
|
|
|
2015-06-27 01:55:34 +12:00
|
|
|
def makePopupMenu(self):
|
|
|
|
index = self.currentIndex()
|
|
|
|
sel = self.getSelection()
|
|
|
|
clipboard = qApp.clipboard()
|
2016-02-07 00:34:22 +13:00
|
|
|
|
2015-06-27 01:55:34 +12:00
|
|
|
menu = QMenu(self)
|
2016-02-07 00:34:22 +13:00
|
|
|
|
2017-11-08 00:02:02 +13:00
|
|
|
# Get index under cursor
|
|
|
|
pos = self.viewport().mapFromGlobal(QCursor.pos())
|
|
|
|
mouseIndex = self.indexAt(pos)
|
|
|
|
|
|
|
|
# Get index's title
|
|
|
|
if mouseIndex.isValid():
|
|
|
|
title = mouseIndex.internalPointer().title()
|
|
|
|
|
|
|
|
elif self.rootIndex().parent().isValid():
|
|
|
|
# mouseIndex is the background of an item, so we check the parent
|
|
|
|
mouseIndex = self.rootIndex().parent()
|
|
|
|
title = mouseIndex.internalPointer().title()
|
|
|
|
|
|
|
|
else:
|
2022-05-17 21:43:22 +12:00
|
|
|
title = safeTranslate(qApp, "outlineBasics", "Root")
|
2017-11-08 00:02:02 +13:00
|
|
|
|
|
|
|
if len(title) > 25:
|
|
|
|
title = title[:25] + "…"
|
|
|
|
|
|
|
|
# Open Item action
|
|
|
|
self.actOpen = QAction(QIcon.fromTheme("go-right"),
|
2022-05-17 21:43:22 +12:00
|
|
|
safeTranslate(qApp, "outlineBasics", "Open {}".format(title)),
|
2017-11-08 00:02:02 +13:00
|
|
|
menu)
|
2017-10-15 08:39:16 +13:00
|
|
|
self.actOpen.triggered.connect(self.openItem)
|
|
|
|
menu.addAction(self.actOpen)
|
2017-10-30 21:26:46 +13:00
|
|
|
|
2017-11-08 00:02:02 +13:00
|
|
|
# Open item(s) in new tab
|
|
|
|
if mouseIndex in sel and len(sel) > 1:
|
2022-05-17 21:43:22 +12:00
|
|
|
actionTitle = safeTranslate(qApp, "outlineBasics", "Open {} items in new tabs").format(len(sel))
|
2017-11-08 00:02:02 +13:00
|
|
|
self._indexesToOpen = sel
|
|
|
|
else:
|
2022-05-17 21:43:22 +12:00
|
|
|
actionTitle = safeTranslate(qApp, "outlineBasics", "Open {} in a new tab").format(title)
|
2017-11-08 00:02:02 +13:00
|
|
|
self._indexesToOpen = [mouseIndex]
|
|
|
|
|
|
|
|
self.actNewTab = QAction(QIcon.fromTheme("go-right"), actionTitle, menu)
|
|
|
|
self.actNewTab.triggered.connect(self.openItemsInNewTabs)
|
|
|
|
menu.addAction(self.actNewTab)
|
|
|
|
|
2017-10-15 08:39:16 +13:00
|
|
|
menu.addSeparator()
|
2017-10-30 21:26:46 +13:00
|
|
|
|
2017-11-25 08:50:18 +13:00
|
|
|
# Add text / folder
|
|
|
|
self.actAddFolder = QAction(QIcon.fromTheme("folder-new"),
|
2022-05-17 21:43:22 +12:00
|
|
|
safeTranslate(qApp, "outlineBasics", "New &Folder"),
|
2017-11-25 08:50:18 +13:00
|
|
|
menu)
|
2015-06-27 01:55:34 +12:00
|
|
|
self.actAddFolder.triggered.connect(self.addFolder)
|
|
|
|
menu.addAction(self.actAddFolder)
|
2016-02-07 00:34:22 +13:00
|
|
|
|
2017-11-25 08:50:18 +13:00
|
|
|
self.actAddText = QAction(QIcon.fromTheme("document-new"),
|
2022-05-17 21:43:22 +12:00
|
|
|
safeTranslate(qApp, "outlineBasics", "New &Text"),
|
2017-11-25 08:50:18 +13:00
|
|
|
menu)
|
2015-06-27 01:55:34 +12:00
|
|
|
self.actAddText.triggered.connect(self.addText)
|
|
|
|
menu.addAction(self.actAddText)
|
2016-02-07 00:34:22 +13:00
|
|
|
|
2015-06-27 01:55:34 +12:00
|
|
|
menu.addSeparator()
|
2016-02-07 00:34:22 +13:00
|
|
|
|
2017-11-25 06:44:31 +13:00
|
|
|
# Copy, cut, paste, duplicate
|
|
|
|
self.actCut = QAction(QIcon.fromTheme("edit-cut"),
|
2022-05-17 21:43:22 +12:00
|
|
|
safeTranslate(qApp, "outlineBasics", "C&ut"), menu)
|
2015-06-27 01:55:34 +12:00
|
|
|
self.actCut.triggered.connect(self.cut)
|
|
|
|
menu.addAction(self.actCut)
|
2016-02-07 00:34:22 +13:00
|
|
|
|
2017-11-29 22:24:07 +13:00
|
|
|
self.actCopy = QAction(QIcon.fromTheme("edit-copy"),
|
2022-05-17 21:43:22 +12:00
|
|
|
safeTranslate(qApp, "outlineBasics", "&Copy"), menu)
|
2017-11-29 22:24:07 +13:00
|
|
|
self.actCopy.triggered.connect(self.copy)
|
|
|
|
menu.addAction(self.actCopy)
|
|
|
|
|
2017-11-25 06:44:31 +13:00
|
|
|
self.actPaste = QAction(QIcon.fromTheme("edit-paste"),
|
2022-05-17 21:43:22 +12:00
|
|
|
safeTranslate(qApp, "outlineBasics", "&Paste"), menu)
|
2015-06-27 01:55:34 +12:00
|
|
|
self.actPaste.triggered.connect(self.paste)
|
|
|
|
menu.addAction(self.actPaste)
|
2016-02-07 00:34:22 +13:00
|
|
|
|
2017-11-25 08:50:18 +13:00
|
|
|
# Rename / duplicate / remove items
|
|
|
|
self.actDelete = QAction(QIcon.fromTheme("edit-delete"),
|
2022-05-17 21:43:22 +12:00
|
|
|
safeTranslate(qApp, "outlineBasics", "&Delete"),
|
2017-11-25 08:50:18 +13:00
|
|
|
menu)
|
|
|
|
self.actDelete.triggered.connect(self.delete)
|
|
|
|
menu.addAction(self.actDelete)
|
|
|
|
|
2017-11-29 22:24:07 +13:00
|
|
|
self.actRename = QAction(QIcon.fromTheme("edit-rename"),
|
2022-05-17 21:43:22 +12:00
|
|
|
safeTranslate(qApp, "outlineBasics", "&Rename"),
|
2017-11-29 22:24:07 +13:00
|
|
|
menu)
|
|
|
|
self.actRename.triggered.connect(self.rename)
|
|
|
|
menu.addAction(self.actRename)
|
|
|
|
|
2015-06-27 01:55:34 +12:00
|
|
|
menu.addSeparator()
|
2016-02-07 00:34:22 +13:00
|
|
|
|
2015-06-27 01:55:34 +12:00
|
|
|
# POV
|
2022-05-17 21:43:22 +12:00
|
|
|
self.menuPOV = QMenu(safeTranslate(qApp, "outlineBasics", "Set POV"), menu)
|
2015-06-27 01:55:34 +12:00
|
|
|
mw = mainWindow()
|
2022-05-17 21:43:22 +12:00
|
|
|
a = QAction(QIcon.fromTheme("dialog-no"), safeTranslate(qApp, "outlineBasics", "None"), self.menuPOV)
|
2015-06-27 01:55:34 +12:00
|
|
|
a.triggered.connect(lambda: self.setPOV(""))
|
|
|
|
self.menuPOV.addAction(a)
|
|
|
|
self.menuPOV.addSeparator()
|
2016-02-07 00:34:22 +13:00
|
|
|
|
2015-06-27 01:55:34 +12:00
|
|
|
menus = []
|
2022-05-17 21:43:22 +12:00
|
|
|
for i in [safeTranslate(qApp, "outlineBasics", "Main"),
|
|
|
|
safeTranslate(qApp, "outlineBasics", "Secondary"),
|
|
|
|
safeTranslate(qApp, "outlineBasics", "Minor")]:
|
2015-06-27 01:55:34 +12:00
|
|
|
m = QMenu(i, self.menuPOV)
|
|
|
|
menus.append(m)
|
|
|
|
self.menuPOV.addMenu(m)
|
2016-02-07 00:34:22 +13:00
|
|
|
|
2015-06-27 01:55:34 +12:00
|
|
|
mpr = QSignalMapper(self.menuPOV)
|
2016-03-04 04:38:38 +13:00
|
|
|
for i in range(mw.mdlCharacter.rowCount()):
|
|
|
|
a = QAction(mw.mdlCharacter.icon(i), mw.mdlCharacter.name(i), self.menuPOV)
|
2015-06-27 01:55:34 +12:00
|
|
|
a.triggered.connect(mpr.map)
|
2016-03-04 04:38:38 +13:00
|
|
|
mpr.setMapping(a, int(mw.mdlCharacter.ID(i)))
|
2016-02-07 00:34:22 +13:00
|
|
|
|
2016-03-04 04:38:38 +13:00
|
|
|
imp = toInt(mw.mdlCharacter.importance(i))
|
2016-02-07 00:34:22 +13:00
|
|
|
|
|
|
|
menus[2 - imp].addAction(a)
|
|
|
|
|
2015-06-27 01:55:34 +12:00
|
|
|
mpr.mapped.connect(self.setPOV)
|
|
|
|
menu.addMenu(self.menuPOV)
|
2016-02-07 00:34:22 +13:00
|
|
|
|
2015-06-27 01:55:34 +12:00
|
|
|
# Status
|
2022-05-17 21:43:22 +12:00
|
|
|
self.menuStatus = QMenu(safeTranslate(qApp, "outlineBasics", "Set Status"), menu)
|
|
|
|
# a = QAction(QIcon.fromTheme("dialog-no"), safeTranslate(qApp, "outlineBasics", "None"), self.menuStatus)
|
2016-02-07 00:34:22 +13:00
|
|
|
# a.triggered.connect(lambda: self.setStatus(""))
|
|
|
|
# self.menuStatus.addAction(a)
|
|
|
|
# self.menuStatus.addSeparator()
|
|
|
|
|
2015-06-27 01:55:34 +12:00
|
|
|
mpr = QSignalMapper(self.menuStatus)
|
|
|
|
for i in range(mw.mdlStatus.rowCount()):
|
|
|
|
a = QAction(mw.mdlStatus.item(i, 0).text(), self.menuStatus)
|
|
|
|
a.triggered.connect(mpr.map)
|
|
|
|
mpr.setMapping(a, i)
|
|
|
|
self.menuStatus.addAction(a)
|
|
|
|
mpr.mapped.connect(self.setStatus)
|
|
|
|
menu.addMenu(self.menuStatus)
|
2016-02-07 00:34:22 +13:00
|
|
|
|
2015-06-27 01:55:34 +12:00
|
|
|
# Labels
|
2022-05-17 21:43:22 +12:00
|
|
|
self.menuLabel = QMenu(safeTranslate(qApp, "outlineBasics", "Set Label"), menu)
|
2015-06-27 01:55:34 +12:00
|
|
|
mpr = QSignalMapper(self.menuLabel)
|
|
|
|
for i in range(mw.mdlLabels.rowCount()):
|
|
|
|
a = QAction(mw.mdlLabels.item(i, 0).icon(),
|
2016-02-07 00:34:22 +13:00
|
|
|
mw.mdlLabels.item(i, 0).text(),
|
2015-06-27 01:55:34 +12:00
|
|
|
self.menuLabel)
|
|
|
|
a.triggered.connect(mpr.map)
|
|
|
|
mpr.setMapping(a, i)
|
|
|
|
self.menuLabel.addAction(a)
|
|
|
|
mpr.mapped.connect(self.setLabel)
|
|
|
|
menu.addMenu(self.menuLabel)
|
2016-02-07 00:34:22 +13:00
|
|
|
|
2017-10-24 00:45:08 +13:00
|
|
|
menu.addSeparator()
|
|
|
|
|
|
|
|
# Custom icons
|
2017-11-30 11:16:01 +13:00
|
|
|
if self.menuCustomIcons:
|
|
|
|
menu.addMenu(self.menuCustomIcons)
|
|
|
|
else:
|
2022-05-17 21:43:22 +12:00
|
|
|
self.menuCustomIcons = QMenu(safeTranslate(qApp, "outlineBasics", "Set Custom Icon"), menu)
|
|
|
|
a = QAction(safeTranslate(qApp, "outlineBasics", "Restore to default"), self.menuCustomIcons)
|
2017-11-30 11:16:01 +13:00
|
|
|
a.triggered.connect(lambda: self.setCustomIcon(""))
|
|
|
|
self.menuCustomIcons.addAction(a)
|
|
|
|
self.menuCustomIcons.addSeparator()
|
|
|
|
|
|
|
|
txt = QLineEdit()
|
|
|
|
txt.textChanged.connect(self.filterLstIcons)
|
|
|
|
txt.setPlaceholderText("Filter icons")
|
2019-02-24 09:21:31 +13:00
|
|
|
txt.setStyleSheet("QLineEdit { background: transparent; border: none; }")
|
2017-11-30 11:16:01 +13:00
|
|
|
act = QWidgetAction(self.menuCustomIcons)
|
|
|
|
act.setDefaultWidget(txt)
|
|
|
|
self.menuCustomIcons.addAction(act)
|
|
|
|
|
|
|
|
self.lstIcons = QListWidget()
|
|
|
|
for i in customIcons():
|
|
|
|
item = QListWidgetItem()
|
|
|
|
item.setIcon(QIcon.fromTheme(i))
|
|
|
|
item.setData(Qt.UserRole, i)
|
|
|
|
item.setToolTip(i)
|
|
|
|
self.lstIcons.addItem(item)
|
|
|
|
self.lstIcons.itemClicked.connect(self.setCustomIconFromItem)
|
|
|
|
self.lstIcons.setViewMode(self.lstIcons.IconMode)
|
|
|
|
self.lstIcons.setUniformItemSizes(True)
|
|
|
|
self.lstIcons.setResizeMode(self.lstIcons.Adjust)
|
|
|
|
self.lstIcons.setMovement(self.lstIcons.Static)
|
|
|
|
self.lstIcons.setStyleSheet("background: transparent; background: none;")
|
|
|
|
self.filterLstIcons("")
|
|
|
|
act = QWidgetAction(self.menuCustomIcons)
|
|
|
|
act.setDefaultWidget(self.lstIcons)
|
|
|
|
self.menuCustomIcons.addAction(act)
|
|
|
|
|
|
|
|
menu.addMenu(self.menuCustomIcons)
|
2017-10-24 00:45:08 +13:00
|
|
|
|
|
|
|
# Disabling stuff
|
2017-11-30 11:16:01 +13:00
|
|
|
if not clipboard.mimeData().hasFormat("application/xml"):
|
2015-06-27 01:55:34 +12:00
|
|
|
self.actPaste.setEnabled(False)
|
2016-02-07 00:34:22 +13:00
|
|
|
|
2015-06-27 01:55:34 +12:00
|
|
|
if len(sel) == 0:
|
|
|
|
self.actCopy.setEnabled(False)
|
|
|
|
self.actCut.setEnabled(False)
|
2017-11-06 01:49:31 +13:00
|
|
|
self.actRename.setEnabled(False)
|
2015-06-27 01:55:34 +12:00
|
|
|
self.actDelete.setEnabled(False)
|
|
|
|
self.menuPOV.setEnabled(False)
|
|
|
|
self.menuStatus.setEnabled(False)
|
|
|
|
self.menuLabel.setEnabled(False)
|
2017-10-24 00:45:08 +13:00
|
|
|
self.menuCustomIcons.setEnabled(False)
|
2016-02-07 00:34:22 +13:00
|
|
|
|
2017-11-06 01:49:31 +13:00
|
|
|
if len(sel) > 1:
|
|
|
|
self.actRename.setEnabled(False)
|
|
|
|
|
2015-06-27 01:55:34 +12:00
|
|
|
return menu
|
2016-02-07 00:34:22 +13:00
|
|
|
|
2017-10-15 08:39:16 +13:00
|
|
|
def openItem(self):
|
2017-11-08 00:02:02 +13:00
|
|
|
#idx = self.currentIndex()
|
|
|
|
idx = self._indexesToOpen[0]
|
2017-10-15 08:39:16 +13:00
|
|
|
from manuskript.functions import MW
|
|
|
|
MW.openIndex(idx)
|
2017-10-30 21:26:46 +13:00
|
|
|
|
2017-11-08 00:02:02 +13:00
|
|
|
def openItemsInNewTabs(self):
|
|
|
|
from manuskript.functions import MW
|
|
|
|
MW.openIndexes(self._indexesToOpen)
|
|
|
|
|
2017-11-06 01:49:31 +13:00
|
|
|
def rename(self):
|
|
|
|
if len(self.getSelection()) == 1:
|
|
|
|
index = self.currentIndex()
|
|
|
|
self.edit(index)
|
|
|
|
elif len(self.getSelection()) > 1:
|
|
|
|
# FIXME: add smart rename
|
|
|
|
pass
|
|
|
|
|
2015-06-10 07:29:17 +12:00
|
|
|
def addFolder(self):
|
|
|
|
self.addItem("folder")
|
2016-02-07 00:34:22 +13:00
|
|
|
|
2015-06-16 01:46:31 +12:00
|
|
|
def addText(self):
|
|
|
|
self.addItem("text")
|
2016-02-07 00:34:22 +13:00
|
|
|
|
2015-06-25 01:43:35 +12:00
|
|
|
def addItem(self, _type="folder"):
|
2015-06-10 07:29:17 +12:00
|
|
|
if len(self.selectedIndexes()) == 0:
|
|
|
|
parent = self.rootIndex()
|
|
|
|
else:
|
|
|
|
parent = self.currentIndex()
|
2016-02-07 00:34:22 +13:00
|
|
|
|
2015-06-25 01:43:35 +12:00
|
|
|
if _type == "text":
|
2015-06-25 20:01:28 +12:00
|
|
|
_type = settings.defaultTextType
|
2016-02-07 00:34:22 +13:00
|
|
|
|
2022-05-17 21:43:22 +12:00
|
|
|
item = outlineItem(title=safeTranslate(qApp, "outlineBasics", "New"), _type=_type)
|
2015-06-10 07:29:17 +12:00
|
|
|
self.model().appendItem(item, parent)
|
2016-02-07 00:34:22 +13:00
|
|
|
|
2015-06-10 01:52:20 +12:00
|
|
|
def copy(self):
|
|
|
|
mimeData = self.model().mimeData(self.selectionModel().selectedIndexes())
|
|
|
|
qApp.clipboard().setMimeData(mimeData)
|
2016-02-07 00:34:22 +13:00
|
|
|
|
2017-11-25 06:44:31 +13:00
|
|
|
def paste(self, mimeData=None):
|
|
|
|
"""
|
|
|
|
Paste item from mimeData to selected item. If mimeData is not given,
|
|
|
|
it is taken from clipboard. If not item selected, paste into root.
|
|
|
|
"""
|
2015-06-10 01:52:20 +12:00
|
|
|
index = self.currentIndex()
|
|
|
|
if len(self.getSelection()) == 0:
|
2015-06-10 07:29:17 +12:00
|
|
|
index = self.rootIndex()
|
2017-11-25 06:44:31 +13:00
|
|
|
|
2017-11-30 11:16:01 +13:00
|
|
|
if not mimeData:
|
2017-11-25 06:44:31 +13:00
|
|
|
mimeData = qApp.clipboard().mimeData()
|
|
|
|
|
|
|
|
self.model().dropMimeData(mimeData, Qt.CopyAction, -1, 0, index)
|
2016-02-07 00:34:22 +13:00
|
|
|
|
2015-06-10 01:52:20 +12:00
|
|
|
def cut(self):
|
|
|
|
self.copy()
|
|
|
|
self.delete()
|
2016-02-07 00:34:22 +13:00
|
|
|
|
2015-06-10 01:52:20 +12:00
|
|
|
def delete(self):
|
2017-11-11 04:26:23 +13:00
|
|
|
"""
|
|
|
|
Shows a warning, and then deletes currently selected indexes.
|
|
|
|
"""
|
|
|
|
if not settings.dontShowDeleteWarning:
|
2021-10-24 03:15:58 +13:00
|
|
|
msgInfo = list()
|
|
|
|
msgInfo.append("<p><b>")
|
2022-05-17 21:43:22 +12:00
|
|
|
msgInfo.append(safeTranslate(qApp, "outlineBasics", "You're about to delete {} item(s).").format(
|
2021-10-24 03:15:58 +13:00
|
|
|
len(self.getSelection())
|
|
|
|
))
|
|
|
|
|
|
|
|
msgInfo.append("</b></p><ul>")
|
|
|
|
for i in self.getSelection():
|
|
|
|
title = self.model().data(i.sibling(i.row(), Outline.title))
|
|
|
|
msgInfo.append("<li>{}</li>".format(str(title)))
|
|
|
|
|
|
|
|
msgInfo.append("</ul><p>")
|
2022-05-17 21:43:22 +12:00
|
|
|
msgInfo.append(safeTranslate(qApp, "outlineBasics", "Are you sure?"))
|
2021-10-24 03:15:58 +13:00
|
|
|
msgInfo.append("</p>")
|
|
|
|
|
2017-11-11 04:26:23 +13:00
|
|
|
msg = QMessageBox(QMessageBox.Warning,
|
2022-05-17 21:43:22 +12:00
|
|
|
safeTranslate(qApp, "outlineBasics", "About to remove"),
|
2021-10-24 03:15:58 +13:00
|
|
|
"".join(msgInfo),
|
2017-11-11 04:26:23 +13:00
|
|
|
QMessageBox.Yes | QMessageBox.Cancel)
|
|
|
|
|
|
|
|
chk = QCheckBox("&Don't show this warning in the future.")
|
|
|
|
msg.setCheckBox(chk)
|
|
|
|
ret = msg.exec()
|
|
|
|
|
|
|
|
if ret == QMessageBox.Cancel:
|
|
|
|
return
|
|
|
|
|
|
|
|
if chk.isChecked():
|
|
|
|
settings.dontShowDeleteWarning = True
|
|
|
|
|
2015-07-03 06:10:25 +12:00
|
|
|
self.model().removeIndexes(self.getSelection())
|
2016-02-07 00:34:22 +13:00
|
|
|
|
2017-11-10 11:01:42 +13:00
|
|
|
def duplicate(self):
|
2017-11-25 06:44:31 +13:00
|
|
|
"""
|
2018-01-07 06:48:40 +13:00
|
|
|
Duplicates item(s), while preserving clipboard content.
|
2017-11-25 06:44:31 +13:00
|
|
|
"""
|
|
|
|
mimeData = self.model().mimeData(self.selectionModel().selectedIndexes())
|
|
|
|
self.paste(mimeData)
|
2017-11-10 11:01:42 +13:00
|
|
|
|
|
|
|
def move(self, delta=1):
|
|
|
|
"""
|
|
|
|
Move selected items up or down.
|
|
|
|
"""
|
|
|
|
|
2018-01-07 06:48:40 +13:00
|
|
|
# we store selected indexes
|
2017-11-10 11:01:42 +13:00
|
|
|
currentID = self.model().ID(self.currentIndex())
|
|
|
|
selIDs = [self.model().ID(i) for i in self.selectedIndexes()]
|
|
|
|
|
|
|
|
# Block signals
|
|
|
|
self.blockSignals(True)
|
|
|
|
self.selectionModel().blockSignals(True)
|
|
|
|
|
|
|
|
# Move each index individually
|
|
|
|
for idx in self.selectedIndexes():
|
|
|
|
self.moveIndex(idx, delta)
|
|
|
|
|
|
|
|
# Done the hardcore way, so inform views
|
|
|
|
self.model().layoutChanged.emit()
|
|
|
|
|
|
|
|
# restore selection
|
|
|
|
selIdx = [self.model().getIndexByID(ID) for ID in selIDs]
|
|
|
|
sm = self.selectionModel()
|
|
|
|
sm.clear()
|
|
|
|
[sm.select(idx, sm.Select) for idx in selIdx]
|
|
|
|
sm.setCurrentIndex(self.model().getIndexByID(currentID), sm.Select)
|
2017-11-11 04:26:23 +13:00
|
|
|
#self.setSmsgBoxelectionModel(sm)
|
2017-11-10 11:01:42 +13:00
|
|
|
|
|
|
|
# Unblock signals
|
|
|
|
self.blockSignals(False)
|
|
|
|
self.selectionModel().blockSignals(False)
|
|
|
|
|
|
|
|
def moveIndex(self, index, delta=1):
|
|
|
|
"""
|
|
|
|
Move the item represented by index. +1 means down, -1 means up.
|
|
|
|
"""
|
|
|
|
|
|
|
|
if not index.isValid():
|
|
|
|
return
|
|
|
|
|
|
|
|
if index.parent().isValid():
|
|
|
|
parentItem = index.parent().internalPointer()
|
|
|
|
else:
|
|
|
|
parentItem = index.model().rootItem
|
|
|
|
|
|
|
|
parentItem.childItems.insert(index.row() + delta,
|
|
|
|
parentItem.childItems.pop(index.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
|
|
|
parentItem.updateWordCount()
|
2017-11-10 11:01:42 +13:00
|
|
|
|
|
|
|
def moveUp(self): self.move(-1)
|
|
|
|
def moveDown(self): self.move(+1)
|
|
|
|
|
2017-11-11 04:26:23 +13:00
|
|
|
def splitDialog(self):
|
|
|
|
"""
|
|
|
|
Opens a dialog to split selected items.
|
|
|
|
|
|
|
|
Call context: if at least one index is selected. Folder or text.
|
|
|
|
"""
|
|
|
|
|
|
|
|
indexes = self.getSelection()
|
|
|
|
if len(indexes) == 0:
|
2017-11-11 05:21:02 +13:00
|
|
|
# No selection, we use parent
|
|
|
|
indexes = [self.rootIndex()]
|
2017-11-11 04:26:23 +13:00
|
|
|
|
|
|
|
splitDialog(self, indexes)
|
|
|
|
|
2017-11-11 05:21:02 +13:00
|
|
|
def merge(self):
|
|
|
|
"""
|
|
|
|
Merges selected items together.
|
|
|
|
|
|
|
|
Call context: Multiple selection, same parent.
|
|
|
|
"""
|
|
|
|
|
|
|
|
# Get selection
|
|
|
|
indexes = self.getSelection()
|
|
|
|
# Get items
|
|
|
|
items = [i.internalPointer() for i in indexes if i.isValid()]
|
|
|
|
# Remove folders
|
|
|
|
items = [i for i in items if not i.isFolder()]
|
|
|
|
|
|
|
|
# Check that we have at least 2 items
|
|
|
|
if len(items) < 2:
|
2022-05-17 21:43:22 +12:00
|
|
|
statusMessage(safeTranslate(qApp, "outlineBasics",
|
2017-12-08 22:01:58 +13:00
|
|
|
"Select at least two items. Folders are ignored."),
|
|
|
|
importance=2)
|
2017-11-11 05:21:02 +13:00
|
|
|
return
|
|
|
|
|
|
|
|
# Check that all share the same parent
|
|
|
|
p = items[0].parent()
|
|
|
|
for i in items:
|
|
|
|
if i.parent() != p:
|
2022-05-17 21:43:22 +12:00
|
|
|
statusMessage(safeTranslate(qApp, "outlineBasics",
|
2017-12-08 22:01:58 +13:00
|
|
|
"All items must be on the same level (share the same parent)."),
|
|
|
|
importance=2)
|
2017-11-11 05:21:02 +13:00
|
|
|
return
|
|
|
|
|
|
|
|
# Sort items by row
|
|
|
|
items = sorted(items, key=lambda i: i.row())
|
|
|
|
|
|
|
|
items[0].mergeWith(items[1:])
|
|
|
|
|
2015-06-10 07:20:26 +12:00
|
|
|
def setPOV(self, POV):
|
|
|
|
for i in self.getSelection():
|
2017-11-16 08:58:12 +13:00
|
|
|
self.model().setData(i.sibling(i.row(), Outline.POV), str(POV))
|
2016-02-07 00:34:22 +13:00
|
|
|
|
2015-06-10 07:20:26 +12:00
|
|
|
def setStatus(self, status):
|
|
|
|
for i in self.getSelection():
|
2017-11-16 08:58:12 +13:00
|
|
|
self.model().setData(i.sibling(i.row(), Outline.status), str(status))
|
2016-02-07 00:34:22 +13:00
|
|
|
|
2015-06-10 07:20:26 +12:00
|
|
|
def setLabel(self, label):
|
|
|
|
for i in self.getSelection():
|
2017-11-16 08:58:12 +13:00
|
|
|
self.model().setData(i.sibling(i.row(), Outline.label), str(label))
|
2017-10-24 00:45:08 +13:00
|
|
|
|
|
|
|
def setCustomIcon(self, customIcon):
|
|
|
|
for i in self.getSelection():
|
|
|
|
item = i.internalPointer()
|
|
|
|
item.setCustomIcon(customIcon)
|
|
|
|
|
|
|
|
def setCustomIconFromItem(self, item):
|
|
|
|
icon = item.data(Qt.UserRole)
|
|
|
|
self.setCustomIcon(icon)
|
|
|
|
self.menu.close()
|
|
|
|
|
|
|
|
def filterLstIcons(self, text):
|
|
|
|
for l in self.lstIcons.findItems("", Qt.MatchContains):
|
|
|
|
l.setHidden(not text in l.data(Qt.UserRole))
|