Adds: ability to import from folder structure. #200

This commit is contained in:
Olivier Keshavjee 2017-11-08 21:35:26 +01:00
parent ccf33b3ccf
commit c6391e976c
5 changed files with 307 additions and 18 deletions

View file

@ -5,7 +5,10 @@ import shutil
import subprocess
from PyQt5.QtCore import QSettings
from PyQt5.QtWidgets import QWidget
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:
@ -22,8 +25,10 @@ class abstractImporter:
# For folder, use "<<folder>>"
icon = ""
@classmethod
def startImport(cls, filePath, parentItem, settingsWidget):
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,
@ -31,15 +36,161 @@ class abstractImporter:
"""
pass
@classmethod
def settingsWidget(cls):
"""
Returns a QWidget if needed for settings.
"""
return None
@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)
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

@ -1,7 +1,11 @@
#!/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):
@ -10,3 +14,122 @@ class folderImporter(abstractImporter):
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 = {}
if self.getSetting("topLevelFolder").value():
parent = outlineItem(title=os.path.basename(filePath),
parent=parentItem)
items.append(parent)
stack[filePath] = parent
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("topLevelFolder", "checkbox",
qApp.translate("Import", "Include top-level folder"),
default=False),
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

@ -895,7 +895,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():
@ -913,8 +913,9 @@ class outlineItem():
return IDs
def findUniqueID(self):
IDs = [int(i) for i in self.IDs]
k = 0
while str(k) in self.IDs:
while k in self.IDs:
k += 1
self.IDs.append(str(k))
return str(k)

View file

@ -10,6 +10,8 @@ from PyQt5.QtWidgets import QWidget, QTableWidgetItem, QListWidgetItem, QTreeVie
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):
@ -17,6 +19,7 @@ class generalSettings(QWidget, Ui_generalSettings):
self.setupUi(self)
self.mw = mainWindow()
self.txtGeneralSplitScenes.setStyleSheet(style.lineEditSS())
# TreeView to select parent
# We use a proxy to display only folders

View file

@ -82,7 +82,8 @@ class importerDialog(QWidget, Ui_importer):
def currentFormat(self):
formatName = self.cmbImporters.currentText()
F = [F for F in self.importers if F.name == formatName][0]
return F
# We instantiate the class
return F()
############################################################################
# Import file
@ -94,7 +95,7 @@ class importerDialog(QWidget, Ui_importer):
"""
# We find the current selected format
F = self.currentFormat()
F = self._format
options = QFileDialog.Options()
options |= QFileDialog.DontUseNativeDialog
@ -159,17 +160,21 @@ class importerDialog(QWidget, Ui_importer):
return
F = self.currentFormat()
self._format = F
self.settingsWidget = generalSettings()
self.setGroupWidget(self.grpSettings, self.settingsWidget)
self.grpSettings.setMinimumWidth(200)
#TODO: custom format widget
self.settingsWidget = F.settingsWidget(self.settingsWidget)
#toolBox = self.settingsWidget.toolBox
#w = QWidget()
#toolBox.insertItem(toolBox.count(), w, "Pandoc")
#See pandoc's abstractPlainText
# Set the settings widget in place
self.setGroupWidget(self.grpSettings, self.settingsWidget)
self.grpSettings.setMinimumWidth(200)
# Clear file name
self.setFileName("")
@ -234,10 +239,14 @@ class importerDialog(QWidget, Ui_importer):
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.
"""
# We find the current selected format
F = self.currentFormat()
F = self._format
# Parent item
ID = self.settingsWidget.importUnderID()
@ -263,6 +272,8 @@ class importerDialog(QWidget, Ui_importer):
def trim(item):
if len(item.title()) > 32:
item.setData(Outline.title.value, item.title()[:32])
# I think it's overkill to do it recursively, since items
# is supposed to contain every imported items.
for c in item.children():
trim(c)
for i in items: