manuskript/manuskript/ui/importers/importer.py
2017-11-15 20:34:05 +01:00

325 lines
11 KiB
Python

#!/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 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 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(style.highlightedTextDark)), Qt.ForegroundRole)
self.cmbImporters.setItemData(self.cmbImporters.count() - 1, QBrush(QColor(style.highlightLight)), 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)
self.preview()
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):
if not self.fileName:
return
# 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