mirror of
https://github.com/olivierkes/manuskript.git
synced 2024-09-30 01:07:24 +13:00
464 lines
16 KiB
Python
464 lines
16 KiB
Python
#!/usr/bin/env python
|
|
# --!-- coding: utf8 --!--
|
|
from PyQt5.QtCore import Qt, QSignalMapper, QSize
|
|
from PyQt5.QtGui import QIcon, QCursor
|
|
from PyQt5.QtWidgets import QAbstractItemView, qApp, QMenu, QAction, \
|
|
QListWidget, QWidgetAction, QListWidgetItem, \
|
|
QLineEdit, QInputDialog, QMessageBox, QCheckBox
|
|
|
|
from manuskript import settings
|
|
from manuskript.enums import Outline
|
|
from manuskript.functions import mainWindow, statusMessage
|
|
from manuskript.functions import toInt, customIcons
|
|
from manuskript.models import outlineItem
|
|
from manuskript.ui.tools.splitDialog import splitDialog
|
|
|
|
|
|
class outlineBasics(QAbstractItemView):
|
|
def __init__(self, parent=None):
|
|
self._indexesToOpen = None
|
|
self.menuCustomIcons = None
|
|
|
|
def getSelection(self):
|
|
sel = []
|
|
for i in self.selectedIndexes():
|
|
if i.column() != 0:
|
|
continue
|
|
if not i in sel:
|
|
sel.append(i)
|
|
return sel
|
|
|
|
def mouseReleaseEvent(self, event):
|
|
if event.button() == Qt.RightButton:
|
|
self.menu = self.makePopupMenu()
|
|
self.menu.popup(event.globalPos())
|
|
# 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.
|
|
|
|
def makePopupMenu(self):
|
|
index = self.currentIndex()
|
|
sel = self.getSelection()
|
|
clipboard = qApp.clipboard()
|
|
|
|
menu = QMenu(self)
|
|
|
|
# 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:
|
|
title = qApp.translate("outlineBasics", "Root")
|
|
|
|
if len(title) > 25:
|
|
title = title[:25] + "…"
|
|
|
|
# Open Item action
|
|
self.actOpen = QAction(QIcon.fromTheme("go-right"),
|
|
qApp.translate("outlineBasics", "Open {}".format(title)),
|
|
menu)
|
|
self.actOpen.triggered.connect(self.openItem)
|
|
menu.addAction(self.actOpen)
|
|
|
|
# Open item(s) in new tab
|
|
if mouseIndex in sel and len(sel) > 1:
|
|
actionTitle = qApp.translate("outlineBasics", "Open {} items in new tabs").format(len(sel))
|
|
self._indexesToOpen = sel
|
|
else:
|
|
actionTitle = qApp.translate("outlineBasics", "Open {} in a new tab").format(title)
|
|
self._indexesToOpen = [mouseIndex]
|
|
|
|
self.actNewTab = QAction(QIcon.fromTheme("go-right"), actionTitle, menu)
|
|
self.actNewTab.triggered.connect(self.openItemsInNewTabs)
|
|
menu.addAction(self.actNewTab)
|
|
|
|
menu.addSeparator()
|
|
|
|
# Add text / folder
|
|
self.actAddFolder = QAction(QIcon.fromTheme("folder-new"),
|
|
qApp.translate("outlineBasics", "New &Folder"),
|
|
menu)
|
|
self.actAddFolder.triggered.connect(self.addFolder)
|
|
menu.addAction(self.actAddFolder)
|
|
|
|
self.actAddText = QAction(QIcon.fromTheme("document-new"),
|
|
qApp.translate("outlineBasics", "New &Text"),
|
|
menu)
|
|
self.actAddText.triggered.connect(self.addText)
|
|
menu.addAction(self.actAddText)
|
|
|
|
menu.addSeparator()
|
|
|
|
# Copy, cut, paste, duplicate
|
|
self.actCut = QAction(QIcon.fromTheme("edit-cut"),
|
|
qApp.translate("outlineBasics", "C&ut"), menu)
|
|
self.actCut.triggered.connect(self.cut)
|
|
menu.addAction(self.actCut)
|
|
|
|
self.actCopy = QAction(QIcon.fromTheme("edit-copy"),
|
|
qApp.translate("outlineBasics", "&Copy"), menu)
|
|
self.actCopy.triggered.connect(self.copy)
|
|
menu.addAction(self.actCopy)
|
|
|
|
self.actPaste = QAction(QIcon.fromTheme("edit-paste"),
|
|
qApp.translate("outlineBasics", "&Paste"), menu)
|
|
self.actPaste.triggered.connect(self.paste)
|
|
menu.addAction(self.actPaste)
|
|
|
|
# Rename / duplicate / remove items
|
|
self.actDelete = QAction(QIcon.fromTheme("edit-delete"),
|
|
qApp.translate("outlineBasics", "&Delete"),
|
|
menu)
|
|
self.actDelete.triggered.connect(self.delete)
|
|
menu.addAction(self.actDelete)
|
|
|
|
self.actRename = QAction(QIcon.fromTheme("edit-rename"),
|
|
qApp.translate("outlineBasics", "&Rename"),
|
|
menu)
|
|
self.actRename.triggered.connect(self.rename)
|
|
menu.addAction(self.actRename)
|
|
|
|
menu.addSeparator()
|
|
|
|
# POV
|
|
self.menuPOV = QMenu(qApp.translate("outlineBasics", "Set POV"), menu)
|
|
mw = mainWindow()
|
|
a = QAction(QIcon.fromTheme("dialog-no"), qApp.translate("outlineBasics", "None"), self.menuPOV)
|
|
a.triggered.connect(lambda: self.setPOV(""))
|
|
self.menuPOV.addAction(a)
|
|
self.menuPOV.addSeparator()
|
|
|
|
menus = []
|
|
for i in [qApp.translate("outlineBasics", "Main"),
|
|
qApp.translate("outlineBasics", "Secondary"),
|
|
qApp.translate("outlineBasics", "Minor")]:
|
|
m = QMenu(i, self.menuPOV)
|
|
menus.append(m)
|
|
self.menuPOV.addMenu(m)
|
|
|
|
mpr = QSignalMapper(self.menuPOV)
|
|
for i in range(mw.mdlCharacter.rowCount()):
|
|
a = QAction(mw.mdlCharacter.icon(i), mw.mdlCharacter.name(i), self.menuPOV)
|
|
a.triggered.connect(mpr.map)
|
|
mpr.setMapping(a, int(mw.mdlCharacter.ID(i)))
|
|
|
|
imp = toInt(mw.mdlCharacter.importance(i))
|
|
|
|
menus[2 - imp].addAction(a)
|
|
|
|
mpr.mapped.connect(self.setPOV)
|
|
menu.addMenu(self.menuPOV)
|
|
|
|
# Status
|
|
self.menuStatus = QMenu(qApp.translate("outlineBasics", "Set Status"), menu)
|
|
# a = QAction(QIcon.fromTheme("dialog-no"), qApp.translate("outlineBasics", "None"), self.menuStatus)
|
|
# a.triggered.connect(lambda: self.setStatus(""))
|
|
# self.menuStatus.addAction(a)
|
|
# self.menuStatus.addSeparator()
|
|
|
|
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)
|
|
|
|
# Labels
|
|
self.menuLabel = QMenu(qApp.translate("outlineBasics", "Set Label"), menu)
|
|
mpr = QSignalMapper(self.menuLabel)
|
|
for i in range(mw.mdlLabels.rowCount()):
|
|
a = QAction(mw.mdlLabels.item(i, 0).icon(),
|
|
mw.mdlLabels.item(i, 0).text(),
|
|
self.menuLabel)
|
|
a.triggered.connect(mpr.map)
|
|
mpr.setMapping(a, i)
|
|
self.menuLabel.addAction(a)
|
|
mpr.mapped.connect(self.setLabel)
|
|
menu.addMenu(self.menuLabel)
|
|
|
|
menu.addSeparator()
|
|
|
|
# Custom icons
|
|
if self.menuCustomIcons:
|
|
menu.addMenu(self.menuCustomIcons)
|
|
else:
|
|
self.menuCustomIcons = QMenu(qApp.translate("outlineBasics", "Set Custom Icon"), menu)
|
|
a = QAction(qApp.translate("outlineBasics", "Restore to default"), self.menuCustomIcons)
|
|
a.triggered.connect(lambda: self.setCustomIcon(""))
|
|
self.menuCustomIcons.addAction(a)
|
|
self.menuCustomIcons.addSeparator()
|
|
|
|
txt = QLineEdit()
|
|
txt.textChanged.connect(self.filterLstIcons)
|
|
txt.setPlaceholderText("Filter icons")
|
|
txt.setStyleSheet("background: transparent; border: none;")
|
|
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)
|
|
|
|
# Disabling stuff
|
|
if not clipboard.mimeData().hasFormat("application/xml"):
|
|
self.actPaste.setEnabled(False)
|
|
|
|
if len(sel) == 0:
|
|
self.actCopy.setEnabled(False)
|
|
self.actCut.setEnabled(False)
|
|
self.actRename.setEnabled(False)
|
|
self.actDelete.setEnabled(False)
|
|
self.menuPOV.setEnabled(False)
|
|
self.menuStatus.setEnabled(False)
|
|
self.menuLabel.setEnabled(False)
|
|
self.menuCustomIcons.setEnabled(False)
|
|
|
|
if len(sel) > 1:
|
|
self.actRename.setEnabled(False)
|
|
|
|
return menu
|
|
|
|
def openItem(self):
|
|
#idx = self.currentIndex()
|
|
idx = self._indexesToOpen[0]
|
|
from manuskript.functions import MW
|
|
MW.openIndex(idx)
|
|
|
|
def openItemsInNewTabs(self):
|
|
from manuskript.functions import MW
|
|
MW.openIndexes(self._indexesToOpen)
|
|
|
|
def rename(self):
|
|
if len(self.getSelection()) == 1:
|
|
index = self.currentIndex()
|
|
self.edit(index)
|
|
elif len(self.getSelection()) > 1:
|
|
# FIXME: add smart rename
|
|
pass
|
|
|
|
def addFolder(self):
|
|
self.addItem("folder")
|
|
|
|
def addText(self):
|
|
self.addItem("text")
|
|
|
|
def addItem(self, _type="folder"):
|
|
if len(self.selectedIndexes()) == 0:
|
|
parent = self.rootIndex()
|
|
else:
|
|
parent = self.currentIndex()
|
|
|
|
if _type == "text":
|
|
_type = settings.defaultTextType
|
|
|
|
item = outlineItem(title=qApp.translate("outlineBasics", "New"), _type=_type)
|
|
self.model().appendItem(item, parent)
|
|
|
|
def copy(self):
|
|
mimeData = self.model().mimeData(self.selectionModel().selectedIndexes())
|
|
qApp.clipboard().setMimeData(mimeData)
|
|
|
|
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.
|
|
"""
|
|
index = self.currentIndex()
|
|
if len(self.getSelection()) == 0:
|
|
index = self.rootIndex()
|
|
|
|
if not mimeData:
|
|
mimeData = qApp.clipboard().mimeData()
|
|
|
|
self.model().dropMimeData(mimeData, Qt.CopyAction, -1, 0, index)
|
|
|
|
def cut(self):
|
|
self.copy()
|
|
self.delete()
|
|
|
|
def delete(self):
|
|
"""
|
|
Shows a warning, and then deletes currently selected indexes.
|
|
"""
|
|
if not settings.dontShowDeleteWarning:
|
|
msg = QMessageBox(QMessageBox.Warning,
|
|
qApp.translate("outlineBasics", "About to remove"),
|
|
qApp.translate("outlineBasics",
|
|
"<p><b>You're about to delete {} item(s).</b></p><p>Are you sure?</p>"
|
|
).format(len(self.getSelection())),
|
|
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
|
|
|
|
self.model().removeIndexes(self.getSelection())
|
|
|
|
def duplicate(self):
|
|
"""
|
|
Duplicates item(s), while preserving clipbaord content.
|
|
"""
|
|
mimeData = self.model().mimeData(self.selectionModel().selectedIndexes())
|
|
self.paste(mimeData)
|
|
|
|
def move(self, delta=1):
|
|
"""
|
|
Move selected items up or down.
|
|
"""
|
|
|
|
# we store selected indexesret
|
|
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)
|
|
#self.setSmsgBoxelectionModel(sm)
|
|
|
|
# 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()))
|
|
parentItem.updateWordCount(emit=False)
|
|
|
|
def moveUp(self): self.move(-1)
|
|
def moveDown(self): self.move(+1)
|
|
|
|
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:
|
|
# No selection, we use parent
|
|
indexes = [self.rootIndex()]
|
|
|
|
splitDialog(self, indexes)
|
|
|
|
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:
|
|
statusMessage(qApp.translate("outlineBasics",
|
|
"Select at least two items. Folders are ignored."),
|
|
importance=2)
|
|
return
|
|
|
|
# Check that all share the same parent
|
|
p = items[0].parent()
|
|
for i in items:
|
|
if i.parent() != p:
|
|
statusMessage(qApp.translate("outlineBasics",
|
|
"All items must be on the same level (share the same parent)."),
|
|
importance=2)
|
|
return
|
|
|
|
# Sort items by row
|
|
items = sorted(items, key=lambda i: i.row())
|
|
|
|
items[0].mergeWith(items[1:])
|
|
|
|
def setPOV(self, POV):
|
|
for i in self.getSelection():
|
|
self.model().setData(i.sibling(i.row(), Outline.POV), str(POV))
|
|
|
|
def setStatus(self, status):
|
|
for i in self.getSelection():
|
|
self.model().setData(i.sibling(i.row(), Outline.status), str(status))
|
|
|
|
def setLabel(self, label):
|
|
for i in self.getSelection():
|
|
self.model().setData(i.sibling(i.row(), Outline.label), str(label))
|
|
|
|
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))
|