From bc70501373c7ce6b62f2b23e92da8cd6b6865512 Mon Sep 17 00:00:00 2001
From: Olivier Keshavjee
Date: Thu, 9 Nov 2017 20:30:54 +0100
Subject: [PATCH 1/5] Fixes a bug when entering non-digit values for item's
goal in metadata
---
manuskript/functions.py | 9 ++++++---
manuskript/ui/views/propertiesView.py | 2 ++
manuskript/ui/views/propertiesView_ui.py | 2 +-
3 files changed, 9 insertions(+), 4 deletions(-)
diff --git a/manuskript/functions.py b/manuskript/functions.py
index b7873ad7..04d57b7a 100644
--- a/manuskript/functions.py
+++ b/manuskript/functions.py
@@ -24,9 +24,12 @@ def wordCount(text):
def toInt(text):
if text:
- return int(text)
- else:
- return 0
+ try:
+ return int(text)
+ except ValueError:
+ pass
+
+ return 0
def toFloat(text):
diff --git a/manuskript/ui/views/propertiesView.py b/manuskript/ui/views/propertiesView.py
index e0fa7191..30857ef8 100644
--- a/manuskript/ui/views/propertiesView.py
+++ b/manuskript/ui/views/propertiesView.py
@@ -1,6 +1,7 @@
#!/usr/bin/env python
# --!-- coding: utf8 --!--
from PyQt5.QtWidgets import QWidget
+from PyQt5.QtGui import QIntValidator
from manuskript.enums import Outline
from manuskript.ui.views.propertiesView_ui import Ui_propertiesView
@@ -19,6 +20,7 @@ class propertiesView(QWidget, Ui_propertiesView):
self.chkCompile.setModel(mdlOutline)
self.txtTitle.setModel(mdlOutline)
self.txtGoal.setModel(mdlOutline)
+ self.txtGoal.setValidator(QIntValidator(0, 9999999))
def getIndexes(self, sourceView):
"""Returns a list of indexes from list of QItemSelectionRange"""
diff --git a/manuskript/ui/views/propertiesView_ui.py b/manuskript/ui/views/propertiesView_ui.py
index 5b8a7e11..e4846fae 100644
--- a/manuskript/ui/views/propertiesView_ui.py
+++ b/manuskript/ui/views/propertiesView_ui.py
@@ -2,7 +2,7 @@
# Form implementation generated from reading ui file 'manuskript/ui/views/propertiesView_ui.ui'
#
-# Created by: PyQt5 UI code generator 5.4.2
+# Created by: PyQt5 UI code generator 5.9
#
# WARNING! All changes made in this file will be lost!
From 3b17c4e2b4a44545b4dfdac3456e92047f15fa9b Mon Sep 17 00:00:00 2001
From: Olivier Keshavjee
Date: Thu, 9 Nov 2017 23:01:42 +0100
Subject: [PATCH 2/5] Adds: Menu Documents. Edit operations (copy, cut, paste,
duplicate, remove), and Move up and down.
---
icons/NumixMsk/128x128/places/folder-copy.svg | 157 ++++++++
icons/NumixMsk/16x16/actions/merge.svg | 6 +
icons/NumixMsk/16x16/actions/split.svg | 7 +
icons/NumixMsk/16x16/places/folder-copy.svg | 117 ++++++
icons/NumixMsk/22x22/actions/merge.svg | 6 +
icons/NumixMsk/22x22/actions/split.svg | 7 +
icons/NumixMsk/22x22/places/folder-copy.svg | 180 +++++++++
icons/NumixMsk/24x24/actions/merge.svg | 6 +
icons/NumixMsk/24x24/actions/split.svg | 7 +
icons/NumixMsk/24x24/places/folder-copy.svg | 180 +++++++++
icons/NumixMsk/256x256/places/folder-copy.svg | 162 ++++++++
icons/NumixMsk/32x32/actions/merge.svg | 6 +
icons/NumixMsk/32x32/actions/split.svg | 7 +
icons/NumixMsk/32x32/places/folder-copy.svg | 167 +++++++++
icons/NumixMsk/48x48/actions/merge.svg | 6 +
icons/NumixMsk/48x48/actions/split.svg | 7 +
icons/NumixMsk/48x48/places/folder-copy.svg | 352 ++++++++++++++++++
icons/NumixMsk/64x64/actions/merge.svg | 6 +
icons/NumixMsk/64x64/actions/split.svg | 7 +
icons/NumixMsk/64x64/places/folder-copy.svg | 193 ++++++++++
icons/NumixMsk/96x96/places/folder-copy.svg | 350 +++++++++++++++++
manuskript/mainWindow.py | 92 ++++-
manuskript/models/outlineModel.py | 53 ++-
manuskript/ui/editors/editorWidget.py | 36 +-
manuskript/ui/editors/mainEditor.py | 19 +-
manuskript/ui/mainWindow.py | 76 ++++
manuskript/ui/mainWindow.ui | 132 ++++++-
manuskript/ui/views/outlineBasics.py | 56 +++
28 files changed, 2374 insertions(+), 26 deletions(-)
create mode 100644 icons/NumixMsk/128x128/places/folder-copy.svg
create mode 100644 icons/NumixMsk/16x16/actions/merge.svg
create mode 100644 icons/NumixMsk/16x16/actions/split.svg
create mode 100644 icons/NumixMsk/16x16/places/folder-copy.svg
create mode 100644 icons/NumixMsk/22x22/actions/merge.svg
create mode 100644 icons/NumixMsk/22x22/actions/split.svg
create mode 100644 icons/NumixMsk/22x22/places/folder-copy.svg
create mode 100644 icons/NumixMsk/24x24/actions/merge.svg
create mode 100644 icons/NumixMsk/24x24/actions/split.svg
create mode 100644 icons/NumixMsk/24x24/places/folder-copy.svg
create mode 100644 icons/NumixMsk/256x256/places/folder-copy.svg
create mode 100644 icons/NumixMsk/32x32/actions/merge.svg
create mode 100644 icons/NumixMsk/32x32/actions/split.svg
create mode 100644 icons/NumixMsk/32x32/places/folder-copy.svg
create mode 100644 icons/NumixMsk/48x48/actions/merge.svg
create mode 100644 icons/NumixMsk/48x48/actions/split.svg
create mode 100644 icons/NumixMsk/48x48/places/folder-copy.svg
create mode 100644 icons/NumixMsk/64x64/actions/merge.svg
create mode 100644 icons/NumixMsk/64x64/actions/split.svg
create mode 100644 icons/NumixMsk/64x64/places/folder-copy.svg
create mode 100644 icons/NumixMsk/96x96/places/folder-copy.svg
diff --git a/icons/NumixMsk/128x128/places/folder-copy.svg b/icons/NumixMsk/128x128/places/folder-copy.svg
new file mode 100644
index 00000000..6474bf44
--- /dev/null
+++ b/icons/NumixMsk/128x128/places/folder-copy.svg
@@ -0,0 +1,157 @@
+
+
+
+
diff --git a/icons/NumixMsk/16x16/actions/merge.svg b/icons/NumixMsk/16x16/actions/merge.svg
new file mode 100644
index 00000000..34bf799a
--- /dev/null
+++ b/icons/NumixMsk/16x16/actions/merge.svg
@@ -0,0 +1,6 @@
+
diff --git a/icons/NumixMsk/16x16/actions/split.svg b/icons/NumixMsk/16x16/actions/split.svg
new file mode 100644
index 00000000..e4952319
--- /dev/null
+++ b/icons/NumixMsk/16x16/actions/split.svg
@@ -0,0 +1,7 @@
+
diff --git a/icons/NumixMsk/16x16/places/folder-copy.svg b/icons/NumixMsk/16x16/places/folder-copy.svg
new file mode 100644
index 00000000..b4008db1
--- /dev/null
+++ b/icons/NumixMsk/16x16/places/folder-copy.svg
@@ -0,0 +1,117 @@
+
+
+
+
diff --git a/icons/NumixMsk/22x22/actions/merge.svg b/icons/NumixMsk/22x22/actions/merge.svg
new file mode 100644
index 00000000..e3996fd9
--- /dev/null
+++ b/icons/NumixMsk/22x22/actions/merge.svg
@@ -0,0 +1,6 @@
+
diff --git a/icons/NumixMsk/22x22/actions/split.svg b/icons/NumixMsk/22x22/actions/split.svg
new file mode 100644
index 00000000..f0040158
--- /dev/null
+++ b/icons/NumixMsk/22x22/actions/split.svg
@@ -0,0 +1,7 @@
+
diff --git a/icons/NumixMsk/22x22/places/folder-copy.svg b/icons/NumixMsk/22x22/places/folder-copy.svg
new file mode 100644
index 00000000..09e1ea9b
--- /dev/null
+++ b/icons/NumixMsk/22x22/places/folder-copy.svg
@@ -0,0 +1,180 @@
+
+
+
+
diff --git a/icons/NumixMsk/24x24/actions/merge.svg b/icons/NumixMsk/24x24/actions/merge.svg
new file mode 100644
index 00000000..96f0ca7b
--- /dev/null
+++ b/icons/NumixMsk/24x24/actions/merge.svg
@@ -0,0 +1,6 @@
+
diff --git a/icons/NumixMsk/24x24/actions/split.svg b/icons/NumixMsk/24x24/actions/split.svg
new file mode 100644
index 00000000..c2478721
--- /dev/null
+++ b/icons/NumixMsk/24x24/actions/split.svg
@@ -0,0 +1,7 @@
+
diff --git a/icons/NumixMsk/24x24/places/folder-copy.svg b/icons/NumixMsk/24x24/places/folder-copy.svg
new file mode 100644
index 00000000..d86840b7
--- /dev/null
+++ b/icons/NumixMsk/24x24/places/folder-copy.svg
@@ -0,0 +1,180 @@
+
+
+
+
diff --git a/icons/NumixMsk/256x256/places/folder-copy.svg b/icons/NumixMsk/256x256/places/folder-copy.svg
new file mode 100644
index 00000000..483a5d8a
--- /dev/null
+++ b/icons/NumixMsk/256x256/places/folder-copy.svg
@@ -0,0 +1,162 @@
+
+
+
+
diff --git a/icons/NumixMsk/32x32/actions/merge.svg b/icons/NumixMsk/32x32/actions/merge.svg
new file mode 100644
index 00000000..dbd0db48
--- /dev/null
+++ b/icons/NumixMsk/32x32/actions/merge.svg
@@ -0,0 +1,6 @@
+
diff --git a/icons/NumixMsk/32x32/actions/split.svg b/icons/NumixMsk/32x32/actions/split.svg
new file mode 100644
index 00000000..04d6458a
--- /dev/null
+++ b/icons/NumixMsk/32x32/actions/split.svg
@@ -0,0 +1,7 @@
+
diff --git a/icons/NumixMsk/32x32/places/folder-copy.svg b/icons/NumixMsk/32x32/places/folder-copy.svg
new file mode 100644
index 00000000..d9f74d97
--- /dev/null
+++ b/icons/NumixMsk/32x32/places/folder-copy.svg
@@ -0,0 +1,167 @@
+
+
+
+
diff --git a/icons/NumixMsk/48x48/actions/merge.svg b/icons/NumixMsk/48x48/actions/merge.svg
new file mode 100644
index 00000000..d8eb78a9
--- /dev/null
+++ b/icons/NumixMsk/48x48/actions/merge.svg
@@ -0,0 +1,6 @@
+
diff --git a/icons/NumixMsk/48x48/actions/split.svg b/icons/NumixMsk/48x48/actions/split.svg
new file mode 100644
index 00000000..67428247
--- /dev/null
+++ b/icons/NumixMsk/48x48/actions/split.svg
@@ -0,0 +1,7 @@
+
diff --git a/icons/NumixMsk/48x48/places/folder-copy.svg b/icons/NumixMsk/48x48/places/folder-copy.svg
new file mode 100644
index 00000000..e943f0cb
--- /dev/null
+++ b/icons/NumixMsk/48x48/places/folder-copy.svg
@@ -0,0 +1,352 @@
+
+
+
+
diff --git a/icons/NumixMsk/64x64/actions/merge.svg b/icons/NumixMsk/64x64/actions/merge.svg
new file mode 100644
index 00000000..c81beca6
--- /dev/null
+++ b/icons/NumixMsk/64x64/actions/merge.svg
@@ -0,0 +1,6 @@
+
diff --git a/icons/NumixMsk/64x64/actions/split.svg b/icons/NumixMsk/64x64/actions/split.svg
new file mode 100644
index 00000000..af0d3d02
--- /dev/null
+++ b/icons/NumixMsk/64x64/actions/split.svg
@@ -0,0 +1,7 @@
+
diff --git a/icons/NumixMsk/64x64/places/folder-copy.svg b/icons/NumixMsk/64x64/places/folder-copy.svg
new file mode 100644
index 00000000..31c3ab64
--- /dev/null
+++ b/icons/NumixMsk/64x64/places/folder-copy.svg
@@ -0,0 +1,193 @@
+
+
+
+
diff --git a/icons/NumixMsk/96x96/places/folder-copy.svg b/icons/NumixMsk/96x96/places/folder-copy.svg
new file mode 100644
index 00000000..6fce318f
--- /dev/null
+++ b/icons/NumixMsk/96x96/places/folder-copy.svg
@@ -0,0 +1,350 @@
+
+
+
+
diff --git a/manuskript/mainWindow.py b/manuskript/mainWindow.py
index 4429d01c..19670e2c 100644
--- a/manuskript/mainWindow.py
+++ b/manuskript/mainWindow.py
@@ -53,7 +53,10 @@ class MainWindow(QMainWindow, Ui_MainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.setupUi(self)
+
+ # Var
self.currentProject = None
+ self._lastFocus = None
self.readSettings()
@@ -101,6 +104,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.actImport, self.actCompile, self.actSettings]:
i.setEnabled(False)
+ # Main Menu:: File
self.actOpen.triggered.connect(self.welcome.openFile)
self.actSave.triggered.connect(self.saveDatas)
self.actSaveAs.triggered.connect(self.welcome.saveAsFile)
@@ -111,10 +115,21 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.actSettings.triggered.connect(self.settingsWindow)
self.actCloseProject.triggered.connect(self.closeProject)
self.actQuit.triggered.connect(self.close)
- self.actToolFrequency.triggered.connect(self.frequencyAnalyzer)
- self.actAbout.triggered.connect(self.about)
- self.generateViewMenu()
+ # Main menu:: Documents
+ self.actCopy.triggered.connect(self.documentsCopy)
+ self.actCut.triggered.connect(self.documentsCut)
+ self.actPaste.triggered.connect(self.documentsPaste)
+ self.actDuplicate.triggered.connect(self.documentsDuplicate)
+ self.actDelete.triggered.connect(self.documentsDelete)
+ self.actMoveUp.triggered.connect(self.documentsMoveUp)
+ self.actMoveDown.triggered.connect(self.documentsMoveDown)
+ self.actSplitDialog.triggered.connect(self.documentsSplitDialog)
+ self.actSplitCursor.triggered.connect(self.documentsSplitCursor)
+ self.actMerge.triggered.connect(self.documentsMerge)
+
+ # Main Menu:: view
+ self.generateViewMenu()
self.actModeGroup = QActionGroup(self)
self.actModeSimple.setActionGroup(self.actModeGroup)
self.actModeFiction.setActionGroup(self.actModeGroup)
@@ -123,6 +138,10 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.actModeFiction.triggered.connect(self.setViewModeFiction)
self.actModeSnowflake.setEnabled(False)
+ # Main Menu:: Tool
+ self.actToolFrequency.triggered.connect(self.frequencyAnalyzer)
+ self.actAbout.triggered.connect(self.about)
+
self.makeUIConnections()
# self.loadProject(os.path.join(appPath(), "test_project.zip"))
@@ -182,6 +201,33 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.toolbar.setVisible(True)
self.stack.setCurrentIndex(1)
+ ###############################################################################
+ # GENERAL / UI STUFF
+ ###############################################################################
+
+ def tabMainChanged(self):
+ "Called when main tab changes."
+ self.menuDocuments.menuAction().setVisible(self.tabMain.currentIndex() == self.TabRedac)
+
+ def focusChanged(self, old, new):
+ """
+ We get notified by qApp when focus changes, from old to new widget.
+ """
+
+ # Determine which item had focus last, to send the keyboard shortcuts
+ # to the right place
+
+ targets = [
+ self.treeRedacOutline,
+ self.mainEditor
+ ]
+
+ while new is not None:
+ if new in targets:
+ self._lastFocus = new
+ break
+ new = new.parent()
+
###############################################################################
# SUMMARY
###############################################################################
@@ -386,6 +432,33 @@ class MainWindow(QMainWindow, Ui_MainWindow):
def openIndexes(self, indexes, newTab=True):
self.mainEditor.openIndexes(indexes, newTab=True)
+ # Menu Documents #############################################################
+
+ # Functions called by the menu Documents
+ # self._lastFocus is the last editor that had focus (either treeView or
+ # mainEditor). So we just pass along the signal.
+
+ def documentsCopy(self):
+ if self._lastFocus: self._lastFocus.copy()
+ def documentsCut(self):
+ if self._lastFocus: self._lastFocus.cut()
+ def documentsPaste(self):
+ if self._lastFocus: self._lastFocus.paste()
+ def documentsDuplicate(self):
+ if self._lastFocus: self._lastFocus.duplicate()
+ def documentsDelete(self):
+ if self._lastFocus: self._lastFocus.delete()
+ def documentsMoveUp(self):
+ if self._lastFocus: self._lastFocus.moveUp()
+ def documentsMoveDown(self):
+ if self._lastFocus: self._lastFocus.moveDown()
+ def documentsSplitDialog(self):
+ print("documentsSplitDialog::FIXME")
+ def documentsSplitCursor(self):
+ print("documentsSplitCursor::FIXME")
+ def documentsMerge(self):
+ print("documentsMerge::FIXME")
+
###############################################################################
# LOAD AND SAVE
###############################################################################
@@ -667,6 +740,9 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.btnOutlineRemoveItem.clicked.connect(self.outlineRemoveItemsOutline, AUC)
self.tabMain.currentChanged.connect(self.toolbar.setCurrentGroup)
+ self.tabMain.currentChanged.connect(self.tabMainChanged)
+
+ qApp.focusChanged.connect(self.focusChanged)
def makeConnections(self):
@@ -988,16 +1064,6 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# Custom "tab" bar on the left
self.lstTabs.setIconSize(QSize(48, 48))
for i in range(self.tabMain.count()):
- #icons = ["general-128px.png",
- #"summary-128px.png",
- #"characters-128px.png",
- #"plot-128px.png",
- #"world-128px.png",
- #"outline-128px.png",
- #"editor-128px.png",
- #""
- #]
- #self.tabMain.setTabIcon(i, QIcon(appPath("icons/Custom/Tabs/{}".format(icons[i]))))
icons = [QIcon.fromTheme("stock_view-details"), #info
QIcon.fromTheme("application-text-template"), #applications-publishing
diff --git a/manuskript/models/outlineModel.py b/manuskript/models/outlineModel.py
index e3f61e57..8831c550 100644
--- a/manuskript/models/outlineModel.py
+++ b/manuskript/models/outlineModel.py
@@ -273,6 +273,19 @@ class outlineModel(QAbstractItemModel):
if items is None:
return False
+ # We check if parent is not a child of one of the items
+ if self.isParentAChildOfItems(parent, items):
+ return False
+
+ return True
+
+ def isParentAChildOfItems(self, parent, items):
+ """
+ Takes a parent index, and a list of outlineItems items. Check whether
+ parent is in a child of one of the items.
+ Return True in that case, False if not.
+ """
+
# Get the parent item
if not parent.isValid():
parentItem = self.rootItem
@@ -286,9 +299,9 @@ class outlineModel(QAbstractItemModel):
# Is item in the path? It would mean that it tries to get dropped
# as a children of himself.
if item.ID() in path:
- return False
+ return True
- return True
+ return False
def decodeMimeData(self, data):
if not data.hasFormat("application/xml"):
@@ -345,12 +358,13 @@ class outlineModel(QAbstractItemModel):
if action == Qt.IgnoreAction:
return True # What is that?
- # Strangely, on some cases, we get a call to dropMimeData though
- # self.canDropMimeData returned False.
- # See https://github.com/olivierkes/manuskript/issues/169 to reproduce.
- # So we double check for safety.
- if not self.canDropMimeData(data, action, row, column, parent):
- return False
+ if action == Qt.MoveAction:
+ # Strangely, on some cases, we get a call to dropMimeData though
+ # self.canDropMimeData returned False.
+ # See https://github.com/olivierkes/manuskript/issues/169 to reproduce.
+ # So we double check for safety.
+ if not self.canDropMimeData(data, action, row, column, parent):
+ return False
items = self.decodeMimeData(data)
if items is None:
@@ -366,6 +380,21 @@ class outlineModel(QAbstractItemModel):
else:
beginRow = self.rowCount() + 1
+ if action == Qt.CopyAction:
+ # Behavior if parent is a text item
+ # For example, we select a text and do: CTRL+C CTRL+V
+ if parent.isValid() and not parent.internalPointer().isFolder():
+ # We insert copy in parent folder, just below
+ beginRow = parent.row() + 1
+ parent = parent.parent()
+
+ if parent.isValid() and parent.internalPointer().isFolder():
+ while self.isParentAChildOfItems(parent, items):
+ # We are copying a folder on itself. Assume duplicates.
+ # Copy not in, but next to
+ beginRow = parent.row() + 1
+ parent = parent.parent()
+
if not items:
return False
@@ -373,7 +402,7 @@ class outlineModel(QAbstractItemModel):
if action == Qt.CopyAction:
for item in items:
- item.getUniqueID()
+ item.getUniqueID(recursive=True)
return r
@@ -934,9 +963,13 @@ class outlineItem():
# IDS
###############################################################################
- def getUniqueID(self):
+ def getUniqueID(self, recursive=False):
self.setData(Outline.ID.value, self._model.rootItem.findUniqueID())
+ if recursive:
+ for c in self.children():
+ c.getUniqueID(recursive)
+
def checkIDs(self):
"""This is called when a model is loaded.
diff --git a/manuskript/ui/editors/editorWidget.py b/manuskript/ui/editors/editorWidget.py
index 60ee4008..309d8eee 100644
--- a/manuskript/ui/editors/editorWidget.py
+++ b/manuskript/ui/editors/editorWidget.py
@@ -266,7 +266,8 @@ class editorWidget(QWidget, Ui_editorWidget_ui):
self.currentIndex = QModelIndex()
self.currentID = None
- self.setView()
+ if self._model:
+ self.setView()
def updateIndexFromID(self):
"""
@@ -323,3 +324,36 @@ class editorWidget(QWidget, Ui_editorWidget_ui):
def setDict(self, dct):
self.currentDict = dct
self.dictChanged.emit(dct)
+
+ ###############################################################################
+ # FUNCTIONS FOR MENU ACCESS
+ ###############################################################################
+
+ def getCurrentItemView(self):
+ if 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 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()
+ def documentsSplitDialog(self):
+ print("documentsSplitDialog::FIXME")
+ def documentsSplitCursor(self):
+ print("documentsSplitCursor::FIXME")
+ def documentsMerge(self):
+ print("documentsMerge::FIXME")
diff --git a/manuskript/ui/editors/mainEditor.py b/manuskript/ui/editors/mainEditor.py
index f4591fa7..b4f920f7 100644
--- a/manuskript/ui/editors/mainEditor.py
+++ b/manuskript/ui/editors/mainEditor.py
@@ -172,7 +172,6 @@ class mainEditor(QWidget, Ui_mainEditor):
ts = ts.secondTab
return r
-
###############################################################################
# SELECTION AND UPDATES
###############################################################################
@@ -245,6 +244,24 @@ class mainEditor(QWidget, Ui_mainEditor):
return title
+ ###############################################################################
+ # FUNCTIONS FOR MENU ACCESS
+ ###############################################################################
+
+ def copy(self): self.currentEditor().copy()
+ def cut(self): self.currentEditor().cut()
+ def paste(self): self.currentEditor().paste()
+ def duplicate(self): self.currentEditor().duplicate()
+ def delete(self): self.currentEditor().delete()
+ def moveUp(self): self.currentEditor().moveUp()
+ def moveDown(self): self.currentEditor().moveDown()
+ def documentsSplitDialog(self):
+ print("documentsSplitDialog::FIXME")
+ def documentsSplitCursor(self):
+ print("documentsSplitCursor::FIXME")
+ def documentsMerge(self):
+ print("documentsMerge::FIXME")
+
###############################################################################
# UI
###############################################################################
diff --git a/manuskript/ui/mainWindow.py b/manuskript/ui/mainWindow.py
index 26b92db6..d0005c49 100644
--- a/manuskript/ui/mainWindow.py
+++ b/manuskript/ui/mainWindow.py
@@ -1054,6 +1054,8 @@ class Ui_MainWindow(object):
self.menuView.setObjectName("menuView")
self.menuMode = QtWidgets.QMenu(self.menuView)
self.menuMode.setObjectName("menuMode")
+ self.menuDocuments = QtWidgets.QMenu(self.menubar)
+ self.menuDocuments.setObjectName("menuDocuments")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
@@ -1193,6 +1195,46 @@ class Ui_MainWindow(object):
icon = QtGui.QIcon.fromTheme("document-import")
self.actImport.setIcon(icon)
self.actImport.setObjectName("actImport")
+ self.actCopy = QtWidgets.QAction(MainWindow)
+ icon = QtGui.QIcon.fromTheme("edit-copy")
+ self.actCopy.setIcon(icon)
+ self.actCopy.setObjectName("actCopy")
+ self.actCut = QtWidgets.QAction(MainWindow)
+ icon = QtGui.QIcon.fromTheme("edit-cut")
+ self.actCut.setIcon(icon)
+ self.actCut.setObjectName("actCut")
+ self.actPaste = QtWidgets.QAction(MainWindow)
+ icon = QtGui.QIcon.fromTheme("edit-paste")
+ self.actPaste.setIcon(icon)
+ self.actPaste.setObjectName("actPaste")
+ self.actSplitDialog = QtWidgets.QAction(MainWindow)
+ icon = QtGui.QIcon.fromTheme("split")
+ self.actSplitDialog.setIcon(icon)
+ self.actSplitDialog.setObjectName("actSplitDialog")
+ self.actSplitCursor = QtWidgets.QAction(MainWindow)
+ icon = QtGui.QIcon.fromTheme("split")
+ self.actSplitCursor.setIcon(icon)
+ self.actSplitCursor.setObjectName("actSplitCursor")
+ self.actMerge = QtWidgets.QAction(MainWindow)
+ icon = QtGui.QIcon.fromTheme("merge")
+ self.actMerge.setIcon(icon)
+ self.actMerge.setObjectName("actMerge")
+ self.actDuplicate = QtWidgets.QAction(MainWindow)
+ icon = QtGui.QIcon.fromTheme("folder-copy")
+ self.actDuplicate.setIcon(icon)
+ self.actDuplicate.setObjectName("actDuplicate")
+ self.actDelete = QtWidgets.QAction(MainWindow)
+ icon = QtGui.QIcon.fromTheme("edit-delete")
+ self.actDelete.setIcon(icon)
+ self.actDelete.setObjectName("actDelete")
+ self.actMoveUp = QtWidgets.QAction(MainWindow)
+ icon = QtGui.QIcon.fromTheme("arrow-up")
+ self.actMoveUp.setIcon(icon)
+ self.actMoveUp.setObjectName("actMoveUp")
+ self.actMoveDown = QtWidgets.QAction(MainWindow)
+ icon = QtGui.QIcon.fromTheme("arrow-down")
+ self.actMoveDown.setIcon(icon)
+ self.actMoveDown.setObjectName("actMoveDown")
self.menuFile.addAction(self.actOpen)
self.menuFile.addAction(self.menuRecents.menuAction())
self.menuFile.addAction(self.actSave)
@@ -1215,8 +1257,21 @@ class Ui_MainWindow(object):
self.menuMode.addAction(self.actModeSnowflake)
self.menuView.addAction(self.menuMode.menuAction())
self.menuView.addSeparator()
+ self.menuDocuments.addAction(self.actCopy)
+ self.menuDocuments.addAction(self.actCut)
+ self.menuDocuments.addAction(self.actPaste)
+ self.menuDocuments.addAction(self.actDuplicate)
+ self.menuDocuments.addAction(self.actDelete)
+ self.menuDocuments.addSeparator()
+ self.menuDocuments.addAction(self.actMoveUp)
+ self.menuDocuments.addAction(self.actMoveDown)
+ self.menuDocuments.addSeparator()
+ self.menuDocuments.addAction(self.actMerge)
+ self.menuDocuments.addAction(self.actSplitDialog)
+ self.menuDocuments.addAction(self.actSplitCursor)
self.menubar.addAction(self.menuFile.menuAction())
self.menubar.addAction(self.menuEdit.menuAction())
+ self.menubar.addAction(self.menuDocuments.menuAction())
self.menubar.addAction(self.menuView.menuAction())
self.menubar.addAction(self.menuTools.menuAction())
self.menubar.addAction(self.menuHelp.menuAction())
@@ -1334,6 +1389,7 @@ class Ui_MainWindow(object):
self.menuEdit.setTitle(_translate("MainWindow", "&Edit"))
self.menuView.setTitle(_translate("MainWindow", "&View"))
self.menuMode.setTitle(_translate("MainWindow", "&Mode"))
+ self.menuDocuments.setTitle(_translate("MainWindow", "&Documents"))
self.dckCheatSheet.setWindowTitle(_translate("MainWindow", "&Cheat sheet"))
self.dckSearch.setWindowTitle(_translate("MainWindow", "Sea&rch"))
self.dckNavigation.setWindowTitle(_translate("MainWindow", "&Navigation"))
@@ -1367,6 +1423,26 @@ class Ui_MainWindow(object):
self.actAbout.setToolTip(_translate("MainWindow", "About Manuskript"))
self.actImport.setText(_translate("MainWindow", "Import…"))
self.actImport.setShortcut(_translate("MainWindow", "F7"))
+ self.actCopy.setText(_translate("MainWindow", "Copy"))
+ self.actCopy.setShortcut(_translate("MainWindow", "Ctrl+C"))
+ self.actCut.setText(_translate("MainWindow", "Cut"))
+ self.actCut.setShortcut(_translate("MainWindow", "Ctrl+X"))
+ self.actPaste.setText(_translate("MainWindow", "Paste"))
+ self.actPaste.setShortcut(_translate("MainWindow", "Ctrl+V"))
+ self.actSplitDialog.setText(_translate("MainWindow", "Split…"))
+ self.actSplitDialog.setShortcut(_translate("MainWindow", "Ctrl+Shift+K"))
+ self.actSplitCursor.setText(_translate("MainWindow", "Split at cursor"))
+ self.actSplitCursor.setShortcut(_translate("MainWindow", "Ctrl+K"))
+ self.actMerge.setText(_translate("MainWindow", "Merge"))
+ self.actMerge.setShortcut(_translate("MainWindow", "Ctrl+M"))
+ self.actDuplicate.setText(_translate("MainWindow", "&Duplicate"))
+ self.actDuplicate.setShortcut(_translate("MainWindow", "Ctrl+D"))
+ self.actDelete.setText(_translate("MainWindow", "Delete"))
+ self.actDelete.setShortcut(_translate("MainWindow", "Del"))
+ self.actMoveUp.setText(_translate("MainWindow", "Move Up"))
+ self.actMoveUp.setShortcut(_translate("MainWindow", "Ctrl+Shift+Up"))
+ self.actMoveDown.setText(_translate("MainWindow", "Move Down"))
+ self.actMoveDown.setShortcut(_translate("MainWindow", "Ctrl+Shift+Down"))
from manuskript.ui.cheatSheet import cheatSheet
from manuskript.ui.editors.mainEditor import mainEditor
diff --git a/manuskript/ui/mainWindow.ui b/manuskript/ui/mainWindow.ui
index d13d8752..e87bd792 100644
--- a/manuskript/ui/mainWindow.ui
+++ b/manuskript/ui/mainWindow.ui
@@ -2158,8 +2158,26 @@
+
+
+ &Documents
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -2485,7 +2503,9 @@ QListView::item:hover {
-
+
+
+ Import…
@@ -2494,6 +2514,116 @@ QListView::item:hover {
F7
+
+
+
+
+
+ Copy
+
+
+ Ctrl+C
+
+
+
+
+
+
+
+ Cut
+
+
+ Ctrl+X
+
+
+
+
+
+
+
+ Paste
+
+
+ Ctrl+V
+
+
+
+
+
+
+
+ Split…
+
+
+ Ctrl+Shift+K
+
+
+
+
+
+
+
+ Split at cursor
+
+
+ Ctrl+K
+
+
+
+
+
+
+
+ Merge
+
+
+ Ctrl+M
+
+
+
+
+
+
+
+ &Duplicate
+
+
+ Ctrl+D
+
+
+
+
+
+
+
+ Delete
+
+
+ Del
+
+
+
+
+
+
+
+ Move Up
+
+
+ Ctrl+Shift+Up
+
+
+
+
+
+
+
+ Move Down
+
+
+ Ctrl+Shift+Down
+
+
diff --git a/manuskript/ui/views/outlineBasics.py b/manuskript/ui/views/outlineBasics.py
index 7b12f794..36c6c4b3 100644
--- a/manuskript/ui/views/outlineBasics.py
+++ b/manuskript/ui/views/outlineBasics.py
@@ -286,6 +286,62 @@ class outlineBasics(QAbstractItemView):
def delete(self):
self.model().removeIndexes(self.getSelection())
+ def duplicate(self):
+ self.copy()
+ self.paste()
+
+ def move(self, delta=1):
+ """
+ Move selected items up or down.
+ """
+
+ # we store selected indexes
+ 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.setSelectionModel(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 setPOV(self, POV):
for i in self.getSelection():
self.model().setData(i.sibling(i.row(), Outline.POV.value), str(POV))
From a153606811a7ba0ca45ae76c9fc17915769a792a Mon Sep 17 00:00:00 2001
From: Olivier Keshavjee
Date: Fri, 10 Nov 2017 16:26:23 +0100
Subject: [PATCH 3/5] Adds: split dialog, split at cursor
---
manuskript/mainWindow.py | 23 ++++++++++-
manuskript/models/outlineModel.py | 33 +++++++++++++++-
manuskript/settings.py | 8 +++-
manuskript/ui/editors/editorWidget.py | 50 +++++++++++++++++++++---
manuskript/ui/editors/mainEditor.py | 6 +--
manuskript/ui/tools/splitDialog.py | 55 +++++++++++++++++++++++++++
manuskript/ui/views/outlineBasics.py | 50 ++++++++++++++++++++----
manuskript/ui/views/textEditView.py | 2 +-
8 files changed, 206 insertions(+), 21 deletions(-)
create mode 100644 manuskript/ui/tools/splitDialog.py
diff --git a/manuskript/mainWindow.py b/manuskript/mainWindow.py
index 19670e2c..5618f25b 100644
--- a/manuskript/mainWindow.py
+++ b/manuskript/mainWindow.py
@@ -439,23 +439,42 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# mainEditor). So we just pass along the signal.
def documentsCopy(self):
+ "Copy selected item(s)."
if self._lastFocus: self._lastFocus.copy()
def documentsCut(self):
+ "Cut selected item(s)."
if self._lastFocus: self._lastFocus.cut()
def documentsPaste(self):
+ "Paste clipboard item(s) into selected item."
if self._lastFocus: self._lastFocus.paste()
def documentsDuplicate(self):
+ "Duplicate selected item(s)."
if self._lastFocus: self._lastFocus.duplicate()
def documentsDelete(self):
+ "Delete selected item(s)."
if self._lastFocus: self._lastFocus.delete()
def documentsMoveUp(self):
+ "Move up selected item(s)."
if self._lastFocus: self._lastFocus.moveUp()
def documentsMoveDown(self):
+ "Move Down selected item(s)."
if self._lastFocus: self._lastFocus.moveDown()
+
def documentsSplitDialog(self):
- print("documentsSplitDialog::FIXME")
+ "Opens a dialog to split selected items."
+ if self._lastFocus: self._lastFocus.splitDialog()
+ # current items or selected items?
+ pass
+ # use outlineBasics, to do that on all selected items.
+ # use editorWidget to do that on selected text.
+
def documentsSplitCursor(self):
- print("documentsSplitCursor::FIXME")
+ """
+ Split current item (open in text editor) at cursor position. If there is
+ a text selection, that selection becomes the title of the new scene.
+ """
+ if self._lastFocus and self._lastFocus == self.mainEditor:
+ self.mainEditor.splitCursor()
def documentsMerge(self):
print("documentsMerge::FIXME")
diff --git a/manuskript/models/outlineModel.py b/manuskript/models/outlineModel.py
index 8831c550..c38260a0 100644
--- a/manuskript/models/outlineModel.py
+++ b/manuskript/models/outlineModel.py
@@ -652,6 +652,7 @@ class outlineItem():
if column == Outline.text.value:
wc = wordCount(data)
self.setData(Outline.wordCount.value, wc)
+ self.emitDataChanged(cols=[Outline.text.value]) # new in 0.5.0
if column == Outline.compile.value:
self.emitDataChanged(cols=[Outline.title.value, Outline.compile.value], recursive=True)
@@ -894,9 +895,39 @@ class outlineItem():
item.setData(Outline.text.value, subTxt)
# Inserting item
- self.parent().insertChild(self.row()+k, item)
+ #self.parent().insertChild(self.row()+k, item)
+ self._model.insertItem(item, self.row()+k, self.parent().index())
k += 1
+ def splitAt(self, position, length=0):
+ """
+ Splits note at position p.
+
+ If length is bigger than 0, it describes the length of the title, made
+ from the character following position.
+ """
+
+ txt = self.text()
+
+ # Stores the new text
+ self.setData(Outline.text.value, txt[:position])
+
+ # Create a copy
+ item = self.copy()
+
+ # Update title
+ if length > 0:
+ title = txt[position:position+length].replace("\n", "")
+ else:
+ title = "{}_{}".format(item.title(), 2)
+ item.setData(Outline.title.value, title)
+
+ # Set text
+ item.setData(Outline.text.value, txt[position+length:])
+
+ # Inserting item using the model to signal views
+ self._model.insertItem(item, self.row()+1, self.parent().index())
+
###############################################################################
# XML
###############################################################################
diff --git a/manuskript/settings.py b/manuskript/settings.py
index 4beb4b5b..5cb1bb43 100644
--- a/manuskript/settings.py
+++ b/manuskript/settings.py
@@ -96,13 +96,14 @@ frequencyAnalyzer = {
viewMode = "fiction" # simple, fiction
saveToZip = True
+dontShowDeleteWarning = False
def save(filename=None, protocol=None):
global spellcheck, dict, corkSliderFactor, viewSettings, corkSizeFactor, folderView, lastTab, openIndexes, \
autoSave, autoSaveDelay, saveOnQuit, autoSaveNoChanges, autoSaveNoChangesDelay, outlineViewColumns, \
corkBackground, corkStyle, fullScreenTheme, defaultTextType, textEditor, revisions, frequencyAnalyzer, viewMode, \
- saveToZip
+ saveToZip, dontShowDeleteWarning
allSettings = {
"viewSettings": viewSettings,
@@ -127,6 +128,7 @@ def save(filename=None, protocol=None):
"frequencyAnalyzer": frequencyAnalyzer,
"viewMode": viewMode,
"saveToZip": saveToZip,
+ "dontShowDeleteWarning": dontShowDeleteWarning,
}
#pp=pprint.PrettyPrinter(indent=4, compact=False)
@@ -294,3 +296,7 @@ def load(string, fromString=False, protocol=None):
if "saveToZip" in allSettings:
global saveToZip
saveToZip = allSettings["saveToZip"]
+
+ if "dontShowDeleteWarning" in allSettings:
+ global dontShowDeleteWarning
+ dontShowDeleteWarning = allSettings["dontShowDeleteWarning"]
diff --git a/manuskript/ui/editors/editorWidget.py b/manuskript/ui/editors/editorWidget.py
index 309d8eee..10903072 100644
--- a/manuskript/ui/editors/editorWidget.py
+++ b/manuskript/ui/editors/editorWidget.py
@@ -8,6 +8,7 @@ from manuskript import settings
from manuskript.functions import AUC, mainWindow
from manuskript.ui.editors.editorWidget_ui import Ui_editorWidget_ui
from manuskript.ui.views.textEditView import textEditView
+from manuskript.ui.tools.splitDialog import splitDialog
class editorWidget(QWidget, Ui_editorWidget_ui):
@@ -330,7 +331,9 @@ class editorWidget(QWidget, Ui_editorWidget_ui):
###############################################################################
def getCurrentItemView(self):
- if self.folderView == "outline":
+ if self.stack.currentIndex() == 0:
+ return self.txtRedacText
+ elif self.folderView == "outline":
return self.outlineView
elif self.folderView == "cork":
return self.corkView
@@ -351,9 +354,46 @@ class editorWidget(QWidget, Ui_editorWidget_ui):
if self.getCurrentItemView(): self.getCurrentItemView().moveUp()
def moveDown(self):
if self.getCurrentItemView(): self.getCurrentItemView().moveDown()
- def documentsSplitDialog(self):
- print("documentsSplitDialog::FIXME")
- def documentsSplitCursor(self):
- print("documentsSplitCursor::FIXME")
+
+ 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():
+ # One of the view
+ 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))
+
def documentsMerge(self):
print("documentsMerge::FIXME")
diff --git a/manuskript/ui/editors/mainEditor.py b/manuskript/ui/editors/mainEditor.py
index b4f920f7..3c51f1dc 100644
--- a/manuskript/ui/editors/mainEditor.py
+++ b/manuskript/ui/editors/mainEditor.py
@@ -255,10 +255,8 @@ class mainEditor(QWidget, Ui_mainEditor):
def delete(self): self.currentEditor().delete()
def moveUp(self): self.currentEditor().moveUp()
def moveDown(self): self.currentEditor().moveDown()
- def documentsSplitDialog(self):
- print("documentsSplitDialog::FIXME")
- def documentsSplitCursor(self):
- print("documentsSplitCursor::FIXME")
+ def splitDialog(self): self.currentEditor().splitDialog()
+ def splitCursor(self): self.currentEditor().splitCursor()
def documentsMerge(self):
print("documentsMerge::FIXME")
diff --git a/manuskript/ui/tools/splitDialog.py b/manuskript/ui/tools/splitDialog.py
new file mode 100644
index 00000000..d77456d8
--- /dev/null
+++ b/manuskript/ui/tools/splitDialog.py
@@ -0,0 +1,55 @@
+#!/usr/bin/env python
+# --!-- coding: utf8 --!--
+from PyQt5.QtWidgets import QInputDialog
+
+
+class splitDialog(QInputDialog):
+ """
+ Opens a dialog to split indexes.
+ """
+ def __init__(self, parent, indexes, mark=None):
+ """
+ @param parent: a QWidget, for the dialog.
+ @param indexes: a list of QModelIndex in the outlineModel
+ @param default: the default split mark
+ """
+ QInputDialog.__init__(self, parent)
+
+ description = self.tr("""
+
Split selected item(s) at the given mark.
+
+
If one of the selected item is a folder, it will be applied
+ recursively to all of it's children items.
+
+
The split mark can contain folling escret ape sequences:
+
+
\\n: line break
+
\\t: tab
+
+
+
+
Mark:
+ """)
+
+ if not mark:
+ mark = "\\n---\\n"
+ mark = mark.replace("\n", "\\n")
+ mark = mark.replace("\t", "\\t")
+
+ self.setLabelText(description)
+ self.setTextValue(mark)
+ self.setWindowTitle(self.tr("Split item(s)"))
+
+ r = self.exec()
+
+ mark = self.textValue()
+
+ if r and mark:
+
+ mark = mark.replace("\\n", "\n")
+ mark = mark.replace("\\t", "\t")
+
+ for idx in indexes:
+ if idx.isValid():
+ item = idx.internalPointer()
+ item.split(mark)
diff --git a/manuskript/ui/views/outlineBasics.py b/manuskript/ui/views/outlineBasics.py
index 36c6c4b3..f43f5b43 100644
--- a/manuskript/ui/views/outlineBasics.py
+++ b/manuskript/ui/views/outlineBasics.py
@@ -2,14 +2,16 @@
# --!-- coding: utf8 --!--
from PyQt5.QtCore import Qt, QSignalMapper, QSize
from PyQt5.QtGui import QIcon, QCursor
-from PyQt5.QtWidgets import QAbstractItemView, qApp, QMenu, QAction
-from PyQt5.QtWidgets import QListWidget, QWidgetAction, QListWidgetItem, QLineEdit
+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
from manuskript.functions import toInt, customIcons
from manuskript.models.outlineModel import outlineItem
+from manuskript.ui.tools.splitDialog import splitDialog
class outlineBasics(QAbstractItemView):
@@ -53,7 +55,7 @@ class outlineBasics(QAbstractItemView):
title = mouseIndex.internalPointer().title()
else:
- title = self.tr("Root")
+ title = qApp.translate("outlineBasics", "Root")
if len(title) > 25:
title = title[:25] + "…"
@@ -67,10 +69,10 @@ class outlineBasics(QAbstractItemView):
# Open item(s) in new tab
if mouseIndex in sel and len(sel) > 1:
- actionTitle = self.tr("Open {} items in new tabs").format(len(sel))
+ actionTitle = qApp.translate("outlineBasics", "Open {} items in new tabs").format(len(sel))
self._indexesToOpen = sel
else:
- actionTitle = self.tr("Open {} in a new tab").format(title)
+ actionTitle = qApp.translate("outlineBasics", "Open {} in a new tab").format(title)
self._indexesToOpen = [mouseIndex]
self.actNewTab = QAction(QIcon.fromTheme("go-right"), actionTitle, menu)
@@ -284,6 +286,27 @@ class outlineBasics(QAbstractItemView):
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",
+ "
You're about to delete {} item(s).
Are you sure?
"
+ ).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):
@@ -295,7 +318,7 @@ class outlineBasics(QAbstractItemView):
Move selected items up or down.
"""
- # we store selected indexes
+ # we store selected indexesret
currentID = self.model().ID(self.currentIndex())
selIDs = [self.model().ID(i) for i in self.selectedIndexes()]
@@ -316,7 +339,7 @@ class outlineBasics(QAbstractItemView):
sm.clear()
[sm.select(idx, sm.Select) for idx in selIdx]
sm.setCurrentIndex(self.model().getIndexByID(currentID), sm.Select)
- #self.setSelectionModel(sm)
+ #self.setSmsgBoxelectionModel(sm)
# Unblock signals
self.blockSignals(False)
@@ -342,6 +365,19 @@ class outlineBasics(QAbstractItemView):
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:
+ return
+
+ splitDialog(self, indexes)
+
def setPOV(self, POV):
for i in self.getSelection():
self.model().setData(i.sibling(i.row(), Outline.POV.value), str(POV))
diff --git a/manuskript/ui/views/textEditView.py b/manuskript/ui/views/textEditView.py
index bb98996a..1c2513f5 100644
--- a/manuskript/ui/views/textEditView.py
+++ b/manuskript/ui/views/textEditView.py
@@ -93,7 +93,7 @@ class textEditView(QTextEdit):
default_locale = QLocale.system().name()
if default_locale is None:
default_locale = enchant.list_dicts()[0][0]
-
+
return default_locale
def setModel(self, model):
From bb57d3d0570c047e2ec4f0a995076cd7f57acabb Mon Sep 17 00:00:00 2001
From: Olivier Keshavjee
Date: Fri, 10 Nov 2017 17:21:02 +0100
Subject: [PATCH 4/5] Adds: merge
---
manuskript/functions.py | 3 +++
manuskript/mainWindow.py | 5 ++--
manuskript/models/outlineModel.py | 17 ++++++++++++
manuskript/ui/editors/editorWidget.py | 23 +++++++++++++---
manuskript/ui/editors/mainEditor.py | 3 +--
manuskript/ui/tools/splitDialog.py | 23 +++++++++++++---
manuskript/ui/views/outlineBasics.py | 38 +++++++++++++++++++++++++--
manuskript/ui/views/textEditView.py | 15 +++++++++++
8 files changed, 114 insertions(+), 13 deletions(-)
diff --git a/manuskript/functions.py b/manuskript/functions.py
index 04d57b7a..03e2a792 100644
--- a/manuskript/functions.py
+++ b/manuskript/functions.py
@@ -358,3 +358,6 @@ def customIcons():
]
return sorted(r)
+
+def statusMessage(message, duration=5000):
+ mainWindow().statusBar().showMessage(message, duration)
diff --git a/manuskript/mainWindow.py b/manuskript/mainWindow.py
index 5618f25b..61773d9a 100644
--- a/manuskript/mainWindow.py
+++ b/manuskript/mainWindow.py
@@ -214,7 +214,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
We get notified by qApp when focus changes, from old to new widget.
"""
- # Determine which item had focus last, to send the keyboard shortcuts
+ # Determine which view had focus last, to send the keyboard shortcuts
# to the right place
targets = [
@@ -476,7 +476,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
if self._lastFocus and self._lastFocus == self.mainEditor:
self.mainEditor.splitCursor()
def documentsMerge(self):
- print("documentsMerge::FIXME")
+ "Merges selected item(s)."
+ if self._lastFocus: self._lastFocus.merge()
###############################################################################
# LOAD AND SAVE
diff --git a/manuskript/models/outlineModel.py b/manuskript/models/outlineModel.py
index c38260a0..2748ba28 100644
--- a/manuskript/models/outlineModel.py
+++ b/manuskript/models/outlineModel.py
@@ -928,6 +928,23 @@ class outlineItem():
# Inserting item using the model to signal views
self._model.insertItem(item, self.row()+1, self.parent().index())
+ def mergeWith(self, items, sep="\n---\n"):
+ """
+ Merges item with several other items. Merge is basic, it merges only
+ the text.
+
+ @param items: list of `outlineItem`s.
+ @param sep: a text added between each item's text.
+ """
+
+ # Merges the texts
+ text = [self.text()]
+ text.extend([i.text() for i in items])
+ self.setData(Outline.text.value, sep.join(text))
+
+ # Removes other items
+ self._model.removeIndexes([i.index() for i in items])
+
###############################################################################
# XML
###############################################################################
diff --git a/manuskript/ui/editors/editorWidget.py b/manuskript/ui/editors/editorWidget.py
index 10903072..623b62ad 100644
--- a/manuskript/ui/editors/editorWidget.py
+++ b/manuskript/ui/editors/editorWidget.py
@@ -331,6 +331,12 @@ class editorWidget(QWidget, Ui_editorWidget_ui):
###############################################################################
def getCurrentItemView(self):
+ """
+ Returns the current item view, between txtRedacText, outlineView and
+ corkView. If folder/text view, returns None. (Because handled
+ differently)
+ """
+
if self.stack.currentIndex() == 0:
return self.txtRedacText
elif self.folderView == "outline":
@@ -370,7 +376,7 @@ class editorWidget(QWidget, Ui_editorWidget_ui):
splitDialog(self, [self.currentIndex], mark=sel)
elif self.getCurrentItemView():
- # One of the view
+ # One of the views
self.getCurrentItemView().splitDialog()
def splitCursor(self):
@@ -395,5 +401,16 @@ class editorWidget(QWidget, Ui_editorWidget_ui):
item.splitAt(pos, len(title))
- def documentsMerge(self):
- print("documentsMerge::FIXME")
+ 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()
diff --git a/manuskript/ui/editors/mainEditor.py b/manuskript/ui/editors/mainEditor.py
index 3c51f1dc..606c4668 100644
--- a/manuskript/ui/editors/mainEditor.py
+++ b/manuskript/ui/editors/mainEditor.py
@@ -257,8 +257,7 @@ class mainEditor(QWidget, Ui_mainEditor):
def moveDown(self): self.currentEditor().moveDown()
def splitDialog(self): self.currentEditor().splitDialog()
def splitCursor(self): self.currentEditor().splitCursor()
- def documentsMerge(self):
- print("documentsMerge::FIXME")
+ def merge(self): self.currentEditor().merge()
###############################################################################
# UI
diff --git a/manuskript/ui/tools/splitDialog.py b/manuskript/ui/tools/splitDialog.py
index d77456d8..544b5339 100644
--- a/manuskript/ui/tools/splitDialog.py
+++ b/manuskript/ui/tools/splitDialog.py
@@ -1,6 +1,7 @@
#!/usr/bin/env python
# --!-- coding: utf8 --!--
from PyQt5.QtWidgets import QInputDialog
+from manuskript.functions import mainWindow
class splitDialog(QInputDialog):
@@ -38,7 +39,16 @@ class splitDialog(QInputDialog):
self.setLabelText(description)
self.setTextValue(mark)
- self.setWindowTitle(self.tr("Split item(s)"))
+
+ if len(indexes) == 0:
+ return
+ if len(indexes) == 1:
+ idx = indexes[0]
+ self.setWindowTitle(
+ self.tr("Split '{}'").format(self.getItem(idx).title())
+ )
+ else:
+ self.setWindowTitle(self.tr("Split items"))
r = self.exec()
@@ -50,6 +60,11 @@ class splitDialog(QInputDialog):
mark = mark.replace("\\t", "\t")
for idx in indexes:
- if idx.isValid():
- item = idx.internalPointer()
- item.split(mark)
+ item = self.getItem(idx)
+ item.split(mark)
+
+ def getItem(self, index):
+ if index.isValid():
+ return index.internalPointer()
+ else:
+ return mainWindow().mdlOutline.rootItem
diff --git a/manuskript/ui/views/outlineBasics.py b/manuskript/ui/views/outlineBasics.py
index f43f5b43..ccd655cb 100644
--- a/manuskript/ui/views/outlineBasics.py
+++ b/manuskript/ui/views/outlineBasics.py
@@ -8,7 +8,7 @@ from PyQt5.QtWidgets import QAbstractItemView, qApp, QMenu, QAction, \
from manuskript import settings
from manuskript.enums import Outline
-from manuskript.functions import mainWindow
+from manuskript.functions import mainWindow, statusMessage
from manuskript.functions import toInt, customIcons
from manuskript.models.outlineModel import outlineItem
from manuskript.ui.tools.splitDialog import splitDialog
@@ -374,10 +374,44 @@ class outlineBasics(QAbstractItemView):
indexes = self.getSelection()
if len(indexes) == 0:
- return
+ # 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."))
+ 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)."))
+ 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.value), str(POV))
diff --git a/manuskript/ui/views/textEditView.py b/manuskript/ui/views/textEditView.py
index 1c2513f5..a9c7907b 100644
--- a/manuskript/ui/views/textEditView.py
+++ b/manuskript/ui/views/textEditView.py
@@ -141,6 +141,21 @@ class textEditView(QTextEdit):
self.setPlainText("")
self.setEnabled(False)
+ def currentIndex(self):
+ """
+ Getter function used to normalized views acces with QAbstractItemViews.
+ """
+ if self._index:
+ return self._index
+ else:
+ return QModelIndex()
+
+ def getSelection(self):
+ """
+ Getter function used to normalized views acces with QAbstractItemViews.
+ """
+ return [self.currentIndex()]
+
def setCurrentModelIndexes(self, indexes):
self._index = None
self._indexes = []
From 4445b55559353981e1cf1fa98178f218b243ecc5 Mon Sep 17 00:00:00 2001
From: Olivier Keshavjee
Date: Fri, 10 Nov 2017 17:33:48 +0100
Subject: [PATCH 5/5] Allows pasted items to keep ID if not already in model.
---
manuskript/models/outlineModel.py | 20 ++++++++++++++++----
1 file changed, 16 insertions(+), 4 deletions(-)
diff --git a/manuskript/models/outlineModel.py b/manuskript/models/outlineModel.py
index 2748ba28..140def2d 100644
--- a/manuskript/models/outlineModel.py
+++ b/manuskript/models/outlineModel.py
@@ -398,11 +398,23 @@ class outlineModel(QAbstractItemModel):
if not items:
return False
- r = self.insertItems(items, beginRow, parent)
-
+ # In case of copy actions, items might be duplicates, so we need new IDs.
+ # But they might not be, if we cut, then paste. Paste is a Copy Action.
+ # The first paste would not need new IDs. But subsequent ones will.
if action == Qt.CopyAction:
+ IDs = self.rootItem.listAllIDs()
+
for item in items:
- item.getUniqueID(recursive=True)
+ if item.ID() in IDs:
+ # Recursively remove ID. So will get a new one when inserted.
+ def stripID(item):
+ item.setData(Outline.ID.value, None)
+ for c in item.children():
+ stripID(c)
+
+ stripID(item)
+
+ r = self.insertItems(items, beginRow, parent)
return r
@@ -928,7 +940,7 @@ class outlineItem():
# Inserting item using the model to signal views
self._model.insertItem(item, self.row()+1, self.parent().index())
- def mergeWith(self, items, sep="\n---\n"):
+ def mergeWith(self, items, sep="\n\n"):
"""
Merges item with several other items. Merge is basic, it merges only
the text.