Adds: import documents from several sources (txt, md, html, epub, docx, OPML, odt, etc.) #200

This commit is contained in:
Olivier Keshavjee 2017-11-09 15:21:25 +01:00
commit 15ccaa513b
24 changed files with 1927 additions and 66 deletions

View file

@ -129,7 +129,7 @@ class basicFormat:
@classmethod
def isValid(cls):
return True
@classmethod
def projectPath(cls):
return os.path.dirname(os.path.abspath(mainWindow().currentProject))

View file

@ -0,0 +1,26 @@
#!/usr/bin/env python
# --!-- coding: utf8 --!--
from manuskript.importer.folderImporter import folderImporter
from manuskript.importer.markdownImporter import markdownImporter
from manuskript.importer.opmlImporter import opmlImporter
from manuskript.importer.pandocImporters import markdownPandocImporter, \
odtPandocImporter, ePubPandocImporter, docXPandocImporter, HTMLPandocImporter, \
rstPandocImporter, LaTeXPandocImporter, OPMLPandocImporter
importers = [
# Internal
markdownImporter,
folderImporter,
opmlImporter,
# Pandoc
markdownPandocImporter,
odtPandocImporter,
ePubPandocImporter,
docXPandocImporter,
HTMLPandocImporter,
rstPandocImporter,
LaTeXPandocImporter,
OPMLPandocImporter,
]

View file

@ -0,0 +1,199 @@
#!/usr/bin/env python
# --!-- coding: utf8 --!--
import os
import shutil
import subprocess
from PyQt5.QtCore import QSettings
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QCheckBox, QHBoxLayout, \
QLabel, QSpinBox, QComboBox, QLineEdit
from manuskript.ui.collapsibleGroupBox2 import collapsibleGroupBox2
from manuskript.ui import style
class abstractImporter:
"""
abstractImporter is used to import documents into manuskript.
The startImport function must be subclassed. It takes a filePath (str to
the document to import), and must return `outlineItem`s.
"""
name = ""
description = ""
fileFormat = "" # File format accepted. For example: "OPML Files (*.opml)"
# For folder, use "<<folder>>"
icon = ""
engine = "Internal"
def __init__(self):
self.settings = {}
def startImport(self, filePath, parentItem, settingsWidget):
"""
Takes a str path to the file/folder to import, and the settingsWidget
returnend by `self.settingsWidget()` containing the user set settings,
and return `outlineItem`s.
"""
pass
@classmethod
def isValid(cls):
return False
def settingsWidget(self, widget):
"""
Takes a QWidget that can be modified and must be returned.
"""
return widget
def addPage(self, widget, title):
"""
Convenience function to add a page to the settingsWidget `widget`, at
the end.
Returns the page widget.
"""
w = QWidget(widget)
w.setLayout(QVBoxLayout())
widget.toolBox.insertItem(widget.toolBox.count(), w, title)
widget.toolBox.layout().setSpacing(0)
return w
def addGroup(self, parent, title):
"""
Adds a collapsible group to the given widget.
"""
g = collapsibleGroupBox2(title=title)
parent.layout().addWidget(g)
g.setLayout(QVBoxLayout())
return g
def addSetting(self, name, type, label, widget=None, default=None,
tooltip=None, min=None, max=None, vals=None, suffix=""):
self.settings[name] = self.setting(name, type, label, widget, default,
tooltip, min, max, vals, suffix)
def widget(self, name):
if name in self.settings:
return self.settings[name].widget()
def getSetting(self, name):
if name in self.settings:
return self.settings[name]
class setting:
"""
A class used to store setting, and display a widget for the user to
modify it.
"""
def __init__(self, name, type, label, widget=None, default=None,
tooltip=None, min=None, max=None, vals=None, suffix=""):
self.name = name
self.type = type
self.label = label
self._widget = widget
self.default = default
self.min = min
self.max = max
self.vals = vals.split("|") if vals else []
self.suffix = suffix
self.tooltip = tooltip
def widget(self, parent=None):
"""
Returns the widget used, or creates it if not done yet. If parent
is given, widget is inserted in parent's layout.
"""
if self._widget:
return self._widget
else:
if "checkbox" in self.type:
self._widget = QCheckBox(self.label)
if self.default:
self._widget.setChecked(True)
if parent:
parent.layout().addWidget(self._widget)
elif "number" in self.type:
l = QHBoxLayout()
label = QLabel(self.label, parent)
label.setWordWrap(True)
l.addWidget(label, 8)
self._widget = QSpinBox()
self._widget.setValue(self.default if self.default else 0)
if self.min:
self._widget.setMinimum(self.min)
if self.max:
self._widget.setMaximum(self.max)
if self.suffix:
self._widget.setSuffix(self.suffix)
l.addWidget(self._widget, 2)
if parent:
parent.layout().addLayout(l)
elif "combo" in self.type:
l = QHBoxLayout()
label = QLabel(self.label, parent)
label.setWordWrap(True)
l.addWidget(label, 6)
self._widget = QComboBox()
self._widget.addItems(self.vals)
if self.default:
self._widget.setCurrentText(self.default)
l.addWidget(self._widget, 2)
if parent:
parent.layout().addLayout(l)
elif "text" in self.type:
l = QHBoxLayout()
label = QLabel(self.label, parent)
label.setWordWrap(True)
l.addWidget(label, 5)
self._widget = QLineEdit()
self._widget.setStyleSheet(style.lineEditSS())
if self.default:
self._widget.setText(self.default)
l.addWidget(self._widget, 3)
if parent:
parent.layout().addLayout(l)
elif "label" in self.type:
self._widget = QLabel(self.label, parent)
self._widget.setWordWrap(True)
if parent:
parent.layout().addWidget(self._widget)
if self.tooltip:
self._widget.setToolTip(self.tooltip)
return self._widget
def value(self):
"""
Return the value contained in the widget.
"""
if not self._widget:
return self.default
else:
if "checkbox" in self.type:
return self._widget.isChecked()
elif "number" in self.type:
return self._widget.value()
elif "combo" in self.type:
return self._widget.currentText()
elif "text" in self.type:
return self._widget.text()

View file

@ -0,0 +1,121 @@
#!/usr/bin/env python
# --!-- coding: utf8 --!--
import os
from manuskript.importer.abstractImporter import abstractImporter
from manuskript.models.outlineModel import outlineItem
from manuskript.enums import Outline
from PyQt5.QtWidgets import qApp
class folderImporter(abstractImporter):
name = "Folder"
description = ""
fileFormat = "<<folder>>"
icon = "folder"
@classmethod
def isValid(cls):
return True
def startImport(self, filePath, parentItem, settingsWidget, fromString=None):
"""
Imports from a folder.
"""
ext = self.getSetting("ext").value()
ext = [e.strip().replace("*", "") for e in ext.split(",")]
sorting = self.getSetting("sortItems").value()
items = []
stack = {}
for dirpath, dirnames, filenames in os.walk(filePath):
if dirpath in stack:
item = stack[dirpath]
else:
# It's the parent folder, and we are not including it
# so every item is attached to parentItem
item = parentItem
def addFile(f):
fName, fExt = os.path.splitext(f)
if fExt in ext:
child = outlineItem(title=fName, _type="md", parent=item)
with open(os.path.join(dirpath, f), "r") as fr:
child._data[Outline.text] = fr.read()
items.append(child)
def addFolder(d):
child = outlineItem(title=d, parent=item)
items.append(child)
stack[os.path.join(dirpath, d)] = child
if not self.getSetting("separateFolderFiles").value():
# Import folder and files together (only makes differences if
# they are sorted, really)
allFiles = dirnames + filenames
if sorting:
allFiles = sorted(allFiles)
for f in allFiles:
if f in dirnames:
addFolder(f)
else:
addFile(f)
else:
# Import first folders, then files
if sorting:
dirnames = sorted(dirnames)
filenames = sorted(filenames)
# Import folders
for d in dirnames:
addFolder(d)
# Import files
for f in filenames:
addFile(f)
return items
def settingsWidget(self, widget):
"""
Takes a QWidget that can be modified and must be returned.
"""
# Add group
group = self.addGroup(widget.toolBox.widget(0),
qApp.translate("Import", "Folder import"))
#group = cls.addPage(widget, "Folder import")
self.addSetting("info", "label",
qApp.translate("Import", """<b>Info:</b> Imports a whole
directory structure. Folders are added as folders, and
plaintext documents within (you chose which ones by extension)
are added as scene.<br/>&nbsp;"""))
self.addSetting("ext", "text",
qApp.translate("Import", "Include only those extensions:"),
default="*.txt, *.md",
tooltip=qApp.translate("Import", "Coma separated values")),
self.addSetting("sortItems", "checkbox",
qApp.translate("Import", "Sort items by name"),
default=True),
self.addSetting("separateFolderFiles", "checkbox",
qApp.translate("Import", "Import folder then files"),
default=True),
for s in self.settings:
self.settings[s].widget(group)
return widget

View file

@ -0,0 +1,187 @@
#!/usr/bin/env python
# --!-- coding: utf8 --!--
from manuskript.importer.abstractImporter import abstractImporter
from manuskript.models.outlineModel import outlineItem
from manuskript.enums import Outline
from PyQt5.QtWidgets import qApp
import re, os
class markdownImporter(abstractImporter):
name = "Markdown"
description = ""
fileFormat = "Markdown files (*.md *.txt *)"
icon = "text-x-markdown"
@classmethod
def isValid(cls):
return True
def startImport(self, filePath, parentItem, settingsWidget, fromString=None):
"""
Very simple import from markdown. We just look at ATX headers (we
ignore setext for the sake of simplicity, for now.)
**A difficulty:** in the following example, we can do things with
markdown headers (like go from level 1 to level 4 and back to level 2)
that we cannot do in an outline.
```
# Level 1
# Level 1
## Level 2
### Level 3
#### Level 4
##### Level 5
### Level 3
# Level 1
#### Level 4? → Level 2
### Level 3? → Level 2
## Level 2 → Level 2
#### Level 4? → Level 3
```
I think the current version of the imported manages that quite well.
**A question:** In the following sample, the first Level 1 becomes a
text element, because it has no other sub elements. But the content of
second Level 1 becomes a text element, with no name. What name should
we give it?
```
# Level 1
Some texte content.
Level 1 will become a text element.
# Level 1
This content has no name.
## Level 2
...
```
"""
if not fromString:
# Read file
with open(filePath, "r") as f:
txt = f.read()
else:
txt = fromString
items = []
parent = parentItem
lastLevel = 0
content = ""
def saveContent(content, parent):
if content.strip():
child = outlineItem(title=parent.title(), parent=parent, _type="md")
child._data[Outline.text] = content
items.append(child)
return ""
def addTitle(name, parent, level):
child = outlineItem(title=name, parent=parent)
child.__miLevel = level
items.append(child)
return child
ATXHeader = re.compile(r"(\#+)\s*(.+?)\s*\#*$")
setextHeader1 = re.compile(r"([^\#-=].+)\n(===+)$", re.MULTILINE)
setextHeader2 = re.compile(r"([^\#-=].+)\n(---+)$", re.MULTILINE)
# We store the level of each item in a temporary var
parent.__miLevel = 0 # markdown importer header level
txt = txt.split("\n")
skipNextLine = False
for i in range(len(txt)):
l = txt[i]
l2 = "\n".join(txt[i:i+2])
header = False
if skipNextLine:
# Last line was a setext-style header.
skipNextLine = False
continue
# Check ATX Header
m = ATXHeader.match(l)
if m:
header = True
level = len(m.group(1))
name = m.group(2)
# Check setext header
m = setextHeader1.match(l2)
if not header and m and len(m.group(1)) == len(m.group(2)):
header = True
level = 1
name = m.group(1)
skipNextLine = True
m = setextHeader2.match(l2)
if not header and m and len(m.group(1)) == len(m.group(2)):
header = True
level = 2
name = m.group(1)
skipNextLine = True
if header:
# save content
content = saveContent(content, parent)
# get parent level
while parent.__miLevel >= level:
parent = parent.parent()
# create title
child = addTitle(name, parent, level)
child.__miLevel = level
# title becomes the new parent
parent = child
lastLevel = level
else:
content += l + "\n"
saveContent(content, parent)
# Clean up
for i in items:
if i.childCount() == 1 and i.children()[0].isText():
# We have a folder with only one text item
# So we make it a text item
i._data[Outline.type] = "md"
i._data[Outline.text] = i.children()[0].text()
c = i.removeChild(0)
items.remove(c)
return items
def settingsWidget(self, widget):
"""
Takes a QWidget that can be modified and must be returned.
"""
# Add group
group = self.addGroup(widget.toolBox.widget(0),
qApp.translate("Import", "Markdown import"))
#group = cls.addPage(widget, "Folder import")
self.addSetting("info", "label",
qApp.translate("Import", """<b>Info:</b> A very simple
parser that will go through a markdown document and
create items for each titles.<br/>&nbsp;"""))
for s in self.settings:
self.settings[s].widget(group)
return widget

View file

@ -0,0 +1,124 @@
#!/usr/bin/env python
# --!-- coding: utf8 --!--
from PyQt5.QtWidgets import qApp, QMessageBox
from manuskript.models.outlineModel import outlineItem
from manuskript.enums import Outline
from lxml import etree as ET
from manuskript.functions import mainWindow
from manuskript.importer.abstractImporter import abstractImporter
class opmlImporter(abstractImporter):
name = "OPML"
description = ""
fileFormat = "OPML Files (*.opml *.xml)"
icon = "text-x-opml+xml"
@classmethod
def isValid(cls):
return True
@classmethod
def startImport(cls, filePath, parentItem, settingsWidget, fromString=None):
"""
Import/export outline cards in OPML format.
"""
ret = False
if filePath != "":
# We have a filePath, so we read the file
try:
with open(filePath, 'rb') as opmlFile:
#opmlContent = cls.saveNewlines(opmlFile.read())
opmlContent = opmlFile.read()
except:
QMessageBox.critical(settingsWidget,
qApp.translate("Import", "OPML Import"),
qApp.translate("Import", "File open failed."))
return None
elif fromString == "":
# We have neither filePath nor fromString, so we leave
return None
else:
# We load from string
opmlContent = bytes(fromString, "utf-8")
parsed = ET.fromstring(opmlContent)
opmlNode = parsed
bodyNode = opmlNode.find("body")
items = []
if bodyNode is not None:
outlineEls = bodyNode.findall("outline")
if outlineEls is not None:
for element in outlineEls:
items.extend(cls.parseItems(element, parentItem))
ret = True
if not ret:
QMessageBox.critical(
settingsWidget,
qApp.translate("Import", "OPML Import"),
qApp.translate("Import", "This does not appear to be a valid OPML file."))
return None
return items
@classmethod
def parseItems(cls, underElement, parentItem=None):
items = []
title = underElement.get('text')
if title is not None:
card = outlineItem(parent=parentItem, title=title)
items.append(card)
body = ""
note = underElement.get('_note')
if note is not None and not cls.isWhitespaceOnly(note):
#body = cls.restoreNewLines(note)
body = note
children = underElement.findall('outline')
if children is not None and len(children) > 0:
for el in children:
items.extend(cls.parseItems(el, card))
else:
card.setData(Outline.type.value, 'md')
card.setData(Outline.text.value, body)
return items
@classmethod
def saveNewlines(cls, inString):
"""
Since XML parsers are notorious for stripping out significant newlines,
save them in a form we can restore after the parse.
"""
inString = inString.replace("\r\n", "\n")
inString = inString.replace("\n", "{{lf}}")
return inString
@classmethod
def restoreNewLines(cls, inString):
"""
Restore any significant newlines
"""
return inString.replace("{{lf}}", "\n")
@classmethod
def isWhitespaceOnly(cls, inString):
"""
Determine whether or not a string only contains whitespace.
"""
s = cls.restoreNewLines(inString)
s = ''.join(s.split())
return len(s) is 0

View file

@ -0,0 +1,156 @@
#!/usr/bin/env python
# --!-- coding: utf8 --!--
from manuskript.importer.abstractImporter import abstractImporter
from manuskript.exporter.pandoc import pandocExporter
from manuskript.importer.opmlImporter import opmlImporter
from manuskript.importer.markdownImporter import markdownImporter
from PyQt5.QtWidgets import qApp
class pandocImporter(abstractImporter):
formatFrom = ""
engine = "Pandoc"
extraArgs = []
@classmethod
def isValid(cls):
return pandocExporter().isValid()
def startImport(self, filePath, parentItem, settingsWidget):
formatTo = self.getSetting("formatTo").value().lower()
wrap = self.getSetting("wrap").value().lower()
# pandoc --from=markdown filename --to=opml --standalone
args = [
"--from={}".format(self.formatFrom),
filePath,
"--to={}".format(formatTo),
"--wrap={}".format(wrap),
]
if formatTo == "opml":
args.append("--standalone")
args += self.extraArgs
r = pandocExporter().run(args)
if formatTo == "opml":
return self.opmlImporter.startImport("", parentItem,
settingsWidget, fromString=r)
elif formatTo == "markdown":
return self.mdImporter.startImport(filePath, parentItem,
settingsWidget, fromString=r)
def settingsWidget(self, widget):
"""
Takes a QWidget that can be modified and must be returned.
"""
# Add group
group = self.addGroup(widget.toolBox.widget(0),
qApp.translate("Import", "Pandoc import"))
self.addSetting("info", "label",
qApp.translate("Import", """<b>Info:</b> Manuskript can
import from <b>markdown</b> or <b>OPML</b>. Pandoc will
convert your document to either (see option below), and
then it will be imported in manuskript. One or the other
might give better result depending on your document.
<br/>&nbsp;"""))
self.addSetting("formatTo", "combo",
qApp.translate("Import", "Import using:"),
vals="markdown|OPML")
self.addSetting("wrap", "combo",
qApp.translate("Import", "Wrap lines:"),
vals="auto|none|preserve",
default="none",
tooltip=qApp.translate("Import", """<p>Should pandoc create
cosmetic / non-semantic line-breaks?</p><p>
<b>auto</b>: wraps at 72 characters.<br>
<b>none</b>: no line wrap.<br>
<b>preserve</b>: tries to preserves line wrap from the
original document.</p>"""))
for s in self.settings:
self.settings[s].widget(group)
self.mdImporter = markdownImporter()
widget = self.mdImporter.settingsWidget(widget)
self.opmlImporter = opmlImporter()
widget = self.opmlImporter.settingsWidget(widget)
return widget
class markdownPandocImporter(pandocImporter):
name = "Markdown"
description = "Markdown, using pandoc"
fileFormat = "Markdown files (*.md *.txt *)"
icon = "text-x-markdown"
formatFrom = "markdown"
class ePubPandocImporter(pandocImporter):
name = "ePub"
description = ""
fileFormat = "ePub files (*.epub)"
icon = "application-epub+zip"
formatFrom = "epub"
class docXPandocImporter(pandocImporter):
name = "DocX"
description = ""
fileFormat = "DocX files (*.docx)"
icon = "application-vnd.openxmlformats-officedocument.wordprocessingml.document"
formatFrom = "docx"
class odtPandocImporter(pandocImporter):
name = "ODT"
description = ""
fileFormat = "Open Document files (*.odt)"
icon = "application-vnd.oasis.opendocument.text"
formatFrom = "odt"
class rstPandocImporter(pandocImporter):
name = "reStructuredText"
description = ""
fileFormat = "reStructuredText files (*.rst)"
icon = "text-plain"
formatFrom = "rst"
class HTMLPandocImporter(pandocImporter):
name = "HTML"
description = ""
fileFormat = "HTML files (*.htm *.html)"
icon = "text-html"
formatFrom = "html"
class LaTeXPandocImporter(pandocImporter):
name = "LaTeX"
description = ""
fileFormat = "LaTeX files (*.tex)"
icon = "text-x-tex"
formatFrom = "latex"
class OPMLPandocImporter(pandocImporter):
name = "OPML"
description = ""
fileFormat = "OPML files (*.opml *.xml)"
icon = "text-x-opml+xml"
formatFrom = "opml"

View file

@ -21,6 +21,7 @@ from manuskript.settingsWindow import settingsWindow
from manuskript.ui import style
from manuskript.ui.about import aboutDialog
from manuskript.ui.collapsibleDockWidgets import collapsibleDockWidgets
from manuskript.ui.importers.importer import importerDialog
from manuskript.ui.exporters.exporter import exporterDialog
from manuskript.ui.helpLabel import helpLabel
from manuskript.ui.mainWindow import Ui_MainWindow
@ -97,12 +98,13 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# Main Menu
for i in [self.actSave, self.actSaveAs, self.actCloseProject,
self.menuEdit, self.menuView, self.menuTools, self.menuHelp,
self.actCompile, self.actSettings]:
self.actImport, self.actCompile, self.actSettings]:
i.setEnabled(False)
self.actOpen.triggered.connect(self.welcome.openFile)
self.actSave.triggered.connect(self.saveDatas)
self.actSaveAs.triggered.connect(self.welcome.saveAsFile)
self.actImport.triggered.connect(self.doImport)
self.actCompile.triggered.connect(self.doCompile)
self.actLabels.triggered.connect(self.settingsLabel)
self.actStatus.triggered.connect(self.settingsStatus)
@ -381,6 +383,9 @@ class MainWindow(QMainWindow, Ui_MainWindow):
def openIndex(self, index):
self.treeRedacOutline.setCurrentIndex(index)
def openIndexes(self, indexes, newTab=True):
self.mainEditor.openIndexes(indexes, newTab=True)
###############################################################################
# LOAD AND SAVE
###############################################################################
@ -408,7 +413,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.makeConnections()
# Load settings
if settings.openIndexes:
if settings.openIndexes and settings.openIndexes != [""]:
self.mainEditor.tabSplitter.restoreOpenIndexes(settings.openIndexes)
self.generateViewMenu()
self.mainEditor.sldCorkSizeFactor.setValue(settings.corkSizeFactor)
@ -460,7 +465,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
i.setEnabled(False)
for i in [self.actSave, self.actSaveAs, self.actCloseProject,
self.menuEdit, self.menuView, self.menuTools, self.menuHelp,
self.actCompile, self.actSettings]:
self.actImport, self.actCompile, self.actSettings]:
i.setEnabled(True)
# Add project name to Window's name
@ -504,7 +509,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
i.setEnabled(True)
for i in [self.actSave, self.actSaveAs, self.actCloseProject,
self.menuEdit, self.menuView, self.menuTools, self.menuHelp,
self.actCompile, self.actSettings]:
self.actImport, self.actCompile, self.actSettings]:
i.setEnabled(False)
# Set Window's name - no project loaded
@ -1306,9 +1311,17 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# POV in settings / views
###############################################################################
# COMPILE
# IMPORT / EXPORT
###############################################################################
def doImport(self):
self.dialog = importerDialog(mw=self)
self.dialog.show()
r = self.dialog.geometry()
r2 = self.geometry()
self.dialog.move(r2.center() - r.center())
def doCompile(self):
self.dialog = exporterDialog(mw=self)
self.dialog.show()

View file

@ -821,10 +821,56 @@ class outlineItem():
return qApp.translate("outlineModel", "{} words").format(
locale.format("%d", wc, grouping=True))
def copy(self):
"""
Returns a copy of item, with no parent, and no ID.
"""
item = outlineItem(xml=self.toXML())
item.setData(Outline.ID.value, None)
return item
###############################################################################
# XML
###############################################################################
def split(self, splitMark, recursive=True):
"""
Split scene at splitMark. If multiple splitMark, multiple splits.
If called on a folder and recursive is True, then it is recursively
applied to every children.
"""
if self.isFolder() and recursive:
for c in self.children():
c.split(splitMark)
else:
txt = self.text().split(splitMark)
if len(txt) == 1:
# Mark not found
return False
else:
# Stores the new text
self.setData(Outline.text.value, txt[0])
k = 1
for subTxt in txt[1:]:
# Create a copy
item = self.copy()
# Change title adding _k
item.setData(Outline.title.value,
"{}_{}".format(item.title(), k+1))
# Set text
item.setData(Outline.text.value, subTxt)
# Inserting item
self.parent().insertChild(self.row()+k, item)
k += 1
###############################################################################
# XML
###############################################################################
def toXML(self):
item = ET.Element("outlineItem")
@ -884,10 +930,9 @@ class outlineItem():
elif child.tag == "revision":
self.appendRevision(child.attrib["timestamp"], child.attrib["text"])
###############################################################################
# IDS
###############################################################################
###############################################################################
# IDS
###############################################################################
def getUniqueID(self):
self.setData(Outline.ID.value, self._model.rootItem.findUniqueID())
@ -900,7 +945,7 @@ class outlineItem():
self.IDs = self.listAllIDs()
if max([self.IDs.count(i) for i in self.IDs if i]) != 1:
print("There are some doublons:", [i for i in self.IDs if i and self.IDs.count(i) != 1])
print("WARNING ! There are some items with same IDs:", [i for i in self.IDs if i and self.IDs.count(i) != 1])
def checkChildren(item):
for c in item.children():
@ -918,8 +963,9 @@ class outlineItem():
return IDs
def findUniqueID(self):
k = 0
while str(k) in self.IDs:
IDs = [int(i) for i in self.IDs]
k = 1
while k in IDs:
k += 1
self.IDs.append(str(k))
return str(k)

View file

@ -55,6 +55,8 @@ class editorWidget(QWidget, Ui_editorWidget_ui):
self.mw = mainWindow()
self._tabWidget = None # set by mainEditor on creation
self._model = None
# def setModel(self, model):
# self._model = model
# self.setView()
@ -83,8 +85,10 @@ class editorWidget(QWidget, Ui_editorWidget_ui):
if r.isValid():
count = r.internalPointer().childCount()
elif self._model:
count = self._model.rootItem.childCount()
else:
count = self.mw.mdlOutline.rootItem.childCount()
count = 0
for c in range(count):
self.corkView.itemDelegate().sizeHintChanged.emit(r.child(c, 0))
@ -102,8 +106,10 @@ class editorWidget(QWidget, Ui_editorWidget_ui):
if self.currentIndex.isValid():
item = self.currentIndex.internalPointer()
elif self._model:
item = self._model.rootItem
else:
item = self.mw.mdlOutline.rootItem
return
i = self._tabWidget.indexOf(self)
self._tabWidget.setTabText(i, item.title())
@ -202,7 +208,7 @@ class editorWidget(QWidget, Ui_editorWidget_ui):
self.txtEdits = []
if item != self.mw.mdlOutline.rootItem:
if item != self._model.rootItem:
addTitle(item)
addChildren(item)
@ -211,7 +217,7 @@ class editorWidget(QWidget, Ui_editorWidget_ui):
elif item and item.isFolder() and self.folderView == "cork":
self.stack.setCurrentIndex(2)
self.corkView.setModel(self.mw.mdlOutline)
self.corkView.setModel(self._model)
self.corkView.setRootIndex(self.currentIndex)
try:
self.corkView.selectionModel().selectionChanged.connect(mainWindow().redacMetadata.selectionChanged, AUC)
@ -225,7 +231,7 @@ class editorWidget(QWidget, Ui_editorWidget_ui):
self.outlineView.setModelCharacters(mainWindow().mdlCharacter)
self.outlineView.setModelLabels(mainWindow().mdlLabels)
self.outlineView.setModelStatus(mainWindow().mdlStatus)
self.outlineView.setModel(self.mw.mdlOutline)
self.outlineView.setModel(self._model)
self.outlineView.setRootIndex(self.currentIndex)
try:
@ -242,9 +248,9 @@ class editorWidget(QWidget, Ui_editorWidget_ui):
self.txtRedacText.setCurrentModelIndex(QModelIndex())
try:
self.mw.mdlOutline.dataChanged.connect(self.modelDataChanged, AUC)
self.mw.mdlOutline.rowsInserted.connect(self.updateIndexFromID, AUC)
self.mw.mdlOutline.rowsRemoved.connect(self.updateIndexFromID, AUC)
self._model.dataChanged.connect(self.modelDataChanged, AUC)
self._model.rowsInserted.connect(self.updateIndexFromID, AUC)
self._model.rowsRemoved.connect(self.updateIndexFromID, AUC)
#self.mw.mdlOutline.rowsAboutToBeRemoved.connect(self.rowsAboutToBeRemoved, AUC)
except TypeError:
pass
@ -254,8 +260,8 @@ class editorWidget(QWidget, Ui_editorWidget_ui):
def setCurrentModelIndex(self, index=None):
if index.isValid():
self.currentIndex = index
self.currentID = self.mw.mdlOutline.ID(index)
# self._model = index.model()
self._model = index.model()
self.currentID = self._model.ID(index)
else:
self.currentIndex = QModelIndex()
self.currentID = None
@ -267,7 +273,7 @@ class editorWidget(QWidget, Ui_editorWidget_ui):
Index might have changed (through drag an drop), so we keep current
item's ID and update index. Item might have been deleted too.
"""
idx = self.mw.mdlOutline.getIndexByID(self.currentID)
idx = self._model.getIndexByID(self.currentID)
# If we have an ID but the ID does not exist, it has been deleted
if self.currentID and idx == QModelIndex():

View file

@ -50,7 +50,9 @@
<string/>
</property>
<property name="icon">
<iconset theme="go-up"/>
<iconset theme="go-up">
<normaloff/>
</iconset>
</property>
<property name="shortcut">
<string>Alt+Up</string>

View file

@ -2,7 +2,7 @@
# Form implementation generated from reading ui file 'manuskript/ui/exporters/manuskript/plainTextSettings_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!
@ -28,9 +28,10 @@ class Ui_exporterSettings(object):
"}")
self.toolBox.setObjectName("toolBox")
self.content = QtWidgets.QWidget()
self.content.setGeometry(QtCore.QRect(0, 0, 491, 842))
self.content.setGeometry(QtCore.QRect(0, 0, 497, 834))
self.content.setObjectName("content")
self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.content)
self.verticalLayout_5.setContentsMargins(0, 0, 0, 0)
self.verticalLayout_5.setObjectName("verticalLayout_5")
self.label = QtWidgets.QLabel(self.content)
self.label.setObjectName("label")
@ -111,9 +112,10 @@ class Ui_exporterSettings(object):
self.verticalLayout_5.addItem(spacerItem1)
self.toolBox.addItem(self.content, "")
self.separations = QtWidgets.QWidget()
self.separations.setGeometry(QtCore.QRect(0, 0, 511, 522))
self.separations.setGeometry(QtCore.QRect(0, 0, 511, 534))
self.separations.setObjectName("separations")
self.verticalLayout_8 = QtWidgets.QVBoxLayout(self.separations)
self.verticalLayout_8.setContentsMargins(0, 0, 0, 0)
self.verticalLayout_8.setObjectName("verticalLayout_8")
self.label_3 = QtWidgets.QLabel(self.separations)
font = QtGui.QFont()
@ -319,10 +321,11 @@ class Ui_exporterSettings(object):
self.verticalLayout_8.addItem(spacerItem6)
self.toolBox.addItem(self.separations, "")
self.transformations = QtWidgets.QWidget()
self.transformations.setGeometry(QtCore.QRect(0, 0, 511, 522))
self.transformations.setGeometry(QtCore.QRect(0, 0, 511, 534))
self.transformations.setStyleSheet("QGroupBox{font-weight:bold;}")
self.transformations.setObjectName("transformations")
self.verticalLayout_6 = QtWidgets.QVBoxLayout(self.transformations)
self.verticalLayout_6.setContentsMargins(0, 0, 0, 0)
self.verticalLayout_6.setObjectName("verticalLayout_6")
self.grpTransTypo = collapsibleGroupBox2(self.transformations)
self.grpTransTypo.setStyleSheet("")
@ -481,10 +484,11 @@ class Ui_exporterSettings(object):
self.verticalLayout_6.addItem(spacerItem10)
self.toolBox.addItem(self.transformations, "")
self.preview = QtWidgets.QWidget()
self.preview.setGeometry(QtCore.QRect(0, 0, 511, 522))
self.preview.setGeometry(QtCore.QRect(0, 0, 511, 534))
self.preview.setStyleSheet("QGroupBox{font-weight:bold;}")
self.preview.setObjectName("preview")
self.verticalLayout_11 = QtWidgets.QVBoxLayout(self.preview)
self.verticalLayout_11.setContentsMargins(0, 0, 0, 0)
self.verticalLayout_11.setObjectName("verticalLayout_11")
self.groupBox = QtWidgets.QGroupBox(self.preview)
self.groupBox.setObjectName("groupBox")

View file

@ -53,8 +53,8 @@ QToolBox::tab:selected, QToolBox::tab:hover{
<rect>
<x>0</x>
<y>0</y>
<width>491</width>
<height>842</height>
<width>497</width>
<height>834</height>
</rect>
</property>
<attribute name="label">
@ -233,7 +233,7 @@ QToolBox::tab:selected, QToolBox::tab:hover{
<x>0</x>
<y>0</y>
<width>511</width>
<height>522</height>
<height>534</height>
</rect>
</property>
<attribute name="label">
@ -773,7 +773,7 @@ QToolBox::tab:selected, QToolBox::tab:hover{
<x>0</x>
<y>0</y>
<width>511</width>
<height>522</height>
<height>534</height>
</rect>
</property>
<property name="styleSheet">
@ -1162,7 +1162,7 @@ QToolBox::tab:selected, QToolBox::tab:hover{
<x>0</x>
<y>0</y>
<width>511</width>
<height>522</height>
<height>534</height>
</rect>
</property>
<property name="styleSheet">

View file

View file

@ -0,0 +1,90 @@
#!/usr/bin/env python
# --!-- coding: utf8 --!--
import json
import os
from PyQt5.QtCore import Qt, QSize, QSortFilterProxyModel, QModelIndex
from PyQt5.QtGui import QIcon, QFontMetrics, QFont
from PyQt5.QtWidgets import QWidget, QTableWidgetItem, QListWidgetItem, QTreeView
from manuskript.functions import mainWindow, writablePath
from manuskript.ui.importers.generalSettings_ui import Ui_generalSettings
from manuskript.enums import Outline
from manuskript.ui import style
class generalSettings(QWidget, Ui_generalSettings):
def __init__(self, parent=None):
QWidget.__init__(self, parent)
self.setupUi(self)
self.mw = mainWindow()
self.txtGeneralSplitScenes.setStyleSheet(style.lineEditSS())
# TreeView to select parent
# We use a proxy to display only folders
proxy = QSortFilterProxyModel()
proxy.setFilterKeyColumn(Outline.type.value)
proxy.setFilterFixedString("folder")
proxy.setSourceModel(self.mw.mdlOutline)
self.treeGeneralParent.setModel(proxy)
for i in range(1, self.mw.mdlOutline.columnCount()):
self.treeGeneralParent.hideColumn(i)
self.treeGeneralParent.setCurrentIndex(self.getParentIndex())
self.chkGeneralParent.toggled.connect(self.treeGeneralParent.setVisible)
self.treeGeneralParent.hide()
def getParentIndex(self):
"""
Returns the currently selected index in the mainWindow.
"""
if len(self.mw.treeRedacOutline.selectionModel().
selection().indexes()) == 0:
idx = QModelIndex()
else:
idx = self.mw.treeRedacOutline.currentIndex()
return idx
def importUnderID(self):
"""
Returns the ID of the item selected in treeGeneralParent, if checked.
"""
if self.chkGeneralParent.isChecked():
idx = self.treeGeneralParent.currentIndex()
# We used a filter proxy model, so we have to map back to source
# to get an index from mdlOutline
idx = self.treeGeneralParent.model().mapToSource(idx)
if idx.isValid():
return idx.internalPointer().ID()
return "0" # 0 is root's ID
def importInTopLevelFolder(self):
"""
Should the import be flat in the parent folder, or create a top-level
folder?
"""
return self.chkGeneralTopLevel.isChecked()
def trimLongTitles(self):
return self.chkGeneralTrimTitles.isChecked()
def splitScenes(self):
"""
Return wheter the user wants to split scenes.
If unchecked, returns False.
If checked, returns the escaped split mark, or default (in placeholderText).
"""
if self.chkGeneralSplitScenes.isChecked():
split = self.txtGeneralSplitScenes.text()
if not split:
split = self.txtGeneralSplitScenes.placeholderText()
split = split.replace("\\n", "\n")
split = split.replace("\\t", "\t")
return split
else:
return False

View file

@ -0,0 +1,77 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'manuskript/ui/importers/generalSettings_ui.ui'
#
# Created by: PyQt5 UI code generator 5.9
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_generalSettings(object):
def setupUi(self, generalSettings):
generalSettings.setObjectName("generalSettings")
generalSettings.resize(267, 401)
self.verticalLayout_2 = QtWidgets.QVBoxLayout(generalSettings)
self.verticalLayout_2.setContentsMargins(0, 0, 0, 0)
self.verticalLayout_2.setSpacing(10)
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.toolBox = QtWidgets.QToolBox(generalSettings)
self.toolBox.setStyleSheet("QToolBox::tab{\n"
" background-color: #BBB;\n"
" padding: 2px;\n"
" border: none;\n"
"}\n"
"\n"
"QToolBox::tab:selected, QToolBox::tab:hover{\n"
" background-color:skyblue;\n"
"}")
self.toolBox.setObjectName("toolBox")
self.general = QtWidgets.QWidget()
self.general.setGeometry(QtCore.QRect(0, 0, 267, 378))
self.general.setObjectName("general")
self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.general)
self.verticalLayout_5.setContentsMargins(6, 6, 6, 6)
self.verticalLayout_5.setObjectName("verticalLayout_5")
self.formLayout_4 = QtWidgets.QFormLayout()
self.formLayout_4.setRowWrapPolicy(QtWidgets.QFormLayout.WrapLongRows)
self.formLayout_4.setObjectName("formLayout_4")
self.chkGeneralSplitScenes = QtWidgets.QCheckBox(self.general)
self.chkGeneralSplitScenes.setObjectName("chkGeneralSplitScenes")
self.formLayout_4.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.chkGeneralSplitScenes)
self.txtGeneralSplitScenes = QtWidgets.QLineEdit(self.general)
self.txtGeneralSplitScenes.setText("")
self.txtGeneralSplitScenes.setObjectName("txtGeneralSplitScenes")
self.formLayout_4.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.txtGeneralSplitScenes)
self.chkGeneralTrimTitles = QtWidgets.QCheckBox(self.general)
self.chkGeneralTrimTitles.setObjectName("chkGeneralTrimTitles")
self.formLayout_4.setWidget(4, QtWidgets.QFormLayout.SpanningRole, self.chkGeneralTrimTitles)
self.treeGeneralParent = QtWidgets.QTreeView(self.general)
self.treeGeneralParent.setHeaderHidden(True)
self.treeGeneralParent.setObjectName("treeGeneralParent")
self.formLayout_4.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.treeGeneralParent)
self.chkGeneralParent = QtWidgets.QCheckBox(self.general)
self.chkGeneralParent.setObjectName("chkGeneralParent")
self.formLayout_4.setWidget(0, QtWidgets.QFormLayout.SpanningRole, self.chkGeneralParent)
self.chkGeneralTopLevel = QtWidgets.QCheckBox(self.general)
self.chkGeneralTopLevel.setObjectName("chkGeneralTopLevel")
self.formLayout_4.setWidget(2, QtWidgets.QFormLayout.SpanningRole, self.chkGeneralTopLevel)
self.verticalLayout_5.addLayout(self.formLayout_4)
self.toolBox.addItem(self.general, "")
self.verticalLayout_2.addWidget(self.toolBox)
self.retranslateUi(generalSettings)
self.toolBox.setCurrentIndex(0)
self.toolBox.layout().setSpacing(0)
QtCore.QMetaObject.connectSlotsByName(generalSettings)
def retranslateUi(self, generalSettings):
_translate = QtCore.QCoreApplication.translate
generalSettings.setWindowTitle(_translate("generalSettings", "Form"))
self.chkGeneralSplitScenes.setText(_translate("generalSettings", "Split scenes at:"))
self.txtGeneralSplitScenes.setPlaceholderText(_translate("generalSettings", "\\n---\\n"))
self.chkGeneralTrimTitles.setText(_translate("generalSettings", "Trim long titles (> 32 chars)"))
self.chkGeneralParent.setText(_translate("generalSettings", "Import under:"))
self.chkGeneralTopLevel.setText(_translate("generalSettings", "Import in a top-level folder"))
self.toolBox.setItemText(self.toolBox.indexOf(self.general), _translate("generalSettings", "General"))

View file

@ -0,0 +1,136 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>generalSettings</class>
<widget class="QWidget" name="generalSettings">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>267</width>
<height>401</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="spacing">
<number>10</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QToolBox" name="toolBox">
<property name="styleSheet">
<string notr="true">QToolBox::tab{
background-color: #BBB;
padding: 2px;
border: none;
}
QToolBox::tab:selected, QToolBox::tab:hover{
background-color:skyblue;
}</string>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<property name="tabSpacing">
<number>0</number>
</property>
<widget class="QWidget" name="general">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>267</width>
<height>378</height>
</rect>
</property>
<attribute name="label">
<string>General</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_5">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item>
<layout class="QFormLayout" name="formLayout_4">
<property name="rowWrapPolicy">
<enum>QFormLayout::WrapLongRows</enum>
</property>
<item row="3" column="0">
<widget class="QCheckBox" name="chkGeneralSplitScenes">
<property name="text">
<string>Split scenes at:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="txtGeneralSplitScenes">
<property name="text">
<string/>
</property>
<property name="placeholderText">
<string>\n---\n</string>
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<widget class="QCheckBox" name="chkGeneralTrimTitles">
<property name="text">
<string>Trim long titles (&gt; 32 chars)</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QTreeView" name="treeGeneralParent">
<property name="headerHidden">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="0" colspan="2">
<widget class="QCheckBox" name="chkGeneralParent">
<property name="text">
<string>Import under:</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QCheckBox" name="chkGeneralTopLevel">
<property name="text">
<string>Import in a top-level folder</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View file

@ -0,0 +1,320 @@
#!/usr/bin/env python
# --!-- coding: utf8 --!--
import json
import os
from PyQt5.QtCore import Qt, QTimer, QUrl
from PyQt5.QtGui import QBrush, QColor, QIcon, QDesktopServices
from PyQt5.QtWidgets import QWidget, QFileDialog, QMessageBox, QStyle
from manuskript.functions import lightBlue, writablePath, appPath
from manuskript.ui.importers.importer_ui import Ui_importer
from manuskript.ui.importers.generalSettings import generalSettings
from manuskript.ui import style
from manuskript import importer
from manuskript.models.outlineModel import outlineModel, outlineItem
from manuskript.enums import Outline
from manuskript.exporter.pandoc import pandocExporter
class importerDialog(QWidget, Ui_importer):
formatsIcon = {
".epub": "application-epub+zip",
".odt": "application-vnd.oasis.opendocument.text",
".docx": "application-vnd.openxmlformats-officedocument.wordprocessingml.document",
".md": "text-x-markdown",
".rst": "text-plain",
".tex": "text-x-tex",
".opml": "text-x-opml+xml",
".xml": "text-x-opml+xml",
".html": "text-html",
}
def __init__(self, parent=None, mw=None):
QWidget.__init__(self, parent)
self.setupUi(self)
# Var
self.mw = mw
self.fileName = ""
self.setStyleSheet(style.mainWindowSS())
self.tree.setStyleSheet("QTreeView{background:transparent;}")
self.editor.setStyleSheet("QWidget{background:transparent;}")
self.editor.toggleSpellcheck(False)
# Register importFormats:
self.importers = importer.importers
# Populate combo box with formats
self.populateImportList()
# Connections
self.btnChoseFile.clicked.connect(self.selectFile)
self.btnClearFileName.clicked.connect(self.setFileName)
self.btnPreview.clicked.connect(self.preview)
self.btnImport.clicked.connect(self.doImport)
self.cmbImporters.currentTextChanged.connect(self.updateSettings)
self.setFileName("")
self.updateSettings()
############################################################################
# Combobox / Formats
############################################################################
def populateImportList(self):
def addFormat(name, icon, identifier):
# Identifier serves to distingues 2 importers that would have the
# same name.
self.cmbImporters.addItem(QIcon.fromTheme(icon), name, identifier)
def addHeader(name):
self.cmbImporters.addItem(name, "header")
self.cmbImporters.setItemData(self.cmbImporters.count() - 1, QBrush(QColor(Qt.darkBlue)), Qt.ForegroundRole)
self.cmbImporters.setItemData(self.cmbImporters.count() - 1, QBrush(lightBlue()), Qt.BackgroundRole)
item = self.cmbImporters.model().item(self.cmbImporters.count() - 1)
item.setFlags(Qt.ItemIsEnabled)
lastEngine = ""
for f in self.importers:
# Header
if f.engine != lastEngine:
addHeader(f.engine)
lastEngine = f.engine
addFormat(f.name, f.icon, "{}:{}".format(f.engine, f.name))
if not f.isValid():
item = self.cmbImporters.model().item(self.cmbImporters.count() - 1)
item.setFlags(Qt.NoItemFlags)
if not pandocExporter().isValid():
self.cmbImporters.addItem(
self.style().standardIcon(QStyle.SP_MessageBoxWarning),
"Install pandoc to import from much more formats",
"::URL::http://pandoc.org/installing.html")
self.cmbImporters.setCurrentIndex(1)
def currentFormat(self):
formatIdentifier = self.cmbImporters.currentData()
if formatIdentifier == "header":
return None
F = [F for F in self.importers
if formatIdentifier == "{}:{}".format(F.engine, F.name)][0]
# We instantiate the class
return F()
############################################################################
# Import file
############################################################################
def selectFile(self):
"""
Called to select a file in the file system. Uses QFileDialog.
"""
# We find the current selected format
F = self._format
options = QFileDialog.Options()
options |= QFileDialog.DontUseNativeDialog
if F.fileFormat == "<<folder>>":
options = QFileDialog.DontUseNativeDialog | QFileDialog.ShowDirsOnly
fileName = QFileDialog.getExistingDirectory(self, "Select import folder",
"", options=options)
else:
fileName, _ = QFileDialog.getOpenFileName(self, "Import from file", "",
F.fileFormat, options=options)
self.setFileName(fileName)
def setFileName(self, fileName):
"""
Updates Ui with given filename. Filename can be empty.
"""
if fileName:
self.fileName = fileName
self.lblFileName.setText(os.path.basename(fileName))
self.lblFileName.setToolTip(fileName)
ext = os.path.splitext(fileName)[1]
icon = None
if ext and ext in self.formatsIcon:
icon = QIcon.fromTheme(self.formatsIcon[ext])
elif os.path.isdir(fileName):
icon = QIcon.fromTheme("folder")
if icon:
self.lblIcon.setVisible(True)
h = self.lblFileName.height()
self.lblIcon.setPixmap(icon.pixmap(h, h))
else:
self.lblIcon.hide()
else:
self.fileName = None
self.lblFileName.setText("")
hasFile = True if fileName else False
self.btnClearFileName.setVisible(hasFile)
self.lblIcon.setVisible(hasFile)
self.btnChoseFile.setVisible(not hasFile)
self.btnPreview.setEnabled(hasFile)
self.btnImport.setEnabled(hasFile)
############################################################################
# UI
############################################################################
def updateSettings(self):
"""
When the current format change (through the combobox), we update the
settings widget using the current format provided settings widget.
"""
# We check if we have to open an URL
data = self.cmbImporters.currentData()
if data and data[:7] == "::URL::" and data[7:]:
# FIXME: use functions.openURL after merge with feature/Exporters
QDesktopServices.openUrl(QUrl(data[7:]))
return
F = self.currentFormat()
self._format = F
# Checking if we have a valid importer (otherwise a header)
if not F:
self.grpSettings.setEnabled(False)
self.grpPreview.setEnabled(False)
return
self.grpSettings.setEnabled(True)
self.grpPreview.setEnabled(True)
self.settingsWidget = generalSettings()
#TODO: custom format widget
self.settingsWidget = F.settingsWidget(self.settingsWidget)
# Set the settings widget in place
self.setGroupWidget(self.grpSettings, self.settingsWidget)
self.grpSettings.setMinimumWidth(200)
# Clear file name
self.setFileName("")
def setGroupWidget(self, group, widget):
"""
Sets the given widget as main widget for QGroupBox group.
"""
# Removes every items from given layout.
l = group.layout()
while l.count():
item = l.itemAt(0)
l.removeItem(item)
item.widget().deleteLater()
l.addWidget(widget)
widget.setParent(group)
############################################################################
# Preview / Import
############################################################################
def preview(self):
# Creating a temporary outlineModel
previewModel = outlineModel(self)
previewModel.loadFromXML(
self.mw.mdlOutline.saveToXML(),
fromString=True)
# Inserting elements
result = self.startImport(previewModel)
if result:
self.tree.setModel(previewModel)
for i in range(1, previewModel.columnCount()):
self.tree.hideColumn(i)
self.tree.selectionModel().currentChanged.connect(self.editor.setCurrentModelIndex)
self.previewSplitter.setStretchFactor(0, 10)
self.previewSplitter.setStretchFactor(1, 40)
def doImport(self):
"""
Called by the Import button.
"""
self.startImport(self.mw.mdlOutline)
# Signal every views that important model changes have happened.
self.mw.mdlOutline.layoutChanged.emit()
# I'm getting seg fault over this message sometimes...
# Using status bar message instead...
#QMessageBox.information(self, self.tr("Import status"),
#self.tr("Import Complete."))
self.mw.statusBar().showMessage("Import complete!", 5000)
self.close()
def startImport(self, outlineModel):
"""
Where most of the magic happens.
Is used by preview and by doImport (actual import).
`outlineModel` is the model where the imported items are added.
FIXME: Optimisation: when adding many outlineItems, outlineItem.updateWordCount
is a bottleneck. It gets called a crazy number of time, and its not
necessary.
"""
items = []
# We find the current selected format
F = self._format
# Parent item
ID = self.settingsWidget.importUnderID()
parentItem = outlineModel.getItemByID(ID)
# Import in top-level folder?
if self.settingsWidget.importInTopLevelFolder():
parent = outlineItem(title=os.path.basename(self.fileName),
parent=parentItem)
parentItem = parent
items.append(parent)
# Calling the importer
rItems = F.startImport(self.fileName,
parentItem,
self.settingsWidget)
items.extend(rItems)
# Do transformations
items = self.doTransformations(items)
return True
def doTransformations(self, items):
"""
Do general transformations.
"""
# Trim long titles
if self.settingsWidget.trimLongTitles():
for item in items:
if len(item.title()) > 32:
item.setData(Outline.title.value, item.title()[:32])
# Split at
if self.settingsWidget.splitScenes():
for item in items:
item.split(self.settingsWidget.splitScenes(), recursive=False)
return items

View file

@ -0,0 +1,107 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'manuskript/ui/importers/importer_ui.ui'
#
# Created by: PyQt5 UI code generator 5.9
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_importer(object):
def setupUi(self, importer):
importer.setObjectName("importer")
importer.resize(867, 560)
self.verticalLayout = QtWidgets.QVBoxLayout(importer)
self.verticalLayout.setObjectName("verticalLayout")
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
self.label = QtWidgets.QLabel(importer)
self.label.setObjectName("label")
self.horizontalLayout.addWidget(self.label)
self.cmbImporters = QtWidgets.QComboBox(importer)
self.cmbImporters.setObjectName("cmbImporters")
self.horizontalLayout.addWidget(self.cmbImporters)
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout.addItem(spacerItem)
self.btnChoseFile = QtWidgets.QPushButton(importer)
icon = QtGui.QIcon.fromTheme("document-import")
self.btnChoseFile.setIcon(icon)
self.btnChoseFile.setObjectName("btnChoseFile")
self.horizontalLayout.addWidget(self.btnChoseFile)
self.lblIcon = QtWidgets.QLabel(importer)
self.lblIcon.setText("")
self.lblIcon.setObjectName("lblIcon")
self.horizontalLayout.addWidget(self.lblIcon)
self.lblFileName = QtWidgets.QLabel(importer)
font = QtGui.QFont()
font.setBold(True)
font.setWeight(75)
self.lblFileName.setFont(font)
self.lblFileName.setText("")
self.lblFileName.setObjectName("lblFileName")
self.horizontalLayout.addWidget(self.lblFileName)
self.btnClearFileName = QtWidgets.QPushButton(importer)
self.btnClearFileName.setText("")
icon = QtGui.QIcon.fromTheme("edit-clear")
self.btnClearFileName.setIcon(icon)
self.btnClearFileName.setFlat(True)
self.btnClearFileName.setObjectName("btnClearFileName")
self.horizontalLayout.addWidget(self.btnClearFileName)
spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout.addItem(spacerItem1)
self.btnPreview = QtWidgets.QPushButton(importer)
icon = QtGui.QIcon.fromTheme("document-print-preview")
self.btnPreview.setIcon(icon)
self.btnPreview.setFlat(True)
self.btnPreview.setObjectName("btnPreview")
self.horizontalLayout.addWidget(self.btnPreview)
self.btnImport = QtWidgets.QPushButton(importer)
icon = QtGui.QIcon.fromTheme("document-import")
self.btnImport.setIcon(icon)
self.btnImport.setObjectName("btnImport")
self.horizontalLayout.addWidget(self.btnImport)
self.verticalLayout.addLayout(self.horizontalLayout)
self.splitter = QtWidgets.QSplitter(importer)
self.splitter.setOrientation(QtCore.Qt.Horizontal)
self.splitter.setChildrenCollapsible(False)
self.splitter.setObjectName("splitter")
self.grpSettings = QtWidgets.QGroupBox(self.splitter)
self.grpSettings.setObjectName("grpSettings")
self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.grpSettings)
self.verticalLayout_3.setContentsMargins(0, 0, 0, 0)
self.verticalLayout_3.setSpacing(0)
self.verticalLayout_3.setObjectName("verticalLayout_3")
self.grpPreview = QtWidgets.QGroupBox(self.splitter)
self.grpPreview.setObjectName("grpPreview")
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.grpPreview)
self.verticalLayout_2.setContentsMargins(0, 0, 0, 0)
self.verticalLayout_2.setSpacing(0)
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.previewSplitter = QtWidgets.QSplitter(self.grpPreview)
self.previewSplitter.setOrientation(QtCore.Qt.Horizontal)
self.previewSplitter.setObjectName("previewSplitter")
self.tree = QtWidgets.QTreeView(self.previewSplitter)
self.tree.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
self.tree.setHeaderHidden(True)
self.tree.setObjectName("tree")
self.editor = editorWidget(self.previewSplitter)
self.editor.setObjectName("editor")
self.verticalLayout_2.addWidget(self.previewSplitter)
self.verticalLayout.addWidget(self.splitter)
self.retranslateUi(importer)
QtCore.QMetaObject.connectSlotsByName(importer)
def retranslateUi(self, importer):
_translate = QtCore.QCoreApplication.translate
importer.setWindowTitle(_translate("importer", "Import"))
self.label.setText(_translate("importer", "Format:"))
self.btnChoseFile.setText(_translate("importer", "Chose file"))
self.btnClearFileName.setToolTip(_translate("importer", "Clear file"))
self.btnPreview.setText(_translate("importer", "Preview"))
self.btnImport.setText(_translate("importer", "Import"))
self.grpSettings.setTitle(_translate("importer", "Settings"))
self.grpPreview.setTitle(_translate("importer", "Preview"))
from manuskript.ui.editors.editorWidget import editorWidget

View file

@ -0,0 +1,211 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>importer</class>
<widget class="QWidget" name="importer">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>867</width>
<height>560</height>
</rect>
</property>
<property name="windowTitle">
<string>Import</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Format:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="cmbImporters"/>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="btnChoseFile">
<property name="text">
<string>Chose file</string>
</property>
<property name="icon">
<iconset theme="document-import">
<normaloff>../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../.designer/backup</iconset>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="lblIcon">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="lblFileName">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnClearFileName">
<property name="toolTip">
<string>Clear file</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset theme="edit-clear">
<normaloff>../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../.designer/backup</iconset>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="btnPreview">
<property name="text">
<string>Preview</string>
</property>
<property name="icon">
<iconset theme="document-print-preview">
<normaloff>../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../.designer/backup</iconset>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnImport">
<property name="text">
<string>Import</string>
</property>
<property name="icon">
<iconset theme="document-import"/>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="childrenCollapsible">
<bool>false</bool>
</property>
<widget class="QGroupBox" name="grpSettings">
<property name="title">
<string>Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
</layout>
</widget>
<widget class="QGroupBox" name="grpPreview">
<property name="title">
<string>Preview</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QSplitter" name="previewSplitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QTreeView" name="tree">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="headerHidden">
<bool>true</bool>
</property>
</widget>
<widget class="editorWidget" name="editor" native="true"/>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>editorWidget</class>
<extends>QWidget</extends>
<header>manuskript.ui.editors.editorWidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View file

@ -1189,12 +1189,17 @@ class Ui_MainWindow(object):
icon = QtGui.QIcon.fromTheme("stock_view-details")
self.actAbout.setIcon(icon)
self.actAbout.setObjectName("actAbout")
self.actImport = QtWidgets.QAction(MainWindow)
icon = QtGui.QIcon.fromTheme("document-import")
self.actImport.setIcon(icon)
self.actImport.setObjectName("actImport")
self.menuFile.addAction(self.actOpen)
self.menuFile.addAction(self.menuRecents.menuAction())
self.menuFile.addAction(self.actSave)
self.menuFile.addAction(self.actSaveAs)
self.menuFile.addAction(self.actCloseProject)
self.menuFile.addSeparator()
self.menuFile.addAction(self.actImport)
self.menuFile.addAction(self.actCompile)
self.menuFile.addSeparator()
self.menuFile.addAction(self.actQuit)
@ -1360,6 +1365,8 @@ class Ui_MainWindow(object):
self.actToolFrequency.setText(_translate("MainWindow", "&Frequency Analyzer"))
self.actAbout.setText(_translate("MainWindow", "&About"))
self.actAbout.setToolTip(_translate("MainWindow", "About Manuskript"))
self.actImport.setText(_translate("MainWindow", "Import…"))
self.actImport.setShortcut(_translate("MainWindow", "F7"))
from manuskript.ui.cheatSheet import cheatSheet
from manuskript.ui.editors.mainEditor import mainEditor

View file

@ -2116,6 +2116,7 @@
<addaction name="actSaveAs"/>
<addaction name="actCloseProject"/>
<addaction name="separator"/>
<addaction name="actImport"/>
<addaction name="actCompile"/>
<addaction name="separator"/>
<addaction name="actQuit"/>
@ -2482,6 +2483,17 @@ QListView::item:hover {
<string>About Manuskript</string>
</property>
</action>
<action name="actImport">
<property name="icon">
<iconset theme="document-import"/>
</property>
<property name="text">
<string>Import…</string>
</property>
<property name="shortcut">
<string>F7</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>

View file

@ -1,7 +1,7 @@
#!/usr/bin/env python
# --!-- coding: utf8 --!--
from PyQt5.QtCore import Qt, QSignalMapper, QSize
from PyQt5.QtGui import QIcon
from PyQt5.QtGui import QIcon, QCursor
from PyQt5.QtWidgets import QAbstractItemView, qApp, QMenu, QAction
from PyQt5.QtWidgets import QListWidget, QWidgetAction, QListWidgetItem, QLineEdit
@ -14,7 +14,7 @@ from manuskript.models.outlineModel import outlineItem
class outlineBasics(QAbstractItemView):
def __init__(self, parent=None):
pass
self._indexesToOpen = None
def getSelection(self):
sel = []
@ -39,11 +39,44 @@ class outlineBasics(QAbstractItemView):
menu = QMenu(self)
# Open items
self.actOpen = QAction(QIcon.fromTheme("go-right"), qApp.translate("outlineBasics", "Open Item"), menu)
# 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 = self.tr("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 = self.tr("Open {} items in new tabs").format(len(sel))
self._indexesToOpen = sel
else:
actionTitle = self.tr("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()
# Rename / add / remove items
@ -185,7 +218,6 @@ class outlineBasics(QAbstractItemView):
self.actAddText.setEnabled(False)
if len(sel) == 0:
self.actOpen.setEnabled(False)
self.actCopy.setEnabled(False)
self.actCut.setEnabled(False)
self.actRename.setEnabled(False)
@ -201,10 +233,15 @@ class outlineBasics(QAbstractItemView):
return menu
def openItem(self):
idx = self.currentIndex()
#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()

View file

@ -31,30 +31,13 @@ class treeView(QTreeView, dndView, outlineBasics):
def makePopupMenu(self):
menu = outlineBasics.makePopupMenu(self)
first = menu.actions()[0]
first = menu.actions()[3]
# Open item in new tab
sel = self.selectedIndexes()
#sel = self.selectedIndexes()
pos = self.viewport().mapFromGlobal(QCursor.pos())
mouseIndex = self.indexAt(pos)
if mouseIndex.isValid():
mouseTitle = mouseIndex.internalPointer().title()
else:
mouseTitle = self.tr("Root")
if mouseIndex in sel and len(sel) > 1:
actionTitle = self.tr("Open {} items in new tabs").format(len(sel))
self._indexesToOpen = sel
else:
actionTitle = self.tr("Open {} in a new tab").format(mouseTitle)
self._indexesToOpen = [mouseIndex]
self.actNewTab = QAction(actionTitle, menu)
self.actNewTab.triggered.connect(self.openNewTab)
menu.insertAction(first, self.actNewTab)
menu.insertSeparator(first)
# Expand /collapse item
if mouseIndex.isValid():
# index = self.currentIndex()
@ -83,9 +66,6 @@ class treeView(QTreeView, dndView, outlineBasics):
return menu
def openNewTab(self):
mainWindow().mainEditor.openIndexes(self._indexesToOpen, newTab=True)
def expandCurrentIndex(self, index=None):
if index is None or type(index) == bool:
index = self._indexesToOpen[0] # self.currentIndex()