From 982a96021b9128d5a5b1fff1a1cc58f2ed842f51 Mon Sep 17 00:00:00 2001 From: Cam Stevenson Date: Sun, 15 Oct 2017 17:25:09 -0400 Subject: [PATCH 01/65] Checkpoint: OPML import --- manuskript/import_export/__init__.py | 0 manuskript/import_export/opml.py | 44 ++++++++++++++++++++++++++++ manuskript/mainWindow.py | 12 ++++++-- manuskript/ui/mainWindow.py | 5 ++++ 4 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 manuskript/import_export/__init__.py create mode 100644 manuskript/import_export/opml.py diff --git a/manuskript/import_export/__init__.py b/manuskript/import_export/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/manuskript/import_export/opml.py b/manuskript/import_export/opml.py new file mode 100644 index 0000000..77e6d31 --- /dev/null +++ b/manuskript/import_export/opml.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- + +# Import/export outline cards in OPML format + +from manuskript.models.outlineModel import outlineItem +from manuskript.enums import Outline +import xmltodict +from manuskript.functions import mainWindow +import xml.etree.ElementTree as ET + + +def handleCard(_, card): + print(card['title']) + + +def exportOpml(): + return True + + +def importOpml(opmlFilePath): + with open(opmlFilePath, 'r') as opmlFile: + opmlContent = opmlFile.read() + + mw = mainWindow() + mdl = mw.mdlOutline + + dict = xmltodict.parse(opmlContent, item_callback=handleCard) + + opmlNode = dict['opml'] + bodyNode = opmlNode['body'] + + outline = bodyNode['outline'] + + for element in outline: + if '@text' in element: + card = outlineItem(parent=mdl.rootItem) + card.title = element['@text'] + card.ID = card.title + card.path = '' + if '@_note' in element: + card.setData(Outline.text.value, element['@_note']) + + return True diff --git a/manuskript/mainWindow.py b/manuskript/mainWindow.py index 3ab3b18..67d178b 100644 --- a/manuskript/mainWindow.py +++ b/manuskript/mainWindow.py @@ -26,6 +26,7 @@ from manuskript.ui.mainWindow import Ui_MainWindow from manuskript.ui.tools.frequencyAnalyzer import frequencyAnalyzer from manuskript.ui.views.outlineDelegates import outlineCharacterDelegate from manuskript.ui.views.plotDelegate import plotDelegate +from manuskript.import_export import opml as opmlInputExport # Spellcheck support from manuskript.ui.views.textEditView import textEditView @@ -94,13 +95,14 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.cmbSummary.currentIndexChanged.emit(0) # Main Menu - for i in [self.actSave, self.actSaveAs, self.actCloseProject, + for i in [self.actSave, self.actSaveAs, self.actImport, self.actCloseProject, self.menuEdit, self.menuView, self.menuTools, self.menuHelp]: 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.importOutline) self.actCompile.triggered.connect(self.doCompile) self.actLabels.triggered.connect(self.settingsLabel) self.actStatus.triggered.connect(self.settingsStatus) @@ -386,7 +388,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): # UI for i in [self.actOpen, self.menuRecents]: i.setEnabled(False) - for i in [self.actSave, self.actSaveAs, self.actCloseProject, + for i in [self.actSave, self.actSaveAs, self.actImport, self.actCloseProject, self.menuEdit, self.menuView, self.menuTools, self.menuHelp]: i.setEnabled(True) @@ -429,7 +431,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): # UI for i in [self.actOpen, self.menuRecents]: i.setEnabled(True) - for i in [self.actSave, self.actSaveAs, self.actCloseProject, + for i in [self.actSave, self.actSaveAs, self.actImport, self.actCloseProject, self.menuEdit, self.menuView, self.menuTools, self.menuHelp]: i.setEnabled(False) @@ -548,6 +550,10 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.statusBar().showMessage( self.tr("Project {} loaded with some errors.").format(project), 5000) + def importOutline(self, project): + opmlInputExport.importOpml('/home/cstevenson/End Plan 2.opml') + return True + ############################################################################### # MAIN CONNECTIONS ############################################################################### diff --git a/manuskript/ui/mainWindow.py b/manuskript/ui/mainWindow.py index 9c13e46..8b4ef49 100644 --- a/manuskript/ui/mainWindow.py +++ b/manuskript/ui/mainWindow.py @@ -1123,6 +1123,9 @@ class Ui_MainWindow(object): self.actSaveAs.setObjectName("actSaveAs") self.actQuit = QtWidgets.QAction(MainWindow) icon = QtGui.QIcon.fromTheme("application-exit") + self.actImport = QtWidgets.QAction(MainWindow) + self.actImport.setIcon(icon) + self.actImport.setObjectName("actImport") self.actQuit.setIcon(icon) self.actQuit.setObjectName("actQuit") self.actShowHelp = QtWidgets.QAction(MainWindow) @@ -1175,6 +1178,7 @@ class Ui_MainWindow(object): self.menuFile.addAction(self.menuRecents.menuAction()) self.menuFile.addAction(self.actSave) self.menuFile.addAction(self.actSaveAs) + self.menuFile.addAction(self.actImport) self.menuFile.addAction(self.actCloseProject) self.menuFile.addSeparator() self.menuFile.addAction(self.actCompile) @@ -1316,6 +1320,7 @@ class Ui_MainWindow(object): self.actSave.setShortcut(_translate("MainWindow", "Ctrl+S")) self.actSaveAs.setText(_translate("MainWindow", "Sa&ve as...")) self.actSaveAs.setShortcut(_translate("MainWindow", "Ctrl+Shift+S")) + self.actImport.setText("Import") self.actQuit.setText(_translate("MainWindow", "&Quit")) self.actQuit.setShortcut(_translate("MainWindow", "Ctrl+Q")) self.actShowHelp.setText(_translate("MainWindow", "&Show help texts")) From bdc6a096f2c1267b9703f0397aaee2d1e792eb40 Mon Sep 17 00:00:00 2001 From: Cam Stevenson Date: Sat, 21 Oct 2017 16:29:17 -0400 Subject: [PATCH 02/65] Checkpoint: OPML import --- manuskript/import_export/opml.py | 49 +++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/manuskript/import_export/opml.py b/manuskript/import_export/opml.py index 77e6d31..fe90fc6 100644 --- a/manuskript/import_export/opml.py +++ b/manuskript/import_export/opml.py @@ -7,7 +7,6 @@ from manuskript.models.outlineModel import outlineItem from manuskript.enums import Outline import xmltodict from manuskript.functions import mainWindow -import xml.etree.ElementTree as ET def handleCard(_, card): @@ -20,12 +19,12 @@ def exportOpml(): def importOpml(opmlFilePath): with open(opmlFilePath, 'r') as opmlFile: - opmlContent = opmlFile.read() + opmlContent = saveNewlines(opmlFile.read()) mw = mainWindow() mdl = mw.mdlOutline - dict = xmltodict.parse(opmlContent, item_callback=handleCard) + dict = xmltodict.parse(opmlContent, strip_whitespace=False) opmlNode = dict['opml'] bodyNode = opmlNode['body'] @@ -33,12 +32,42 @@ def importOpml(opmlFilePath): outline = bodyNode['outline'] for element in outline: - if '@text' in element: - card = outlineItem(parent=mdl.rootItem) - card.title = element['@text'] - card.ID = card.title - card.path = '' - if '@_note' in element: - card.setData(Outline.text.value, element['@_note']) + parseItems(underElement=element, parentItem=mdl.rootItem) return True + +def parseItems(underElement, parentItem): + if '@text' in underElement: + card = outlineItem(parent=parentItem, title=underElement['@text']) + + text = "" + summary = "" + if '@_note' in underElement: + text = restoreNewLines(underElement['@_note']) + summary = text[0:128] + + card.setData(Outline.summaryFull.value, summary) + + if 'outline' in underElement: + elements = underElement['outline'] + + for el in elements: + parseItems(el, card) + else: + card.setData(Outline.type.value, 'md') + card.setData(Outline.text.value, text) + + # I assume I don't have to do the following + # parentItem.appendChild(card) + + return + +def saveNewlines(inString): + inString = inString.replace("\r\n", "\n") + inString = inString.replace("\n", "{{lf}}") + + return inString + +def restoreNewLines(inString): + return inString.replace("{{lf}}", "\n") + From 44db1a59893d8a0f5baa7b3682fa9618ce4ed795 Mon Sep 17 00:00:00 2001 From: Cam Stevenson Date: Sun, 22 Oct 2017 10:21:39 -0400 Subject: [PATCH 03/65] Checkpoint: functional OPML import --- manuskript/import_export/opml.py | 14 +++++++++----- manuskript/mainWindow.py | 6 +++--- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/manuskript/import_export/opml.py b/manuskript/import_export/opml.py index fe90fc6..e178ddc 100644 --- a/manuskript/import_export/opml.py +++ b/manuskript/import_export/opml.py @@ -7,10 +7,7 @@ from manuskript.models.outlineModel import outlineItem from manuskript.enums import Outline import xmltodict from manuskript.functions import mainWindow - - -def handleCard(_, card): - print(card['title']) +from PyQt5.QtCore import QModelIndex def exportOpml(): @@ -32,10 +29,15 @@ def importOpml(opmlFilePath): outline = bodyNode['outline'] for element in outline: - parseItems(underElement=element, parentItem=mdl.rootItem) + parseItems(element, mdl.rootItem) + + mdl.layoutChanged.emit() + + mw.treeRedacOutline.viewport().update() return True + def parseItems(underElement, parentItem): if '@text' in underElement: card = outlineItem(parent=parentItem, title=underElement['@text']) @@ -62,12 +64,14 @@ def parseItems(underElement, parentItem): return + def saveNewlines(inString): inString = inString.replace("\r\n", "\n") inString = inString.replace("\n", "{{lf}}") return inString + def restoreNewLines(inString): return inString.replace("{{lf}}", "\n") diff --git a/manuskript/mainWindow.py b/manuskript/mainWindow.py index 9401c61..92850ff 100644 --- a/manuskript/mainWindow.py +++ b/manuskript/mainWindow.py @@ -98,7 +98,7 @@ 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.actCompile, self.actImport, self.actSettings]: i.setEnabled(False) self.actOpen.triggered.connect(self.welcome.openFile) @@ -447,7 +447,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.actCompile, self.actImport, self.actSettings]: i.setEnabled(True) # Add project name to Window's name @@ -491,7 +491,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.actCompile, self.actImport, self.actSettings]: i.setEnabled(False) # Set Window's name - no project loaded From 2d020aba3a50359426428d123c6576044117cf80 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Wed, 1 Nov 2017 15:39:28 +0100 Subject: [PATCH 04/65] Updates Swedish translation #177 --- i18n/manuskript_sv.qm | Bin 62901 -> 64552 bytes i18n/manuskript_sv.ts | 708 +++++++++++++++++++++++------------------- 2 files changed, 382 insertions(+), 326 deletions(-) diff --git a/i18n/manuskript_sv.qm b/i18n/manuskript_sv.qm index 82eccc8e18a7d8d5f9dd0010df07ebb9d2293a5c..a4fd3705a3cb8adbc40251d2b2d66d24b1675345 100644 GIT binary patch delta 7204 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 delta 5892 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 Har}P)S=%ly diff --git a/i18n/manuskript_sv.ts b/i18n/manuskript_sv.ts index 92f2bc8..cb60a6e 100644 --- a/i18n/manuskript_sv.ts +++ b/i18n/manuskript_sv.ts @@ -8,7 +8,6 @@ <p>A superset of markdown.</p> <p>Website: <a href="http://fletcherpenney.net/multimarkdown/">http://fletcherpenney.net/multimarkdown/</a></p> - This makes limited sense in Swedish. <p>En supermängd till markdown.</p> <p>Websida: <a href="http://fletcherpenney.net/multimarkdown/">http://fletcherpenney.net/multimarkdown/</a></p> @@ -26,7 +25,6 @@ python module 'markdown'. - Not sure why this is repeated again. python-modulen 'markdown'. @@ -54,7 +52,6 @@ Preview with highlighter. - Unclear what highlighter is -- going with the physical version used to highlight text. Förhandsgranskning med märkpenna. @@ -77,7 +74,6 @@ A little known format modestly used. You know, web sites for example. - Is this sarcasm? :) Ett relativt okänt format som används blygsamt, t.ex. på websidor. @@ -92,14 +88,13 @@ - + Error Fel Books that don't kill trees. - What's the context here? It looks a bit random. :) Böcker som inte mördar träd. @@ -115,14 +110,12 @@ Needs latex to be installed. - Incorrect casing in English. Kräver att LaTex är installerat. a valid latex installation. See pandoc recommendations on: <a href="http://pandoc.org/installing.html">http://pandoc.org/installing.html</a>. If you want unicode support, you need xelatex. - This string seems incomplete in English? en giltig LaTex-installation. Se vad pandoc rekommenderar på: <a href="http://pandoc.org/installing.html">http://pandoc.org/installing.html</a>. Vill du ha stöd för unicode behöver du xelatex. @@ -184,50 +177,62 @@ Välj rotnivå för rubriker: - + Use reference-style links instead of inline links Använd referenslänkar i stället för infogade länkar - + Use ATX-style headers Använd ATX-rubriker - + Self-contained html files, with no dependencies Fristående HTML-filer utan andra beroenden - + Use <q> tags for quotes in HTML Använd <q>-taggar för citat i HTML - + LaTeX engine used to produce the PDF. LaTeX-motor som används för att skapa PDF-dokumentet. - + Paper size: Pappersstorlek: - + Font size: Teckenstorlek: - + Class: Klass: - + Line spacing: Radavstånd: + + + Disable YAML metadata block. +Use that if you get YAML related error. + Avaktivera YAML metadata block. +Använd detta om du får ett felmeddelande angående YAML. + + + + Convert to ePUB3 + Konvertera till ePUB3 + ExportersManager @@ -333,7 +338,7 @@ MainWindow - + General Allmänt @@ -378,7 +383,7 @@ Författare - + Name Namn @@ -388,7 +393,7 @@ E-mail - + Summary Sammanfattning @@ -398,7 +403,7 @@ Situation: - + Summary: Sammanfattning: @@ -408,17 +413,17 @@ En mening - + One paragraph Ett stycke - + One page En sida - + Full Full @@ -468,7 +473,7 @@ Namn - + Filter Filter @@ -523,7 +528,7 @@ Detaljerad info - + Plots Handlingar @@ -538,7 +543,7 @@ Karaktär(er) - + Description Beskrivning @@ -553,442 +558,459 @@ Lösningssteg - + World Värld - + Populates with empty data Fylls med tom data - + More Mer - + Source of passion Passionskälla - + Source of conflict Konfliktkälla - + Outline Utkast - + Editor Redigerare - + Debug - Placeholder? Debug - + FlatData - Placeholder? FlatData - + Persos - Placeholder? Persos - + Labels Etiketter - + &File &Arkiv - + &Recents S&enaste - + &Help &Hjälp - + &Tools &Verktyg - + &Edit &Redigera - + &View &Visa - + &Mode &Läge - + &Cheat sheet &Fusklapp - + Sea&rch S&ök - + &Navigation &Navigation - + &Open &Öppna - + Ctrl+O Ctrl+O - + &Save &Spara - + Ctrl+S Ctrl+S - + Sa&ve as... Spara s&om... - + Ctrl+Shift+S Ctrl+Shift+S - + &Quit &Avsluta - + Ctrl+Q Ctrl+Q - + &Show help texts &Visa hjälptexter - + Ctrl+Shift+B Ctrl+Shift+B - + &Spellcheck &Stavningskontroll - + F9 F9 - + &Labels... &Etiketter... - + &Status... &Status... - + Tree Träd - + &Simple &Enkelt - + &Fiction &Skönlitteratur - + S&nowflake S&nöflingemetoden - + Index cards Registerkort - + S&ettings I&nställningar - + F8 F8 - + &Close project S&täng projekt - + Co&mpile Ko&mpilera - + F6 F6 - + &Frequency Analyzer &Frekvensanalys - + &About &Om - + About Manuskript Om Manuskript - + The file {} does not exist. Try again. Filen {} finns inte. Försök igen. - + Manuskript Manuskript - + Project {} saved. Projekt {} sparades. - + WARNING: Project {} not saved. VARNING: Projekt {} sparades ej. - + Project {} loaded. Projekt {} laddades. - + Project {} loaded with some errors: Projekt {} laddades med vissa fel: - + * {} wasn't found in project file. * {} hittades inte i projektfilen. - + Project {} loaded with some errors. Projekt {} laddades med vissa fel. - + (~{} pages) (~{} sidor) - + Words: {}{} Ord: {}{} - + Book summary Sammanfattning av boken - + Project tree Projektträd - + Metadata Metadata - + Story line Handling - + Enter informations about your book, and yourself. Skriv information om din bok och dig själv. - + The basic situation, in the form of a 'What if...?' question. Ex: 'What if the most dangerous evil wizard could wasn't abled to kill a baby?' (Harry Potter) Den grundläggande situationen i form av en "Tänk om...?"-mening. Exempel: 'Tänk om världens farligaste onda trollkarl misslyckades med att döda en baby?' (Harry Potter) - + Take time to think about a one sentence (~50 words) summary of your book. Then expand it to a paragraph, then to a page, then to a full summary. Tänk ut en kort (ca. 50 ord) mening som sammanfattar din bok. Utveckla den sedan till ett stycke, till en sida och sist till en full sammanfattning. - + Create your characters. Skapa dina karaktärer. - + Develop plots. Utveckla handlingen. - + Build worlds. Create hierarchy of broad categories down to specific details. Utforma världar. Skapa en hierarki av generella kategorier ned till minsta detalj. - + Create the outline of your masterpiece. Skapa ett utkast för ditt mästerverk. - + Write. Skriv. - + Debug info. Sometimes useful. Debug-information. Kan vara användbar. - + Dictionary Ordbok - + Install PyEnchant to use spellcheck Installera PyEnchant för stavningskontroll - + Nothing Ingenting - + POV Synvinkel - + Label Etikett - + Progress Framsteg - + Compile Kompilera - + Icon color Ikonfärg - + Text color Textfärg - + Background color Bakgrundsfärg - + Icon Ikon - + Text Text - + Background Bakgrund - + Border Kant - + Corner Hörn + + + Add plot step (CTRL+Enter) + Lägg till nytt steg i handlingen (CTRL+Enter) + + + + Ctrl+Return + Ctrl+Enter + + + + Remove selected plot step(s) (CTRL+Backspace) + Ta bort valda steg i handlingen (CTRL+Backspace) + + + + Ctrl+Backspace + Ctrl+Backspace + Settings @@ -998,596 +1020,631 @@ Inställningar - + General Allmänt - + Revisions Ändringshistorik - + Views Visningar - + Labels Etiketter - + Status Status - + Fullscreen Fullskärm - + General settings Allmänna inställningar - + Application style Applikationens utseende - + You might need to restart manuskript in order to avoid some visual issues. Du kan behöva starta om Manuskript för att undvika problem med utseendet. - + Application language Applikationens språk - + You will need to restart manuskript for the translation to take effect. Du behöver starta om Manuskript för att se ändringarna. - + Loading Uppstart - + Automatically load last project on startup Ladda automatiskt senaste projekt vid uppstart - + Saving Sparande - + Automatically save every Spara automatiskt var - + minutes. minut. - + If no changes during Om inget har ändrats på - + seconds. sekunder. - + Save on quit Spara vid avslut - + <html><head/><body><p>If you check this option, your project will be save as one single file. Easier to copy or backup, but does not allow collaborative editing, or versionning.<br/>If this is unchecked, your project will be save as a folder containing many small files.</p></body></html> <html><head/><body><p>Markeras detta alternativ sparas ditt projekt som en samlad fil. Detta gör det lättare att ta en backup eller göra en kopia, men tillåter inte gemensam redigering eller versionshantering.<br/>Markeras alternativet inte sparas ditt projekt som en mapp med många små filer.</p></body></html> - + Save to one single file Spara i en samlad fil - + Revisions are a way to keep track of modifications. For each text item, it stores any changes you make to the main text, allowing you to see and restoring previous versions. Ändringshistorik låter dig följa dina ändringar. För varje textobjekt sparas ändringar som du har gjort vilket ger dig möjlighet till att se och återskapa tidigare versioner. - + Keep revisions Aktivera Ändringshistorik - + S&mart remove S&mart borttagning - + Keep: Behåll: - + Smart remove allows you to keep only a certain number of revisions. It is strongly recommended to use it, lest you file will becomes full of thousands of insignificant changes. Smart borttagning låter dig behålla ett visst antal ändringar. Du rekommenderas att använda den för att undvika att din fil fylls med tusentals av oviktiga ändringar. - + revisions per day for the last month ändringar per dag för senaste månaden - + revisions per minute for the last 10 minutes ändringar per minut för senaste 10 minuterna - + revisions per hour for the last day ändringar per timme för senaste dagen - + revisions per 10 minutes for the last hour ändringar per 10 minuter för senaste timmen - + revisions per week till the end of time ändringar per vecka tills vidare - + Views settings Visningsinställningar - + Tree Träd - + Colors Färger - + Icon color: Ikonfärg: - + Nothing Ingenting - + POV Synvinkel - + Label Etikett - + Progress Framsteg - + Compile Kompilera - + Text color: Textfärg: - + Background color: Bakgrundsfärg: - + Folders Mappar - + Show ite&m count Visa antal obje&kt - + Show wordcount Visa antal ord - + Show progress Visa framsteg - + Show summary Visa sammanfattning - + &Nothing I&ngenting - + Text Text - + Outline Utkast - + Visible columns Synliga kolumner - + Goal Mål - + Word count Antal ord - + Percentage Procent - + Title Titel - + Index cards Registerkort - + Background Bakgrund - + Color: Färg: - + Ctrl+S Ctrl+S - + Image: Bild: - + Style Utseende - + Old style Gammalt utseende - + New style Nytt utseende - + Item colors Objektfärger - + Border color: Kantfärg: - + Corner color: Hörnfärg: - + Text editor Textredigerare - + Font Typsnitt - + Family: Familj: - + Size: Teckenstorlek: - + Misspelled: Felstavat: - + Background: Bakgrund: - + Cursor Markör - + Use block insertion of Använd blockmarkör på - + px pixlar - + Paragraphs Stycke - + Line spacing: Radavstånd: - + Single Enkelt - + 1.5 lines 1.5 rader - + Double Dubbelt - + Proportional Proportionellt - + % % - + Tab width: Tabb-bredd: - + Indent 1st line Indrag på rad 1 - + Spacing: - It seems like some labels could be missing here? Should this say "Spacing before and after" or should it be split up into multiple labels? Avstånd: - + Alignment: Justering: - + Left Vänster - + Center Centrera - + Right Höger - + Justify Justerad - + New Ny - + Edit Redigera - + Delete Ta bort - + Theme name: Temats namn: - + Apply Använd - + Cancel Avbryt - + Window Background Fönsterbakgrund - + Text Background Textbakgrund - + Text Options Textinställningar - + Paragraph Options Styckesinställningar - + Type: Typ: - + No Image Ingen bild - + Tiled Sida vid sida - + Centered Centrerad - + Stretched Sträckt - + Scaled Skalad - + Zoomed Zoomad - + Opacity: Opacitet: - + Position: Position: - + Width: Bredd: - + Corner radius: Hörnradie: - + Margins: Marginaler: - + Padding: Utfyllnad: - + Font: Typsnitt: - + Alignment Justering + + + Icon Size + Storlek på ikoner + + + + TextLabel + Placeholder string? + TextEtikett + + + + Disable blinking + Avaktivera blinkning + + + + Text area + Textområde + + + + Max width + Maximal bredd + + + + Left/Right margins: + Vänster-/Höger-marginaler: + + + + Top/Bottom margins: + Topp-/Botten-marginaler: + SpellAction - + Spelling Suggestions Stavningsförslag - + &Add to dictionary &Lägg till i ordbok - + &Remove from custom dictionary &Ta bort från ordbok @@ -1771,7 +1828,6 @@ Dock Widgets Toolbar - This has no good Swedish translation and could be out of context. Verktygsrad för Dock Widgets @@ -1954,12 +2010,14 @@ - Replace ... with … + Replace ... with … + Character issue in the source text? Ersätt ... med … - Replace --- with — + Replace --- with — + Character issue in source text? Ersätt --- med — @@ -2168,7 +2226,6 @@ Lock ! - Is the space after Lock intentional? Lås! @@ -2240,17 +2297,17 @@ F11 - + Root Rot - + {} words / {} {} ord / {} - + {} words {} ord @@ -2309,86 +2366,93 @@ Göm automatiskt - - outlineBasic - - - Open Item - Öppna Objekt - - outlineBasics - + New Folder Ny Mapp - + New Text Ny Text - + Delete Ta Bort - + Copy Kopiera - + Cut Klipp Ut - + Paste Klistra In - + Set POV Ange Synvinkel - + None Ingen - + Main Huvudkaraktär - + Secondary Bikaraktär - + Minor Statist - + Set Status Ange Status - + Set Label Ange Etikett - + New Ny + + + Open Item + Öppna Objekt + + + + Set Custom Icon + Välj Anpassad Ikon + + + + Restore to default + Återställ till standard + outlineCharacterDelegate @@ -2416,47 +2480,47 @@ outlineModel - + Title Titel - + POV Synvinkel - + Label Etikett - + Status Status - + Compile Kompilera - + Word count Antal ord - + Goal Mål - + {} words / {} ({}) {} ord / {} ({}) - + {} words {} ord @@ -2464,17 +2528,17 @@ pandocSettings - + General Allmänt - + Table of Content Innehållsförteckning - + Custom settings for {} Anpassade inställningar för {} @@ -2489,7 +2553,6 @@ Secundary - Misspelled in English Bikaraktär @@ -2508,7 +2571,6 @@ Promise - Not sure of the context here Löfte @@ -2604,7 +2666,6 @@ 3. Stand up - Context unclear. 3. Stå upp @@ -2686,37 +2747,37 @@ plotModel - + New plot Ny handling - + Name Namn - + Meta Meta - + New step Nytt steg - + Main Huvudkaraktär - + Secondary Bikaraktär - + Minor Statist @@ -2724,22 +2785,22 @@ plotTreeView - + Main Huvudkaraktär - + Secondary Bikaraktär - + Minor Statist - + **Plot:** {} **Handling:** {} @@ -2754,7 +2815,6 @@ Secundary - Typo in English text Bikaraktär @@ -3123,28 +3183,27 @@ settingsWindow - + New status Ny status - + New label Ny etikett - + newtheme - Keeping in English since the formatting looks strange. newtheme - + New theme Nytt tema - + (read-only) (skrivskyddad) @@ -3159,7 +3218,6 @@ TextLabel - Keeping in English since formatting looks strange. TextLabel @@ -3199,22 +3257,22 @@ tabSplitter - + Open selected items in that view. Öppna valda objekt i den visningen. - + Split horizontally Dela horisontellt - + Close split Stäng delning - + Split vertically Dela vertikalt @@ -3230,7 +3288,7 @@ textEditView - + Various Diverse @@ -3301,22 +3359,22 @@ Öppna {} i en ny flik - + Expand {} Expandera {} - + Collapse {} Minimera {} - + Expand All Expandera Alla - + Collapse All Minimera Alla @@ -3331,7 +3389,6 @@ 1 - Not sure what this is? 1 @@ -3365,14 +3422,13 @@ Uppsats - + Demo projects Demoprojekt Add level - This functionality seems a bit unclear in both English and Swedish Lägg till nivå @@ -3446,12 +3502,12 @@ Tom skönlitteratur - + Chapter Kapitel - + Scene Scen @@ -3476,72 +3532,72 @@ Tom facklitteratur - + words each. ord vardera. - + of av - + Text Text - + Something Någonting - + <b>Total:</b> {} words (~ {} pages) <b>Total:</b> {} ord (~ {} sidor) - + Fiction Skönlitteratur - + Non-fiction Facklitteratur - + Idea Idé - + Note Anteckning - + Research Referensinformation - + TODO Att Göra - + First draft Första utkast - + Second draft Andra utkast - + Final Slutgiltig From 2cc721674a287e3ec9c915148b99232ee6036dc5 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Thu, 2 Nov 2017 12:14:45 +0100 Subject: [PATCH 05/65] Fixes: Index card text almost invisible in dark themes. #183 --- manuskript/ui/views/corkDelegate.py | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/manuskript/ui/views/corkDelegate.py b/manuskript/ui/views/corkDelegate.py index ab4e4c5..6e6eee9 100644 --- a/manuskript/ui/views/corkDelegate.py +++ b/manuskript/ui/views/corkDelegate.py @@ -141,14 +141,14 @@ class corkDelegate(QStyledItemDelegate): iconSize = max(24 * self.factor, 18) item = index.internalPointer() fm = QFontMetrics(option.font) - h = fm.lineSpacing() - + h = fm.lineSpacing() + self.itemRect = option.rect.adjusted(margin, margin, -margin, -margin) - + top = 15 * self.factor self.topRect = QRect(self.itemRect) self.topRect.setHeight(top) - + self.cardRect = QRect(self.itemRect.topLeft() + QPoint(0, top), self.itemRect.bottomRight()) self.iconRect = QRect(self.cardRect.topLeft() + QPoint(margin, margin), @@ -167,7 +167,7 @@ class corkDelegate(QStyledItemDelegate): self.mainRect.bottomRight()) if not item.data(Outline.summarySentence.value): self.mainTextRect.setTopLeft(self.mainLineRect.topLeft()) - + def updateRects_v1(self, option, index): margin = self.margin iconSize = max(16 * self.factor, 12) @@ -196,7 +196,7 @@ class corkDelegate(QStyledItemDelegate): self.paint_v2(p, option, index) else: self.paint_v1(p, option, index) - + def paint_v2(self, p, option, index): # QStyledItemDelegate.paint(self, p, option, index) if not index.isValid(): @@ -212,7 +212,7 @@ class corkDelegate(QStyledItemDelegate): p.translate(self.mainRect.center()) p.rotate(angle) p.translate(-self.mainRect.center()) - + def drawRect(r): p.save() p.setBrush(Qt.gray) @@ -243,7 +243,7 @@ class corkDelegate(QStyledItemDelegate): p.setBrush(col) else: p.setBrush(Qt.white) - + p.setPen(Qt.NoPen) p.drawRect(self.cardRect) if item.isFolder(): @@ -273,10 +273,10 @@ class corkDelegate(QStyledItemDelegate): self.labelRect.bottomRight() + QPointF(1, w / 2), self.labelRect.bottomRight() + QPointF(1, 1), ]) - - p.drawPolygon(poly) + + p.drawPolygon(poly) p.restore() - + if settings.viewSettings["Cork"]["Corner"] == "Nothing" or \ color == Qt.transparent: # No corner, so title can be full width @@ -299,8 +299,9 @@ class corkDelegate(QStyledItemDelegate): # Draw title p.save() text = index.data() - + if text: + p.setPen(Qt.black) if settings.viewSettings["Cork"]["Text"] != "Nothing": col = colors[settings.viewSettings["Cork"]["Text"]] if col == Qt.transparent: @@ -357,6 +358,7 @@ class corkDelegate(QStyledItemDelegate): f = QFont(option.font) f.setBold(True) p.setFont(f) + p.setPen(Qt.black) fm = QFontMetrics(f) elidedText = fm.elidedText(lineSummary, Qt.ElideRight, self.mainLineRect.width()) p.drawText(self.mainLineRect, Qt.AlignLeft | Qt.AlignVCenter, elidedText) @@ -364,8 +366,11 @@ class corkDelegate(QStyledItemDelegate): # Full summary if fullSummary: + p.save() p.setFont(option.font) + p.setPen(Qt.black) p.drawText(self.mainTextRect, Qt.TextWordWrap, fullSummary) + p.restore() def paint_v1(self, p, option, index): # QStyledItemDelegate.paint(self, p, option, index) From 4ddb95ff678769540f29ba4b3385559d85af7d11 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Sat, 4 Nov 2017 08:01:45 +0100 Subject: [PATCH 06/65] Removes MultiMarkDown exporter. See #186. --- manuskript/exporter/__init__.py | 6 ++---- manuskript/exporter/mmd.py | 28 ---------------------------- 2 files changed, 2 insertions(+), 32 deletions(-) delete mode 100644 manuskript/exporter/mmd.py diff --git a/manuskript/exporter/__init__.py b/manuskript/exporter/__init__.py index b8e7df4..c9edb18 100644 --- a/manuskript/exporter/__init__.py +++ b/manuskript/exporter/__init__.py @@ -2,13 +2,11 @@ # --!-- coding: utf8 --!-- from manuskript.exporter.manuskript import manuskriptExporter -from manuskript.exporter.mmd import mmdExporter from manuskript.exporter.pandoc import pandocExporter exporters = [ manuskriptExporter(), - pandocExporter(), - mmdExporter() + pandocExporter() ] def getExporterByName(name): @@ -16,4 +14,4 @@ def getExporterByName(name): if e.name == name: return e - return None \ No newline at end of file + return None diff --git a/manuskript/exporter/mmd.py b/manuskript/exporter/mmd.py deleted file mode 100644 index fbe5bae..0000000 --- a/manuskript/exporter/mmd.py +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env python -# --!-- coding: utf8 --!-- -from PyQt5.QtWidgets import qApp - -from manuskript.exporter.basic import basicExporter, basicFormat - - -class mmdExporter(basicExporter): - - name = "MultiMarkdown" - description = qApp.translate("Export", """

A superset of markdown.

-

Website: http://fletcherpenney.net/multimarkdown/

- """) - exportTo = [ - basicFormat("HTML", "A little known format modestly used. You know, web sites for example.", "text-html"), - basicFormat("latex", "", "text-x-tex"), - basicFormat("Flat XML", "", "text-xml"), - basicFormat("ePub", "Books that don't kill trees.", icon="application-epub+zip"), - ] - cmd = "multimarkdown" - - def version(self): - if self.isValid(): - r = self.run(["-v"]) - return r.split("\n")[1] - else: - return "" - From fc1bd40c21fa206bc4c8067591545e55c5e57fb3 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Sat, 4 Nov 2017 08:26:42 +0100 Subject: [PATCH 07/65] Uses instead of (#186) --- manuskript/exporter/pandoc/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manuskript/exporter/pandoc/__init__.py b/manuskript/exporter/pandoc/__init__.py index d36e78d..d03e017 100644 --- a/manuskript/exporter/pandoc/__init__.py +++ b/manuskript/exporter/pandoc/__init__.py @@ -40,7 +40,7 @@ class pandocExporter(basicExporter): def version(self): if self.isValid(): - r = self.run(["-v"]) + r = self.run(["--version"]) return r.split("\n")[0] else: return "" From 81de19165b3c917e4a2d272e7158df40a6139a0f Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Sun, 5 Nov 2017 13:49:31 +0100 Subject: [PATCH 08/65] Adds a 'Rename Item' option to context menu in the Tree view #189 --- icons/NumixMsk/16x16/actions/edit-rename.svg | 5 +++++ icons/NumixMsk/22x22/actions/edit-rename.svg | 5 +++++ icons/NumixMsk/24x24/actions/edit-rename.svg | 5 +++++ icons/NumixMsk/32x32/actions/edit-rename.svg | 5 +++++ icons/NumixMsk/48x48/actions/edit-rename.svg | 5 +++++ icons/NumixMsk/64x64/actions/edit-rename.svg | 5 +++++ manuskript/ui/views/outlineBasics.py | 20 ++++++++++++++++++-- 7 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 icons/NumixMsk/16x16/actions/edit-rename.svg create mode 100644 icons/NumixMsk/22x22/actions/edit-rename.svg create mode 100644 icons/NumixMsk/24x24/actions/edit-rename.svg create mode 100644 icons/NumixMsk/32x32/actions/edit-rename.svg create mode 100644 icons/NumixMsk/48x48/actions/edit-rename.svg create mode 100644 icons/NumixMsk/64x64/actions/edit-rename.svg diff --git a/icons/NumixMsk/16x16/actions/edit-rename.svg b/icons/NumixMsk/16x16/actions/edit-rename.svg new file mode 100644 index 0000000..ff35100 --- /dev/null +++ b/icons/NumixMsk/16x16/actions/edit-rename.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/icons/NumixMsk/22x22/actions/edit-rename.svg b/icons/NumixMsk/22x22/actions/edit-rename.svg new file mode 100644 index 0000000..7742210 --- /dev/null +++ b/icons/NumixMsk/22x22/actions/edit-rename.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/icons/NumixMsk/24x24/actions/edit-rename.svg b/icons/NumixMsk/24x24/actions/edit-rename.svg new file mode 100644 index 0000000..319f6b4 --- /dev/null +++ b/icons/NumixMsk/24x24/actions/edit-rename.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/icons/NumixMsk/32x32/actions/edit-rename.svg b/icons/NumixMsk/32x32/actions/edit-rename.svg new file mode 100644 index 0000000..e409ff8 --- /dev/null +++ b/icons/NumixMsk/32x32/actions/edit-rename.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/icons/NumixMsk/48x48/actions/edit-rename.svg b/icons/NumixMsk/48x48/actions/edit-rename.svg new file mode 100644 index 0000000..5237dd8 --- /dev/null +++ b/icons/NumixMsk/48x48/actions/edit-rename.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/icons/NumixMsk/64x64/actions/edit-rename.svg b/icons/NumixMsk/64x64/actions/edit-rename.svg new file mode 100644 index 0000000..b6e130b --- /dev/null +++ b/icons/NumixMsk/64x64/actions/edit-rename.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/manuskript/ui/views/outlineBasics.py b/manuskript/ui/views/outlineBasics.py index 7fc6959..8c21a8e 100644 --- a/manuskript/ui/views/outlineBasics.py +++ b/manuskript/ui/views/outlineBasics.py @@ -39,14 +39,18 @@ class outlineBasics(QAbstractItemView): menu = QMenu(self) - # Add / remove items + # Open items self.actOpen = QAction(QIcon.fromTheme("go-right"), qApp.translate("outlineBasics", "Open Item"), menu) self.actOpen.triggered.connect(self.openItem) menu.addAction(self.actOpen) menu.addSeparator() - # Add / remove items + # Rename / add / remove items + self.actRename = QAction(QIcon.fromTheme("edit-rename"), qApp.translate("outlineBasics", "Rename"), menu) + self.actRename.triggered.connect(self.rename) + menu.addAction(self.actRename) + self.actAddFolder = QAction(QIcon.fromTheme("folder-new"), qApp.translate("outlineBasics", "New Folder"), menu) self.actAddFolder.triggered.connect(self.addFolder) menu.addAction(self.actAddFolder) @@ -184,12 +188,16 @@ class outlineBasics(QAbstractItemView): self.actOpen.setEnabled(False) self.actCopy.setEnabled(False) self.actCut.setEnabled(False) + self.actRename.setEnabled(False) self.actDelete.setEnabled(False) self.menuPOV.setEnabled(False) self.menuStatus.setEnabled(False) self.menuLabel.setEnabled(False) self.menuCustomIcons.setEnabled(False) + if len(sel) > 1: + self.actRename.setEnabled(False) + return menu def openItem(self): @@ -197,6 +205,14 @@ class outlineBasics(QAbstractItemView): from manuskript.functions import MW MW.openIndex(idx) + def rename(self): + if len(self.getSelection()) == 1: + index = self.currentIndex() + self.edit(index) + elif len(self.getSelection()) > 1: + # FIXME: add smart rename + pass + def addFolder(self): self.addItem("folder") From 1fa86ddd73959cc6fb958ba8c462a33333b90744 Mon Sep 17 00:00:00 2001 From: Cam Stevenson Date: Sun, 29 Oct 2017 11:26:43 -0400 Subject: [PATCH 09/65] Finish OPML Import --- manuskript/import_export/opml.py | 125 ++++++++++++++++++------- manuskript/mainWindow.py | 12 +-- manuskript/ui/editors/mainEditor.py | 20 ++++ manuskript/ui/editors/mainEditor_ui.py | 10 ++ manuskript/ui/editors/mainEditor_ui.ui | 17 ++++ manuskript/ui/mainWindow.py | 5 - 6 files changed, 142 insertions(+), 47 deletions(-) diff --git a/manuskript/import_export/opml.py b/manuskript/import_export/opml.py index e178ddc..9dc6327 100644 --- a/manuskript/import_export/opml.py +++ b/manuskript/import_export/opml.py @@ -2,69 +2,112 @@ # --!-- coding: utf8 --!-- # Import/export outline cards in OPML format - +from PyQt5.QtWidgets import QMessageBox from manuskript.models.outlineModel import outlineItem from manuskript.enums import Outline -import xmltodict +from lxml import etree as ET from manuskript.functions import mainWindow -from PyQt5.QtCore import QModelIndex -def exportOpml(): - return True - - -def importOpml(opmlFilePath): - with open(opmlFilePath, 'r') as opmlFile: - opmlContent = saveNewlines(opmlFile.read()) - +def importOpml(opmlFilePath, idx): + ret = False mw = mainWindow() + + try: + with open(opmlFilePath, 'r') as opmlFile: + opmlContent = saveNewlines(opmlFile.read()) + except: + # TODO: Translation + QMessageBox.critical(mw, mw.tr("OPML Import"), + mw.tr("File open failed.")) + return False + mdl = mw.mdlOutline - dict = xmltodict.parse(opmlContent, strip_whitespace=False) + if idx.internalPointer() is not None: + parentItem = idx.internalPointer() + else: + parentItem = mdl.rootItem - opmlNode = dict['opml'] - bodyNode = opmlNode['body'] + try: + parsed = ET.fromstring(bytes(opmlContent, 'utf-8')) - outline = bodyNode['outline'] + opmlNode = parsed + bodyNode = opmlNode.find("body") - for element in outline: - parseItems(element, mdl.rootItem) + if bodyNode is not None: + outlineEls = bodyNode.findall("outline") - mdl.layoutChanged.emit() + if outlineEls is not None: + for element in outlineEls: + parseItems(element, parentItem) - mw.treeRedacOutline.viewport().update() + mdl.layoutChanged.emit() + mw.treeRedacOutline.viewport().update() + ret = True + except: + pass - return True + # TODO: Translation + if ret: + QMessageBox.information(mw, mw.tr("OPML Import"), + mw.tr("Import Complete.")) + else: + QMessageBox.critical(mw, mw.tr("OPML Import"), + mw.tr("This does not appear to be a valid OPML file.")) + + return ret def parseItems(underElement, parentItem): - if '@text' in underElement: - card = outlineItem(parent=parentItem, title=underElement['@text']) + text = underElement.get('text') + if text is not None: + """ + In the case where the title is exceptionally long, trim it so it isn't + distracting in the tab label + """ + title = text[0:32] + if len(title) < len(text): + title += '...' - text = "" + card = outlineItem(parent=parentItem, title=title) + + body = "" summary = "" - if '@_note' in underElement: - text = restoreNewLines(underElement['@_note']) - summary = text[0:128] + note = underElement.get('_note') + if note is not None and not isWhitespaceOnly(note): + body = restoreNewLines(note) + summary = body[0:128] + else: + """ + There's no note (body), but there is a title. Fill the + body with the title to support cards that consist only + of a title. + """ + body = text card.setData(Outline.summaryFull.value, summary) - if 'outline' in underElement: - elements = underElement['outline'] - - for el in elements: + children = underElement.findall('outline') + if children is not None and len(children) > 0: + for el in children: parseItems(el, card) else: card.setData(Outline.type.value, 'md') - card.setData(Outline.text.value, text) + card.setData(Outline.text.value, body) - # I assume I don't have to do the following - # parentItem.appendChild(card) + # I assume I don't have to do the following + # parentItem.appendChild(card) return +""" +Since XML parsers are notorious for stripping out significant newlines, +save them in a form we can restore after the parse. +""" + + def saveNewlines(inString): inString = inString.replace("\r\n", "\n") inString = inString.replace("\n", "{{lf}}") @@ -72,6 +115,22 @@ def saveNewlines(inString): return inString +""" +Restore any significant newlines +""" + + def restoreNewLines(inString): return inString.replace("{{lf}}", "\n") + +""" +Determine whether or not a string only contains whitespace. +""" + + +def isWhitespaceOnly(inString): + str = restoreNewLines(inString) + str = ''.join(str.split()) + + return len(str) is 0 diff --git a/manuskript/mainWindow.py b/manuskript/mainWindow.py index 92850ff..36d40e1 100644 --- a/manuskript/mainWindow.py +++ b/manuskript/mainWindow.py @@ -27,7 +27,6 @@ from manuskript.ui.mainWindow import Ui_MainWindow from manuskript.ui.tools.frequencyAnalyzer import frequencyAnalyzer from manuskript.ui.views.outlineDelegates import outlineCharacterDelegate from manuskript.ui.views.plotDelegate import plotDelegate -from manuskript.import_export import opml as opmlInputExport # Spellcheck support from manuskript.ui.views.textEditView import textEditView @@ -98,13 +97,12 @@ 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.actImport, self.actSettings]: + 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.importOutline) self.actCompile.triggered.connect(self.doCompile) self.actLabels.triggered.connect(self.settingsLabel) self.actStatus.triggered.connect(self.settingsStatus) @@ -447,7 +445,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.actImport, self.actSettings]: + self.actCompile, self.actSettings]: i.setEnabled(True) # Add project name to Window's name @@ -491,7 +489,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.actImport, self.actSettings]: + self.actCompile, self.actSettings]: i.setEnabled(False) # Set Window's name - no project loaded @@ -627,10 +625,6 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.statusBar().showMessage( self.tr("Project {} loaded with some errors.").format(project), 5000) - def importOutline(self, project): - opmlInputExport.importOpml('/home/cstevenson/End Plan 2.opml') - return True - ############################################################################### # MAIN CONNECTIONS ############################################################################### diff --git a/manuskript/ui/editors/mainEditor.py b/manuskript/ui/editors/mainEditor.py index ab5c1b6..71571e7 100644 --- a/manuskript/ui/editors/mainEditor.py +++ b/manuskript/ui/editors/mainEditor.py @@ -14,6 +14,7 @@ from manuskript.ui import style from manuskript.ui.editors.editorWidget import editorWidget from manuskript.ui.editors.fullScreenEditor import fullScreenEditor from manuskript.ui.editors.mainEditor_ui import Ui_mainEditor +from manuskript.import_export import opml as opmlInputExport locale.setlocale(locale.LC_ALL, '') @@ -44,6 +45,10 @@ class mainEditor(QWidget, Ui_mainEditor): self.btnRedacFullscreen.clicked.connect( self.showFullScreen, AUC) + self.btnImport.clicked.connect( + lambda v: self.importOPML() + ) + # self.tab.setDocumentMode(False) # Bug in Qt < 5.5: doesn't always load icons from custom theme. @@ -217,6 +222,7 @@ class mainEditor(QWidget, Ui_mainEditor): self.btnRedacFolderText.setVisible(visible) self.btnRedacFolderCork.setVisible(visible) self.btnRedacFolderOutline.setVisible(visible) + self.btnImport.setVisible(visible) self.sldCorkSizeFactor.setVisible(visible and self.btnRedacFolderCork.isChecked()) self.btnRedacFullscreen.setVisible(not visible) @@ -296,6 +302,20 @@ class mainEditor(QWidget, Ui_mainEditor): if self.currentEditor(): self._fullScreen = fullScreenEditor(self.currentEditor().currentIndex) + def importOPML(self): + from PyQt5.QtWidgets import QFileDialog + options = QFileDialog.Options() + options |= QFileDialog.DontUseNativeDialog + fileName, _ = QFileDialog.getOpenFileName(self, "Import OPML", "", + "OPML Files (*.opml)", options=options) + if fileName: + if len(self.mw.treeRedacOutline.selectionModel(). + selection().indexes()) == 0: + idx = QModelIndex() + else: + idx = self.mw.treeRedacOutline.currentIndex() + opmlInputExport.importOpml(fileName, idx) + ############################################################################### # DICT AND STUFF LIKE THAT ############################################################################### diff --git a/manuskript/ui/editors/mainEditor_ui.py b/manuskript/ui/editors/mainEditor_ui.py index 269f0fc..22fd1f0 100644 --- a/manuskript/ui/editors/mainEditor_ui.py +++ b/manuskript/ui/editors/mainEditor_ui.py @@ -55,6 +55,14 @@ class Ui_mainEditor(object): self.btnRedacFolderOutline.setObjectName("btnRedacFolderOutline") self.buttonGroup.addButton(self.btnRedacFolderOutline) self.horizontalLayout_19.addWidget(self.btnRedacFolderOutline) + self.btnImport = QtWidgets.QPushButton(mainEditor) + self.btnImport.setText("") + icon = QtGui.QIcon.fromTheme("document-open") + self.btnImport.setIcon(icon) + self.btnImport.setFlat(True) + self.btnImport.setObjectName("btnImport") + self.buttonGroup.addButton(self.btnImport) + self.horizontalLayout_19.addWidget(self.btnImport) self.sldCorkSizeFactor = QtWidgets.QSlider(mainEditor) self.sldCorkSizeFactor.setMinimumSize(QtCore.QSize(100, 0)) self.sldCorkSizeFactor.setMaximumSize(QtCore.QSize(200, 16777215)) @@ -109,6 +117,8 @@ class Ui_mainEditor(object): self.btnRedacFolderCork.setText(_translate("mainEditor", "Index cards")) self.btnRedacFolderOutline.setText(_translate("mainEditor", "Outline")) self.btnRedacFullscreen.setShortcut(_translate("mainEditor", "F11")) + # TODO: Translation + self.btnImport.setToolTip(_translate("mainEditor", "Import items from an OPML file into the current folder")) from manuskript.ui.editors.tabSplitter import tabSplitter from manuskript.ui.editors.textFormat import textFormat diff --git a/manuskript/ui/editors/mainEditor_ui.ui b/manuskript/ui/editors/mainEditor_ui.ui index 68a3a68..ce73c97 100644 --- a/manuskript/ui/editors/mainEditor_ui.ui +++ b/manuskript/ui/editors/mainEditor_ui.ui @@ -111,6 +111,23 @@ + + + + + Import items from an OPML file into the current folder + + + + + + + + + true + + + diff --git a/manuskript/ui/mainWindow.py b/manuskript/ui/mainWindow.py index ad5d093..a2a9a74 100644 --- a/manuskript/ui/mainWindow.py +++ b/manuskript/ui/mainWindow.py @@ -1123,9 +1123,6 @@ class Ui_MainWindow(object): self.actSaveAs.setObjectName("actSaveAs") self.actQuit = QtWidgets.QAction(MainWindow) icon = QtGui.QIcon.fromTheme("application-exit") - self.actImport = QtWidgets.QAction(MainWindow) - self.actImport.setIcon(icon) - self.actImport.setObjectName("actImport") self.actQuit.setIcon(icon) self.actQuit.setObjectName("actQuit") self.actShowHelp = QtWidgets.QAction(MainWindow) @@ -1186,7 +1183,6 @@ class Ui_MainWindow(object): self.menuFile.addAction(self.menuRecents.menuAction()) self.menuFile.addAction(self.actSave) self.menuFile.addAction(self.actSaveAs) - self.menuFile.addAction(self.actImport) self.menuFile.addAction(self.actCloseProject) self.menuFile.addSeparator() self.menuFile.addAction(self.actCompile) @@ -1328,7 +1324,6 @@ class Ui_MainWindow(object): self.actSave.setShortcut(_translate("MainWindow", "Ctrl+S")) self.actSaveAs.setText(_translate("MainWindow", "Sa&ve as...")) self.actSaveAs.setShortcut(_translate("MainWindow", "Ctrl+Shift+S")) - self.actImport.setText("Import") self.actQuit.setText(_translate("MainWindow", "&Quit")) self.actQuit.setShortcut(_translate("MainWindow", "Ctrl+Q")) self.actShowHelp.setText(_translate("MainWindow", "&Show help texts")) From 6afda2a7a1bd269b84bc80b73ceda14cd8db4f8d Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 5 Nov 2017 18:14:00 +0100 Subject: [PATCH 10/65] Expand german translation --- i18n/manuskript_de.ts | 699 +++++++++++++++++++++--------------------- 1 file changed, 357 insertions(+), 342 deletions(-) diff --git a/i18n/manuskript_de.ts b/i18n/manuskript_de.ts index 070b7b6..afbde09 100644 --- a/i18n/manuskript_de.ts +++ b/i18n/manuskript_de.ts @@ -1,5 +1,6 @@ - + + Export @@ -162,19 +163,23 @@ <p>A superset of markdown.</p> <p>Website: <a href="http://fletcherpenney.net/multimarkdown/">http://fletcherpenney.net/multimarkdown/</a></p> - + <p>Ein Set von Hervorhebungen.</p> + <p>Website: <a href="http://fletcherpenney.net/multimarkdown/">http://fletcherpenney.net/multimarkdown/</a></p> + Just like plain text, excepts adds markdown titles. Presupposes that texts are formatted in markdown. - + Ähnlich Plaintext, erlaubt aber Hervorhebungen für Titel + Setzt vorraus, dass Texte bereits in Markdown formatiert sind. Simplest export to plain text. Allows you to use your own markup not understood by manuskript, for example <a href='www.fountain.io'>Fountain</a>. - + Einfacher Export in Plaintext. Erlaubt die Benutzung von Markups, die von Manuskript nicht +interpretiert werden können, wie zum Beispiel <a href='www.fountain.io'>Fountain</a>. @@ -182,43 +187,51 @@ formats.</p> <p>Website: <a href="http://www.pandoc.org">http://pandoc.org/</a></p> - + <p>Ein universaler Dokumenten-Konverter. Kann zur Konvertierung von Markup in eine Reihe von anderen Formaten genutzt werden. + <p>Website: <a href="http://www.pandoc.org">http://pandoc.org/</a></p> + a valid latex installation. See pandoc recommendations on: <a href="http://pandoc.org/installing.html">http://pandoc.org/installing.html</a>. If you want unicode support, you need xelatex. - + eine funktionierende LaTex-Installation. Siehe Systemvorrausetzungen für Pandoc: + <a href="http://pandoc.org/installing.html">http://pandoc.org/installing.html</a>. Wenn Unicode unterstützt werden soll, wird xelatex benötigt. Export to markdown, using pandoc. Allows more formatting options than the basic manuskript exporter. - + Exportiert nach Markdown via pandoc. Erlaubt mehr Formatierungen + als der Basis-Manuskript-Export. LaTeX is a word processor and document markup language used to create beautiful documents. - + LaTeX ist ein Textverarbeitungsprogramm und Dokumenten Markup-Language, das zur Erstellung + wunderschöner Dokumente benutzt wird. The purpose of this format is to provide a way to exchange information between outliners and Internet services that can be browsed or controlled through an outliner. - + Der Zweck dieses Formats ist ein einfacher Weg zum Austausch von Informationen + zwischen Außenstehenden und Internetservices, die von Außenstehend + durchsucht oder kontrolliert werden können. Disable YAML metadata block. Use that if you get YAML related error. - + Entfernt den YAML-Metadaten-Block. +Nutze das, wenn du YAML-Errors bekommst. Convert to ePUB3 - + Konvertierung nach ePUB3 @@ -299,7 +312,7 @@ Use that if you get YAML related error. Exclude words (coma seperated): - Wörter ausschließen: <br>(Trennung durch Komma) + Wörter ausschließen: <br>(Trennung durch Komma): @@ -772,229 +785,231 @@ Use that if you get YAML related error. Book informations - + Buchinformationen &About - + &Über About Manuskript - + Über Manuskript The file {} does not exist. Try again. - + Die Datei {} existiert nicht. Versuche es noch einmal. Manuskript - Manuskript + Manuskript Project {} saved. - + Projekt {} gespeichert. WARNING: Project {} not saved. - + WARNUNG: Projekt {} nicht gespeichert. Project {} loaded. - + Projekt {} geladen. Project {} loaded with some errors: - + Projekt {} mit einigen Fehlern geladen: * {} wasn't found in project file. - + * {} konnte in der Projektdatei nicht gefunden werden. Project {} loaded with some errors. - + Projekt {} wurde mit einigen Fehlern geladen. (~{} pages) - + (~{} Seiten) Words: {}{} - + Wörter: {}{} Book summary - + Buchzusammenfassung Project tree - + Projektbaum Metadata - + Metadaten Story line - + Handlung Enter informations about your book, and yourself. - + Gib Informationen über dein Buch und dich ein. The basic situation, in the form of a 'What if...?' question. Ex: 'What if the most dangerous evil wizard could wasn't abled to kill a baby?' (Harry Potter) - + Die Ausgangssituation, in Form von 'Was wäre wenn ...?" Fragen. Beispiel: "Was wäre wenn der gefährlichste + böse Zauberer nicht in der Lage wäre, ein Baby zu töten?" (Harry Potter) Take time to think about a one sentence (~50 words) summary of your book. Then expand it to a paragraph, then to a page, then to a full summary. - + Nimm dir Zeit, dir einen Satz zu überlegen (~50 Wörter), um dein Buch zusammenzufassen. Dann erweitere ihn + zu einem Absatz, dann zu einer ganzen Seite und abschließend zu einer kompletten Zusammenfassung. Create your characters. - + Erstelle deine Charaktere. Develop plots. - + Entwickle Handlungsstränge. Build worlds. Create hierarchy of broad categories down to specific details. - + Erbaue Welten. Erstelle Hierachien breitgefächterter Kategorien, bis hin zu spezifischen Details. Create the outline of your masterpiece. - + Erstelle den Entwurf deines Meisterwerks. Write. - + Schreibe. Debug info. Sometimes useful. - + Debuginformationen. Manchmal hilfreich. Dictionary - + Wörterbuch Install PyEnchant to use spellcheck - + Installiere PyEnchant um die Rechtschreibprüfung zu nutzen Nothing - Keine + Keine POV - POV + Perspektive Label - Label + Beschriftung Progress - Fortschritt + Fortschritt Compile - Compile + Kompiliere Icon color - + Iconfarbe Text color - + Textfarbe Background color - + Hintergrundfarbe Icon - + Icon Text - Text + Text Background - Hintergrund + Hintergrund Border - + Rahmen Corner - + Ecke Add plot step (CTRL+Enter) - + Füge Handlungsschritt hinzu (Strg+Enter) Ctrl+Return - + Strg+Enter Remove selected plot step(s) (CTRL+Backspace) - + Entferne markierte(n) Handlungsschritt(e) (Strg+Löschtaste) Ctrl+Backspace - + Strg+Löschtaste @@ -1267,7 +1282,7 @@ Use that if you get YAML related error. Word count - Wortzählung + Wortanzahl @@ -1542,77 +1557,77 @@ Use that if you get YAML related error. Style - + Stil Old style - + Alter Stil New style - + Neuer Stil Cursor - + Zeiger Use block insertion of - + Nutze Absatzeinrückung von Alignment: - + Ausrichtung: Justify - + Blocksatz Alignment - + Ausrichtung Icon Size - + Icongröße TextLabel - Textlabel + Textbeschriftung Disable blinking - + Blinken Aus Text area - + Textfeld Max width - + Max. Breite Left/Right margins: - + Abstände Rechts/Links: Top/Bottom margins: - + Abstände Oben/Unten: @@ -1620,17 +1635,17 @@ Use that if you get YAML related error. Spelling Suggestions - + Korrekturvorschläge &Add to dictionary - + Zum Wörterbuch &hinzufügen &Remove from custom dictionary - + Aus dem Wörterbuch &entfernen @@ -1638,12 +1653,12 @@ Use that if you get YAML related error. Loaded translation: {}. - + Geladene Übersetzung: {}. Note: No translator found or loaded for locale {}. - + Notiz: Keine Übersetzung für {} gefunden oder geladen. @@ -1666,17 +1681,17 @@ Use that if you get YAML related error. Word count - Wortzahl + Wortanzahl One line summary - Inhaltsangabe in einem Satz + Zusammenfassung in einem Satz Few sentences summary: - Inhaltsangabe in wenigen Sätzen: + Zusammenfassung in wenigen Sätzen: @@ -1684,17 +1699,17 @@ Use that if you get YAML related error. New character - + Neuer Charakter Name - Name + Name Value - + Wert @@ -1702,17 +1717,17 @@ Use that if you get YAML related error. Main - + Primär Secondary - + Sekundär Minor - + Nebensächlich @@ -1730,37 +1745,37 @@ Use that if you get YAML related error. Minor - + Nebensächlich Secondary - + Sekundär Main - + Primär Characters - Charaktere + Charaktere Texts - + Texte Plots - Handlungsstränge + Handlungsstränge World - Welt + Welt @@ -1768,27 +1783,27 @@ Use that if you get YAML related error. None - Nichts + Nichts Main - + Primär Secondary - + Sekundär Minor - + Nebensächlich Various - + Verschiedene @@ -1796,7 +1811,7 @@ Use that if you get YAML related error. Various - + Verschiedene @@ -1804,7 +1819,7 @@ Use that if you get YAML related error. Various - + Verschiedene @@ -1812,7 +1827,7 @@ Use that if you get YAML related error. Dock Widgets Toolbar - + @@ -1828,12 +1843,12 @@ Use that if you get YAML related error. One line summary - Inhaltsangabe in einem Satz + Inhaltsangabe in einem Satz Full summary - Vollständige Inhaltsangabe + Vollständige Zusammenfassung @@ -1877,7 +1892,7 @@ Use that if you get YAML related error. {} (not implemented yet) - + {} (noch nicht implimentiert) @@ -1992,12 +2007,12 @@ Use that if you get YAML related error. Typographic replacements: Typografisches Ersetzen:
- + Replace ... with … Ersetze ... durch … - + Replace --- with — Ersetze --- durch — @@ -2070,17 +2085,17 @@ Use that if you get YAML related error. Folder - + Ordner {}Level {} folder - + {}Level {} Ordner {}Level {} text - + {}Level {} Text
@@ -2088,37 +2103,37 @@ Use that if you get YAML related error. Installed - + Installiert Custom - Benutzerdefiniert + Benutzerdefiniert Not found - + Nicht gefunden {} not found. Install it, or set path manually. - + {} nicht gefunden. Installiere es oder gib den Pfad manuell ein. <b>Status:</b> uninstalled. - + <b>Status:</b> Nicht installiert. <b>Requires:</b> - + <b>Voraussetzung</b> Set {} executable path. - + Setze {} ausführbaren Pfad. @@ -2126,17 +2141,17 @@ Use that if you get YAML related error. Phrases - + Phrasen Frequency - + Häufigkeit Word - + Wort @@ -2144,17 +2159,17 @@ Use that if you get YAML related error. Theme: - + Thema: {} words / {} - + {} Wörter / {} {} words - {} Wörter + {} Wörter @@ -2162,7 +2177,7 @@ Use that if you get YAML related error. If you don't wanna see me, you can hide me in Help menu. - + Wenn du mich nicht sehen willst, kannst du mich über das Hilfemenü verschwinden lassen. @@ -2170,7 +2185,7 @@ Use that if you get YAML related error. Various - + Verschiedenes @@ -2213,32 +2228,32 @@ Use that if you get YAML related error. ~{} h. - + ~{} h. ~{} mn. - + ~{} m. {}:{} - + {} s. - + {} s. {} remaining - + {} verbleibend {} words remaining - + {} Wörter verbleiben @@ -2271,27 +2286,27 @@ Use that if you get YAML related error. Go to parent item - + Gehe zum übergeordneten Element Alt+Up - + Alt+Nach oben Root - + Stamm {} words / {} - + {} Wörter / {} {} words - {} Wörter + {} Wörter @@ -2299,7 +2314,7 @@ Use that if you get YAML related error. Markdown - + Markdown @@ -2345,7 +2360,7 @@ Use that if you get YAML related error. Auto-hide - + Automatisches Ausblenden @@ -2408,32 +2423,32 @@ Use that if you get YAML related error. Main - + Primär Secondary - + Sekundär Minor - + Nebensächlich Open Item - + Öffne Element Set Custom Icon - + Benutzerdefinierten Icon einfügen Restore to default - + Auf Standard zurücksetzen @@ -2441,22 +2456,22 @@ Use that if you get YAML related error. None - Nichts + Nichts Main - + Primär Secondary - + Sekundär Minor - + Nebensächlich @@ -2474,37 +2489,37 @@ Use that if you get YAML related error. Title - Titel + Titel POV - POV + Perspektive Label - Label + Beschriftung Status - Status + Status Compile - Compile + Kompiliere Word count - + Wortanzahl Goal - Ziel + Ziel @@ -2512,17 +2527,17 @@ Use that if you get YAML related error. General - Allgemein + Allgemein Table of Content - + Inhaltsverzeichnis Custom settings for {} - + Benutzerdefinierte Einstellungen für {} @@ -2530,17 +2545,17 @@ Use that if you get YAML related error. Main - + Primär Secundary - + Sekundär Minors - + Nebensächlich @@ -2548,182 +2563,182 @@ Use that if you get YAML related error. General - Allgemein + Allgemein Promise - + Versprechen Problem - + Problem Progress - Fortschritt + Fortschritt Resolution - + Lösung Try / Fail - + Versuch / Scheitern No and - + Nein und Yes but - + Ja, aber Freytag's pyramid - + Freytags Pyramide Exposition - + Darstellung Rising action - + Spannungsanstieg Climax - + Klimax Falling action - + Spannungsabfall Three acts - + Drei Akte 1. Setup - + 1. Aufbau 1. Inciting event - + 1. Aufmacher 1. Turning point - + 1. Wendepunkt 2. Choice - + 2. Wahl 2. Reversal - + 2. Wende 2. Disaster - + 2. Katastrophe 3. Stand up - + 3. Gegenwehr 3. Climax - + 3. Klimax 3. Ending - + 3. Ende Hero's journey - + Heldenreise Ordinary world - + Gewöhnliche Welt Call to adventure - + Ruf des Abenteuers Refusal of the call - + Absage des Rufs Meeting with mentor - + Treffen mit dem Mentor Corssing the Threshold - + Übertreten der Schwelle Tests - + Prüfungen Approach - + Annäherung Abyss - + Abgrund Reward / Revelation - + Belohnung / Offenbarung Transformation - + Verwandlung Atonement - + Sühne Return - + Rückkehr @@ -2731,37 +2746,37 @@ Use that if you get YAML related error. New plot - + Neuer Handlungsstrang Name - Name + Name Meta - + Meta New step - + Neuer Schritt Main - + Primär Secondary - + Sekundär Minor - + Nebensächlich @@ -2769,22 +2784,22 @@ Use that if you get YAML related error. Main - + Primär Secondary - + Sekundär Minor - + Nebensächlich **Plot:** {} - + **Handlungsstrang:** {} @@ -2792,17 +2807,17 @@ Use that if you get YAML related error. Main - + Primär Secundary - + Sekundär Minors - + Nebensächlich @@ -2983,27 +2998,27 @@ Use that if you get YAML related error. Motivation - Motivation + Motivation Goal - Ziel + Ziel Epiphany - Schicksal + Schicksal Short summary - + Kurzzusammenfassung Longer summary - + Lange Zusammenfassung @@ -3031,67 +3046,67 @@ Use that if you get YAML related error. Show modifications - + Zeige Modifikationen Show ancient version - + Zeige vorherige Version Show spaces - + Zeige Leerzeichen Show modifications only - + Zeige nur die Modifikationen {} years ago - + {} Jahre zuvor {} months ago - + {} Monate zuvor {} days ago - + {} Tage zuvor 1 day ago - + 1 Tag zuvor {} hours ago - + {} Stunden zuvor {} minutes ago - + {} Minuten zuvor {} seconds ago - + {} Sekunden zuvor Line {}: - + Zeile {}: Clear all - + Leeren @@ -3109,57 +3124,57 @@ Use that if you get YAML related error. Search in: - + Suche in: All - + Alles Title - Titel + Titel Text - Text + Text Summary - Inhaltsangabe + Zusammenfassung Notes - Notizen + Notizen POV - POV + Perspektive Status - Status + Status Label - Label + Beschriftung Options: - + Optionen: Case sensitive - + Groß-/Kleinschreibung berücksichtigen @@ -3167,27 +3182,27 @@ Use that if you get YAML related error. New status - + Neuer Status New label - + Neue Beschriftung newtheme - + New theme - + Neues Thema (read-only) - + (schreibgeschützt) @@ -3205,17 +3220,17 @@ Use that if you get YAML related error. Minor - + Nebensächlich Secondary - + Sekundär Main - + Primär @@ -3228,12 +3243,12 @@ Use that if you get YAML related error. Show Plots - + Zeige Handlungsstränge Show Characters - + Zeige Charaktere @@ -3241,22 +3256,22 @@ Use that if you get YAML related error. Open selected items in that view. - + Öffne markierte Elemente in dieser Ansicht. Split horizontally - + Ansicht horizontal teilen Close split - + Teilung beenden Split vertically - + Ansicht vertikal teilen @@ -3264,7 +3279,7 @@ Use that if you get YAML related error. Insert reference - + Referenz einfügen @@ -3272,7 +3287,7 @@ Use that if you get YAML related error. Various - + Verschiedenes @@ -3285,42 +3300,42 @@ Use that if you get YAML related error. CTRL+B - + Strg+B CTRL+I - + Strg+I CTRL+U - + Strg+U CTRL+P - + Strg+P CTRL+L - + Strg+L CTRL+E - + Strg+E CTRL+R - + Strg+R CTRL+J - + Strg+J @@ -3328,37 +3343,37 @@ Use that if you get YAML related error. Root - + Stamm Open {} items in new tabs - + Öffne {} Elemente in neuen Tabs Open {} in a new tab - + Öffne {} in einem neuen Tab Expand {} - + {} ausklappen Collapse {} - + {} einklappen Expand All - + Alles ausklappen Collapse All - + Alles einklappen @@ -3441,147 +3456,147 @@ Use that if you get YAML related error. Open project - + Öffne Projekt Manuskript project (*.msk);;All files (*) - + Manuskript Projekt (*.msk);;Alle Dateien (*) Save project as... - + Speichern als ... Manuskript project (*.msk) - + Manuskript Projekt (*.msk) Manuskript - Manuskript + Manuskript Create New Project - + Erzeuge neues Projekt Warning - + Warnung Overwrite existing project {} ? - + Existierendes Projekt {} überschreiben? Empty fiction - + Leere Geschichte Chapter - + Kapitel Scene - + Szene Trilogy - + Triologie Book - + Buch Section - + Absatz Empty non-fiction - + Leeres Sachbuch words each. - + Wörter. of - + von Text - Text + Text Something - + Irgendwas <b>Total:</b> {} words (~ {} pages) - + <b>Gesamt:</b> {} Wörter (~ {} Seiten) Fiction - + Geschichte Non-fiction - + Sachtext Idea - + Idee Note - + Notiz Research - + Recherche TODO - + ToDo First draft - + Erster Entwurf Second draft - + Zweiter Entwurf Final - + Final @@ -3589,212 +3604,212 @@ Use that if you get YAML related error. New item - + Neues Element Fantasy world building - + Bau einer Fantasy-Welt Physical - + Geographie Climate - + Klima Topography - + Relief Astronomy - + Astronomie Natural ressources - + Natur Wild life - + Fauna Flora - + Flora History - + Geschichte Races - + Arten Diseases - + Krankheiten Cultural - + Kultur Customs - + Bräuche Food - + Essen Languages - + Sprachen Education - + Erziehung Dresses - + Kleidung Science - + Wissenschaft Calendar - + Zeitrechnung Bodily language - + Körpersprache Ethics - + Ethik Religion - + Religion Government - + Regierung Politics - + Politik Gender roles - + Geschlichterrollen Music and arts - + Musik und Kunst Architecture - + Architektur Military - + Militär Technology - + Technologie Courtship - + Balzverhalten Demography - + Demographie Transportation - + Transportmittel Medicine - + Medizin Magic system - + Magiesystem Rules - + Regeln Organization - + Organisation Magical objects - + Magische Objekte Magical places - + Magische Orte Magical races - + Magische Wesen Important places - + Wichtige Orte Important objects - + Wichtige Objekte From 628ed427855cf08c0574bc885e1d0e6d5263232b Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Mon, 6 Nov 2017 08:48:23 +0100 Subject: [PATCH 11/65] Updates German's compiled translation --- i18n/manuskript_de.qm | Bin 37679 -> 67048 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/i18n/manuskript_de.qm b/i18n/manuskript_de.qm index 7c4ce7fe389a1b49280251576f51e38bf32e1c74..21df45137dabdf801b7ee3980ddf131731c7adc4 100644 GIT binary patch literal 67048 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 E0i<6ai2wiq From 572feb54091c4e464278c9ddd721492f19e77fdc Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Mon, 6 Nov 2017 09:16:44 +0100 Subject: [PATCH 12/65] Creates empty import window --- .../{import_export => importer}/__init__.py | 0 .../{import_export => importer}/opml.py | 0 manuskript/mainWindow.py | 18 ++- manuskript/ui/editors/mainEditor.py | 2 +- manuskript/ui/importers/__init__.py | 0 manuskript/ui/importers/importer.py | 23 +++ manuskript/ui/importers/importer_ui.py | 72 ++++++++++ manuskript/ui/importers/importer_ui.ui | 133 ++++++++++++++++++ manuskript/ui/mainWindow.py | 6 + manuskript/ui/mainWindow.ui | 9 ++ 10 files changed, 258 insertions(+), 5 deletions(-) rename manuskript/{import_export => importer}/__init__.py (100%) rename manuskript/{import_export => importer}/opml.py (100%) create mode 100644 manuskript/ui/importers/__init__.py create mode 100644 manuskript/ui/importers/importer.py create mode 100644 manuskript/ui/importers/importer_ui.py create mode 100644 manuskript/ui/importers/importer_ui.ui diff --git a/manuskript/import_export/__init__.py b/manuskript/importer/__init__.py similarity index 100% rename from manuskript/import_export/__init__.py rename to manuskript/importer/__init__.py diff --git a/manuskript/import_export/opml.py b/manuskript/importer/opml.py similarity index 100% rename from manuskript/import_export/opml.py rename to manuskript/importer/opml.py diff --git a/manuskript/mainWindow.py b/manuskript/mainWindow.py index b47892e..6a01d1b 100644 --- a/manuskript/mainWindow.py +++ b/manuskript/mainWindow.py @@ -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) @@ -460,7 +462,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 +506,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 +1308,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() diff --git a/manuskript/ui/editors/mainEditor.py b/manuskript/ui/editors/mainEditor.py index d5dcc67..8791bfd 100644 --- a/manuskript/ui/editors/mainEditor.py +++ b/manuskript/ui/editors/mainEditor.py @@ -14,7 +14,7 @@ from manuskript.ui import style from manuskript.ui.editors.editorWidget import editorWidget from manuskript.ui.editors.fullScreenEditor import fullScreenEditor from manuskript.ui.editors.mainEditor_ui import Ui_mainEditor -from manuskript.import_export import opml as opmlInputExport +from manuskript.importer import opml as opmlInputExport locale.setlocale(locale.LC_ALL, '') diff --git a/manuskript/ui/importers/__init__.py b/manuskript/ui/importers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/manuskript/ui/importers/importer.py b/manuskript/ui/importers/importer.py new file mode 100644 index 0000000..24c62b1 --- /dev/null +++ b/manuskript/ui/importers/importer.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- +import json +import os + +from PyQt5.QtCore import Qt +from PyQt5.QtGui import QBrush, QColor, QIcon +from PyQt5.QtWidgets import QWidget + +from manuskript import exporter +from manuskript.functions import lightBlue, writablePath +from manuskript.ui.importers.importer_ui import Ui_importer + + +class importerDialog(QWidget, Ui_importer): + def __init__(self, parent=None, mw=None): + QWidget.__init__(self, parent) + self.setupUi(self) + + # Var + self.mw = mw + + #TODO diff --git a/manuskript/ui/importers/importer_ui.py b/manuskript/ui/importers/importer_ui.py new file mode 100644 index 0000000..c5ccc28 --- /dev/null +++ b/manuskript/ui/importers/importer_ui.py @@ -0,0 +1,72 @@ +# -*- 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(694, 489) + 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) + self.btnManageImporters = QtWidgets.QPushButton(importer) + icon = QtGui.QIcon.fromTheme("preferences-system") + self.btnManageImporters.setIcon(icon) + self.btnManageImporters.setObjectName("btnManageImporters") + self.horizontalLayout.addWidget(self.btnManageImporters) + 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.btnPreview = QtWidgets.QPushButton(importer) + icon = QtGui.QIcon.fromTheme("document-print-preview") + self.btnPreview.setIcon(icon) + self.btnPreview.setObjectName("btnPreview") + self.horizontalLayout.addWidget(self.btnPreview) + self.verticalLayout.addLayout(self.horizontalLayout) + self.splitter = QtWidgets.QSplitter(importer) + self.splitter.setOrientation(QtCore.Qt.Horizontal) + 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.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", "Import from:")) + self.btnManageImporters.setText(_translate("importer", "Manage importers")) + self.btnChoseFile.setText(_translate("importer", "Chose file")) + self.btnPreview.setText(_translate("importer", "Preview")) + self.grpSettings.setTitle(_translate("importer", "Settings")) + self.grpPreview.setTitle(_translate("importer", "Preview")) + diff --git a/manuskript/ui/importers/importer_ui.ui b/manuskript/ui/importers/importer_ui.ui new file mode 100644 index 0000000..2d3ce52 --- /dev/null +++ b/manuskript/ui/importers/importer_ui.ui @@ -0,0 +1,133 @@ + + + importer + + + + 0 + 0 + 694 + 489 + + + + Import + + + + + + + + Import from: + + + + + + + + + + Manage importers + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Chose file + + + + + + + + + + Preview + + + + + + + + + + + + + + Qt::Horizontal + + + + Settings + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + Preview + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + diff --git a/manuskript/ui/mainWindow.py b/manuskript/ui/mainWindow.py index 7323796..91bc55d 100644 --- a/manuskript/ui/mainWindow.py +++ b/manuskript/ui/mainWindow.py @@ -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,7 @@ 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…")) 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 05063d7..f341bd6 100644 --- a/manuskript/ui/mainWindow.ui +++ b/manuskript/ui/mainWindow.ui @@ -2116,6 +2116,7 @@ + @@ -2482,6 +2483,14 @@ QListView::item:hover { About Manuskript + + + + + + Import… + + From 72b44fe90d39eac4cc4fc38de6554f7516873ac0 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Mon, 6 Nov 2017 09:30:33 +0100 Subject: [PATCH 13/65] Fixes docstrings --- manuskript/importer/opml.py | 49 +++++++++++++++---------------------- 1 file changed, 20 insertions(+), 29 deletions(-) diff --git a/manuskript/importer/opml.py b/manuskript/importer/opml.py index 9dc6327..51ff936 100644 --- a/manuskript/importer/opml.py +++ b/manuskript/importer/opml.py @@ -1,7 +1,6 @@ #!/usr/bin/env python # --!-- coding: utf8 --!-- -# Import/export outline cards in OPML format from PyQt5.QtWidgets import QMessageBox from manuskript.models.outlineModel import outlineItem from manuskript.enums import Outline @@ -10,6 +9,9 @@ from manuskript.functions import mainWindow def importOpml(opmlFilePath, idx): + """ + Import/export outline cards in OPML format. + """ ret = False mw = mainWindow() @@ -62,10 +64,9 @@ def importOpml(opmlFilePath, idx): def parseItems(underElement, parentItem): text = underElement.get('text') if text is not None: - """ - In the case where the title is exceptionally long, trim it so it isn't - distracting in the tab label - """ + + # In the case where the title is exceptionally long, trim it so it isn't + # distracting in the tab label title = text[0:32] if len(title) < len(text): title += '...' @@ -79,11 +80,10 @@ def parseItems(underElement, parentItem): body = restoreNewLines(note) summary = body[0:128] else: - """ - There's no note (body), but there is a title. Fill the - body with the title to support cards that consist only - of a title. - """ + + # There's no note (body), but there is a title. Fill the + # body with the title to support cards that consist only + # of a title. body = text card.setData(Outline.summaryFull.value, summary) @@ -101,35 +101,26 @@ def parseItems(underElement, parentItem): return - -""" -Since XML parsers are notorious for stripping out significant newlines, -save them in a form we can restore after the parse. -""" - - def saveNewlines(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 - -""" -Restore any significant newlines -""" - - def restoreNewLines(inString): + """ + Restore any significant newlines + """ return inString.replace("{{lf}}", "\n") - -""" -Determine whether or not a string only contains whitespace. -""" - - def isWhitespaceOnly(inString): + """ + Determine whether or not a string only contains whitespace. + """ str = restoreNewLines(inString) str = ''.join(str.split()) From b520b12d7a6968344354bfaa071bb92e42f00705 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Mon, 6 Nov 2017 10:05:20 +0100 Subject: [PATCH 14/65] Adds a tip if pandoc is not installed. #190 --- manuskript/exporter/basic.py | 4 +++- manuskript/exporter/pandoc/__init__.py | 2 ++ manuskript/functions.py | 6 ++++++ manuskript/mainWindow.py | 3 +-- manuskript/ui/exporters/exporter.py | 18 ++++++++++++++---- 5 files changed, 26 insertions(+), 7 deletions(-) diff --git a/manuskript/exporter/basic.py b/manuskript/exporter/basic.py index 5acbe9e..0cf6100 100644 --- a/manuskript/exporter/basic.py +++ b/manuskript/exporter/basic.py @@ -19,6 +19,8 @@ class basicExporter: cmd = "" customPath = "" icon = "" + absentTip = "" # A tip displayed when exporter is absent. + absentURL = "" # URL to open if exporter is absent. def __init__(self): settings = QSettings() @@ -129,7 +131,7 @@ class basicFormat: @classmethod def isValid(cls): return True - + @classmethod def projectPath(cls): return os.path.dirname(os.path.abspath(mainWindow().currentProject)) diff --git a/manuskript/exporter/pandoc/__init__.py b/manuskript/exporter/pandoc/__init__.py index d03e017..55df717 100644 --- a/manuskript/exporter/pandoc/__init__.py +++ b/manuskript/exporter/pandoc/__init__.py @@ -22,6 +22,8 @@ class pandocExporter(basicExporter):

Website: http://pandoc.org/

""") cmd = "pandoc" + absentTip = "Install pandoc to benefit from a wide range of export formats (DocX, ePub, PDF, etc.)" + absentURL = "http://pandoc.org/installing.html" def __init__(self): basicExporter.__init__(self) diff --git a/manuskript/functions.py b/manuskript/functions.py index b7873ad..f708f9a 100644 --- a/manuskript/functions.py +++ b/manuskript/functions.py @@ -6,7 +6,9 @@ import re from random import * from PyQt5.QtCore import Qt, QRect, QStandardPaths, QObject, QRegExp, QDir +from PyQt5.QtCore import QUrl from PyQt5.QtGui import QBrush, QIcon, QPainter, QColor, QImage, QPixmap +from PyQt5.QtGui import QDesktopServices from PyQt5.QtWidgets import qApp, QTextEdit from manuskript.enums import Outline @@ -355,3 +357,7 @@ def customIcons(): ] return sorted(r) + +def openURL(url): + QDesktopServices.openUrl(QUrl(url)) + diff --git a/manuskript/mainWindow.py b/manuskript/mainWindow.py index b47892e..4996c09 100644 --- a/manuskript/mainWindow.py +++ b/manuskript/mainWindow.py @@ -1142,8 +1142,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): w.setDict(settings.dict) def openPyEnchantWebPage(self): - from PyQt5.QtGui import QDesktopServices - QDesktopServices.openUrl(QUrl("http://pythonhosted.org/pyenchant/")) + F.openURL("http://pythonhosted.org/pyenchant/") def toggleSpellcheck(self, val): settings.spellcheck = val diff --git a/manuskript/ui/exporters/exporter.py b/manuskript/ui/exporters/exporter.py index da1628c..a219efa 100644 --- a/manuskript/ui/exporters/exporter.py +++ b/manuskript/ui/exporters/exporter.py @@ -5,10 +5,10 @@ import os from PyQt5.QtCore import Qt from PyQt5.QtGui import QBrush, QColor, QIcon -from PyQt5.QtWidgets import QWidget +from PyQt5.QtWidgets import QWidget, QStyle from manuskript import exporter -from manuskript.functions import lightBlue, writablePath +from manuskript.functions import lightBlue, writablePath, openURL from manuskript.ui.exporters.exporter_ui import Ui_exporter from manuskript.ui.exporters.exportersManager import exportersManager @@ -42,7 +42,7 @@ class exporterDialog(QWidget, Ui_exporter): self.cmbExporters.clear() for E in exporter.exporters: - if not E.isValid(): + if not E.isValid() and not E.absentTip: continue self.cmbExporters.addItem(QIcon(E.icon), E.name) @@ -51,6 +51,10 @@ class exporterDialog(QWidget, Ui_exporter): item = self.cmbExporters.model().item(self.cmbExporters.count() - 1) item.setFlags(Qt.ItemIsEnabled) + if not E.isValid() and E.absentTip: + self.cmbExporters.addItem(self.style().standardIcon(QStyle.SP_MessageBoxWarning), E.absentTip, "::URL::" + E.absentURL) + continue + for f in E.exportTo: if not f.isValid(): @@ -60,6 +64,12 @@ class exporterDialog(QWidget, Ui_exporter): self.cmbExporters.addItem(QIcon.fromTheme(f.icon), name, E.name) def updateUi(self, index): + + # We check if we have an URL to open + data = self.cmbExporters.currentData() + if data and data[:7] == "::URL::" and data[7:]: + openURL(data[7:]) + E, F = self.getSelectedExporter() if not E or not F or not F.implemented: @@ -142,4 +152,4 @@ class exporterDialog(QWidget, Ui_exporter): item.widget().deleteLater() l.addWidget(widget) - widget.setParent(group) \ No newline at end of file + widget.setParent(group) From 340fceeda3c7aef756536e276e4a39a7089649a1 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Mon, 6 Nov 2017 18:21:40 +0100 Subject: [PATCH 15/65] Checkpoint: selecting file working, have to preview and import now. --- manuskript/ui/importers/importer.py | 119 ++++++++++++++++++++++++- manuskript/ui/importers/importer_ui.py | 26 +++++- manuskript/ui/importers/importer_ui.ui | 55 +++++++++++- manuskript/ui/mainWindow.py | 1 + manuskript/ui/mainWindow.ui | 3 + 5 files changed, 196 insertions(+), 8 deletions(-) diff --git a/manuskript/ui/importers/importer.py b/manuskript/ui/importers/importer.py index 24c62b1..3254a92 100644 --- a/manuskript/ui/importers/importer.py +++ b/manuskript/ui/importers/importer.py @@ -5,19 +5,132 @@ import os from PyQt5.QtCore import Qt from PyQt5.QtGui import QBrush, QColor, QIcon -from PyQt5.QtWidgets import QWidget +from PyQt5.QtWidgets import QWidget, QFileDialog from manuskript import exporter -from manuskript.functions import lightBlue, writablePath +from manuskript.functions import lightBlue, writablePath, appPath from manuskript.ui.importers.importer_ui import Ui_importer +class importFormat: + def __init__(self, name, icon, fileFormat): + self.name = name + self.icon = icon + self.fileFormat = fileFormat 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", + ".html": "text-html", + } + def __init__(self, parent=None, mw=None): QWidget.__init__(self, parent) self.setupUi(self) # Var self.mw = mw + self.fileName = "" - #TODO + # Register importFormats: + self.formats = [] + self.formats.append(importFormat("OPML", "text-x-opml+xml", + "OPML Files (*.opml)")) + self.formats.append(importFormat("Markdown", "text-x-markdown", + "Markdown files (*.md; *.txt; *)")) + self.formats.append(importFormat("Folder", "folder", + "<>")) + + # 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.setFileName("") + + ############################################################################ + # Combobox / Formats + ############################################################################ + + def populateImportList(self): + + def addFormat(name, icon): + self.cmbImporters.addItem(QIcon.fromTheme(icon), name) + + for f in self.formats: + addFormat(f.name, f.icon) + + ############################################################################ + # Import file + ############################################################################ + + def selectFile(self): + + # We find the current selected format + formatName = self.cmbImporters.currentText() + F = [F for F in self.formats if F.name == formatName][0] + + options = QFileDialog.Options() + options |= QFileDialog.DontUseNativeDialog + if F.fileFormat == "<>": + 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): + if fileName: + self.fileName = fileName + self.btnPreview.setEnabled(True) + self.lblFileName.setText(os.path.basename(fileName)) + self.lblFileName.setToolTip(fileName) + self.btnClearFileName.setVisible(True) + ext = os.path.splitext(fileName)[1] + if ext and ext in self.formatsIcon: + self.lblIcon.setVisible(True) + h = self.lblFileName.height() + self.lblIcon.setPixmap( + QIcon.fromTheme(self.formatsIcon[ext]).pixmap(h, h) + ) + elif os.path.isdir(fileName): + self.lblIcon.setVisible(True) + h = self.lblFileName.height() + self.lblIcon.setPixmap(QIcon.fromTheme("folder").pixmap(h, h)) + + else: + self.fileName = None + self.btnPreview.setEnabled(False) + self.lblFileName.setText("") + self.btnClearFileName.setVisible(False) + self.lblIcon.setVisible(False) + + ############################################################################ + # Preview + ############################################################################ + + def preview(self): + # TODO + pass + + ############################################################################ + # + ############################################################################ + + def getParentIndex(self): + if len(self.mw.treeRedacOutline.selectionModel(). + selection().indexes()) == 0: + idx = QModelIndex() + else: + idx = self.mw.treeRedacOutline.currentIndex() + return idx diff --git a/manuskript/ui/importers/importer_ui.py b/manuskript/ui/importers/importer_ui.py index c5ccc28..b6730a5 100644 --- a/manuskript/ui/importers/importer_ui.py +++ b/manuskript/ui/importers/importer_ui.py @@ -23,6 +23,7 @@ class Ui_importer(object): self.cmbImporters.setObjectName("cmbImporters") self.horizontalLayout.addWidget(self.cmbImporters) self.btnManageImporters = QtWidgets.QPushButton(importer) + self.btnManageImporters.setText("") icon = QtGui.QIcon.fromTheme("preferences-system") self.btnManageImporters.setIcon(icon) self.btnManageImporters.setObjectName("btnManageImporters") @@ -34,6 +35,27 @@ class Ui_importer(object): 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) @@ -63,8 +85,8 @@ class Ui_importer(object): def retranslateUi(self, importer): _translate = QtCore.QCoreApplication.translate importer.setWindowTitle(_translate("importer", "Import")) - self.label.setText(_translate("importer", "Import from:")) - self.btnManageImporters.setText(_translate("importer", "Manage importers")) + self.label.setText(_translate("importer", "Format:")) + self.btnManageImporters.setToolTip(_translate("importer", "Manage importers")) self.btnChoseFile.setText(_translate("importer", "Chose file")) self.btnPreview.setText(_translate("importer", "Preview")) self.grpSettings.setTitle(_translate("importer", "Settings")) diff --git a/manuskript/ui/importers/importer_ui.ui b/manuskript/ui/importers/importer_ui.ui index 2d3ce52..c7877ab 100644 --- a/manuskript/ui/importers/importer_ui.ui +++ b/manuskript/ui/importers/importer_ui.ui @@ -19,7 +19,7 @@ - Import from: + Format: @@ -28,9 +28,12 @@ - + Manage importers + + + @@ -39,7 +42,7 @@ - + Qt::Horizontal @@ -61,6 +64,52 @@ + + + + + + + + + + + + 75 + true + + + + + + + + + + + + + + + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + diff --git a/manuskript/ui/mainWindow.py b/manuskript/ui/mainWindow.py index 91bc55d..26b92db 100644 --- a/manuskript/ui/mainWindow.py +++ b/manuskript/ui/mainWindow.py @@ -1366,6 +1366,7 @@ class Ui_MainWindow(object): 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 diff --git a/manuskript/ui/mainWindow.ui b/manuskript/ui/mainWindow.ui index f341bd6..d13d875 100644 --- a/manuskript/ui/mainWindow.ui +++ b/manuskript/ui/mainWindow.ui @@ -2490,6 +2490,9 @@ QListView::item:hover { Import… + + F7 + From 0a0649a0db6c98de56ed2f2b82a0978784594bb2 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Mon, 6 Nov 2017 18:29:18 +0100 Subject: [PATCH 16/65] Fixes: Manuskript crashes when a wrong locale is set. See #188. --- manuskript/models/outlineModel.py | 7 ++++++- manuskript/ui/editors/mainEditor.py | 6 ++++-- manuskript/ui/editors/mainEditor_ui.py | 5 ++--- manuskript/ui/welcome.py | 6 ++++-- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/manuskript/models/outlineModel.py b/manuskript/models/outlineModel.py index 9b4f766..5261778 100644 --- a/manuskript/models/outlineModel.py +++ b/manuskript/models/outlineModel.py @@ -17,7 +17,12 @@ from lxml import etree as ET from manuskript.enums import Outline from manuskript.functions import mainWindow, toInt, wordCount, HTML2PlainText -locale.setlocale(locale.LC_ALL, '') +try: + locale.setlocale(locale.LC_ALL, '') +except: + # Invalid locale, but not really a big deal because it's used only for + # number formating + pass import time, os diff --git a/manuskript/ui/editors/mainEditor.py b/manuskript/ui/editors/mainEditor.py index 16358b0..f4591fa 100644 --- a/manuskript/ui/editors/mainEditor.py +++ b/manuskript/ui/editors/mainEditor.py @@ -15,8 +15,10 @@ from manuskript.ui.editors.editorWidget import editorWidget from manuskript.ui.editors.fullScreenEditor import fullScreenEditor from manuskript.ui.editors.mainEditor_ui import Ui_mainEditor -locale.setlocale(locale.LC_ALL, '') - +try: + locale.setlocale(locale.LC_ALL, '') +except: + pass class mainEditor(QWidget, Ui_mainEditor): """ diff --git a/manuskript/ui/editors/mainEditor_ui.py b/manuskript/ui/editors/mainEditor_ui.py index 269f0fc..45c8e6f 100644 --- a/manuskript/ui/editors/mainEditor_ui.py +++ b/manuskript/ui/editors/mainEditor_ui.py @@ -2,8 +2,7 @@ # Form implementation generated from reading ui file 'manuskript/ui/editors/mainEditor_ui.ui' # -# Created: Sat Oct 14 21:30:36 2017 -# by: PyQt5 UI code generator 5.2.1 +# Created by: PyQt5 UI code generator 5.9 # # WARNING! All changes made in this file will be lost! @@ -14,8 +13,8 @@ class Ui_mainEditor(object): mainEditor.setObjectName("mainEditor") mainEditor.resize(791, 319) self.verticalLayout = QtWidgets.QVBoxLayout(mainEditor) - self.verticalLayout.setSpacing(0) self.verticalLayout.setContentsMargins(0, 0, 0, 0) + self.verticalLayout.setSpacing(0) self.verticalLayout.setObjectName("verticalLayout") self.tabSplitter = tabSplitter(mainEditor) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Expanding) diff --git a/manuskript/ui/welcome.py b/manuskript/ui/welcome.py index c6d4c68..4b3286b 100644 --- a/manuskript/ui/welcome.py +++ b/manuskript/ui/welcome.py @@ -21,8 +21,10 @@ from manuskript.models.plotModel import plotModel from manuskript.models.worldModel import worldModel from manuskript.ui.welcome_ui import Ui_welcome -locale.setlocale(locale.LC_ALL, '') - +try: + locale.setlocale(locale.LC_ALL, '') +except: + pass class welcome(QWidget, Ui_welcome): def __init__(self, parent=None): From 221c7a181d6ea2985b57fee02e08f69e6a37983a Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Tue, 7 Nov 2017 11:25:19 +0100 Subject: [PATCH 17/65] Checkpoint: basic mechanics of settings implemented --- manuskript/importer/__init__.py | 12 ++ manuskript/importer/abstractImporter.py | 39 +++++ manuskript/importer/folderImporter.py | 12 ++ manuskript/importer/markdownImporter.py | 12 ++ manuskript/importer/opml.py | 127 ----------------- manuskript/importer/opmlImporter.py | 134 ++++++++++++++++++ manuskript/ui/editors/mainEditor.py | 20 --- manuskript/ui/editors/mainEditor_ui.py | 15 +- manuskript/ui/editors/mainEditor_ui.ui | 21 +-- .../manuskript/plainTextSettings_ui.py | 14 +- .../manuskript/plainTextSettings_ui.ui | 10 +- manuskript/ui/importers/generalSettings.py | 46 ++++++ manuskript/ui/importers/generalSettings_ui.py | 67 +++++++++ manuskript/ui/importers/generalSettings_ui.ui | 103 ++++++++++++++ manuskript/ui/importers/importer.py | 112 +++++++++------ manuskript/ui/importers/importer_ui.py | 19 ++- manuskript/ui/importers/importer_ui.ui | 47 +++--- 17 files changed, 552 insertions(+), 258 deletions(-) create mode 100644 manuskript/importer/abstractImporter.py create mode 100644 manuskript/importer/folderImporter.py create mode 100644 manuskript/importer/markdownImporter.py delete mode 100644 manuskript/importer/opml.py create mode 100644 manuskript/importer/opmlImporter.py create mode 100644 manuskript/ui/importers/generalSettings.py create mode 100644 manuskript/ui/importers/generalSettings_ui.py create mode 100644 manuskript/ui/importers/generalSettings_ui.ui diff --git a/manuskript/importer/__init__.py b/manuskript/importer/__init__.py index e69de29..d2f0bc1 100644 --- a/manuskript/importer/__init__.py +++ b/manuskript/importer/__init__.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- + +from manuskript.importer.folderImporter import folderImporter +from manuskript.importer.markdownImporter import markdownImporter +from manuskript.importer.opmlImporter import opmlImporter + +importers = [ + markdownImporter, + opmlImporter, + folderImporter, + ] diff --git a/manuskript/importer/abstractImporter.py b/manuskript/importer/abstractImporter.py new file mode 100644 index 0000000..822ebe5 --- /dev/null +++ b/manuskript/importer/abstractImporter.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- +import os +import shutil +import subprocess + +from PyQt5.QtCore import QSettings +from PyQt5.QtWidgets import QWidget + + +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 "<>" + icon = "" + + @classmethod + def startImport(cls, filePath): + """ + Takes a str path to the file/folder to import, and return `outlineItem`s. + """ + pass + + @classmethod + def settingsWidget(cls): + """ + Returns a QWidget if needed for settings. + """ + return None + + diff --git a/manuskript/importer/folderImporter.py b/manuskript/importer/folderImporter.py new file mode 100644 index 0000000..a961888 --- /dev/null +++ b/manuskript/importer/folderImporter.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- + +from manuskript.importer.abstractImporter import abstractImporter + + +class folderImporter(abstractImporter): + + name = "Folder" + description = "" + fileFormat = "<>" + icon = "folder" diff --git a/manuskript/importer/markdownImporter.py b/manuskript/importer/markdownImporter.py new file mode 100644 index 0000000..0a3a5cd --- /dev/null +++ b/manuskript/importer/markdownImporter.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- + +from manuskript.importer.abstractImporter import abstractImporter + + +class markdownImporter(abstractImporter): + + name = "Markdown" + description = "" + fileFormat = "Markdown files (*.md; *.txt; *)" + icon = "text-x-markdown" diff --git a/manuskript/importer/opml.py b/manuskript/importer/opml.py deleted file mode 100644 index 51ff936..0000000 --- a/manuskript/importer/opml.py +++ /dev/null @@ -1,127 +0,0 @@ -#!/usr/bin/env python -# --!-- coding: utf8 --!-- - -from PyQt5.QtWidgets import QMessageBox -from manuskript.models.outlineModel import outlineItem -from manuskript.enums import Outline -from lxml import etree as ET -from manuskript.functions import mainWindow - - -def importOpml(opmlFilePath, idx): - """ - Import/export outline cards in OPML format. - """ - ret = False - mw = mainWindow() - - try: - with open(opmlFilePath, 'r') as opmlFile: - opmlContent = saveNewlines(opmlFile.read()) - except: - # TODO: Translation - QMessageBox.critical(mw, mw.tr("OPML Import"), - mw.tr("File open failed.")) - return False - - mdl = mw.mdlOutline - - if idx.internalPointer() is not None: - parentItem = idx.internalPointer() - else: - parentItem = mdl.rootItem - - try: - parsed = ET.fromstring(bytes(opmlContent, 'utf-8')) - - opmlNode = parsed - bodyNode = opmlNode.find("body") - - if bodyNode is not None: - outlineEls = bodyNode.findall("outline") - - if outlineEls is not None: - for element in outlineEls: - parseItems(element, parentItem) - - mdl.layoutChanged.emit() - mw.treeRedacOutline.viewport().update() - ret = True - except: - pass - - # TODO: Translation - if ret: - QMessageBox.information(mw, mw.tr("OPML Import"), - mw.tr("Import Complete.")) - else: - QMessageBox.critical(mw, mw.tr("OPML Import"), - mw.tr("This does not appear to be a valid OPML file.")) - - return ret - - -def parseItems(underElement, parentItem): - text = underElement.get('text') - if text is not None: - - # In the case where the title is exceptionally long, trim it so it isn't - # distracting in the tab label - title = text[0:32] - if len(title) < len(text): - title += '...' - - card = outlineItem(parent=parentItem, title=title) - - body = "" - summary = "" - note = underElement.get('_note') - if note is not None and not isWhitespaceOnly(note): - body = restoreNewLines(note) - summary = body[0:128] - else: - - # There's no note (body), but there is a title. Fill the - # body with the title to support cards that consist only - # of a title. - body = text - - card.setData(Outline.summaryFull.value, summary) - - children = underElement.findall('outline') - if children is not None and len(children) > 0: - for el in children: - parseItems(el, card) - else: - card.setData(Outline.type.value, 'md') - card.setData(Outline.text.value, body) - - # I assume I don't have to do the following - # parentItem.appendChild(card) - - return - -def saveNewlines(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 - -def restoreNewLines(inString): - """ - Restore any significant newlines - """ - return inString.replace("{{lf}}", "\n") - -def isWhitespaceOnly(inString): - """ - Determine whether or not a string only contains whitespace. - """ - str = restoreNewLines(inString) - str = ''.join(str.split()) - - return len(str) is 0 diff --git a/manuskript/importer/opmlImporter.py b/manuskript/importer/opmlImporter.py new file mode 100644 index 0000000..636d7b4 --- /dev/null +++ b/manuskript/importer/opmlImporter.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- + +from PyQt5.QtWidgets import 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)" + icon = "text-x-opml+xml" + + def importOpml(opmlFilePath, idx): + """ + Import/export outline cards in OPML format. + """ + ret = False + mw = mainWindow() + + try: + with open(opmlFilePath, 'r') as opmlFile: + opmlContent = saveNewlines(opmlFile.read()) + except: + # TODO: Translation + QMessageBox.critical(mw, mw.tr("OPML Import"), + mw.tr("File open failed.")) + return False + + mdl = mw.mdlOutline + + if idx.internalPointer() is not None: + parentItem = idx.internalPointer() + else: + parentItem = mdl.rootItem + + try: + parsed = ET.fromstring(bytes(opmlContent, 'utf-8')) + + opmlNode = parsed + bodyNode = opmlNode.find("body") + + if bodyNode is not None: + outlineEls = bodyNode.findall("outline") + + if outlineEls is not None: + for element in outlineEls: + parseItems(element, parentItem) + + mdl.layoutChanged.emit() + mw.treeRedacOutline.viewport().update() + ret = True + except: + pass + + # TODO: Translation + if ret: + QMessageBox.information(mw, mw.tr("OPML Import"), + mw.tr("Import Complete.")) + else: + QMessageBox.critical(mw, mw.tr("OPML Import"), + mw.tr("This does not appear to be a valid OPML file.")) + + return ret + + + def parseItems(underElement, parentItem): + text = underElement.get('text') + if text is not None: + + # In the case where the title is exceptionally long, trim it so it isn't + # distracting in the tab label + title = text[0:32] + if len(title) < len(text): + title += '...' + + card = outlineItem(parent=parentItem, title=title) + + body = "" + summary = "" + note = underElement.get('_note') + if note is not None and not isWhitespaceOnly(note): + body = restoreNewLines(note) + summary = body[0:128] + else: + + # There's no note (body), but there is a title. Fill the + # body with the title to support cards that consist only + # of a title. + body = text + + card.setData(Outline.summaryFull.value, summary) + + children = underElement.findall('outline') + if children is not None and len(children) > 0: + for el in children: + parseItems(el, card) + else: + card.setData(Outline.type.value, 'md') + card.setData(Outline.text.value, body) + + # I assume I don't have to do the following + # parentItem.appendChild(card) + + return + + def saveNewlines(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 + + def restoreNewLines(inString): + """ + Restore any significant newlines + """ + return inString.replace("{{lf}}", "\n") + + def isWhitespaceOnly(inString): + """ + Determine whether or not a string only contains whitespace. + """ + str = restoreNewLines(inString) + str = ''.join(str.split()) + + return len(str) is 0 diff --git a/manuskript/ui/editors/mainEditor.py b/manuskript/ui/editors/mainEditor.py index 8791bfd..16358b0 100644 --- a/manuskript/ui/editors/mainEditor.py +++ b/manuskript/ui/editors/mainEditor.py @@ -14,7 +14,6 @@ from manuskript.ui import style from manuskript.ui.editors.editorWidget import editorWidget from manuskript.ui.editors.fullScreenEditor import fullScreenEditor from manuskript.ui.editors.mainEditor_ui import Ui_mainEditor -from manuskript.importer import opml as opmlInputExport locale.setlocale(locale.LC_ALL, '') @@ -84,10 +83,6 @@ class mainEditor(QWidget, Ui_mainEditor): self.btnRedacFullscreen.clicked.connect( self.showFullScreen, AUC) - self.btnImport.clicked.connect( - lambda v: self.importOPML() - ) - # self.tab.setDocumentMode(False) # Bug in Qt < 5.5: doesn't always load icons from custom theme. @@ -264,7 +259,6 @@ class mainEditor(QWidget, Ui_mainEditor): self.btnRedacFolderText.setVisible(visible) self.btnRedacFolderCork.setVisible(visible) self.btnRedacFolderOutline.setVisible(visible) - self.btnImport.setVisible(visible) self.sldCorkSizeFactor.setVisible(visible and self.btnRedacFolderCork.isChecked()) self.btnRedacFullscreen.setVisible(not visible) @@ -344,20 +338,6 @@ class mainEditor(QWidget, Ui_mainEditor): if self.currentEditor(): self._fullScreen = fullScreenEditor(self.currentEditor().currentIndex) - def importOPML(self): - from PyQt5.QtWidgets import QFileDialog - options = QFileDialog.Options() - options |= QFileDialog.DontUseNativeDialog - fileName, _ = QFileDialog.getOpenFileName(self, "Import OPML", "", - "OPML Files (*.opml)", options=options) - if fileName: - if len(self.mw.treeRedacOutline.selectionModel(). - selection().indexes()) == 0: - idx = QModelIndex() - else: - idx = self.mw.treeRedacOutline.currentIndex() - opmlInputExport.importOpml(fileName, idx) - ############################################################################### # DICT AND STUFF LIKE THAT ############################################################################### diff --git a/manuskript/ui/editors/mainEditor_ui.py b/manuskript/ui/editors/mainEditor_ui.py index 22fd1f0..45c8e6f 100644 --- a/manuskript/ui/editors/mainEditor_ui.py +++ b/manuskript/ui/editors/mainEditor_ui.py @@ -2,8 +2,7 @@ # Form implementation generated from reading ui file 'manuskript/ui/editors/mainEditor_ui.ui' # -# Created: Sat Oct 14 21:30:36 2017 -# by: PyQt5 UI code generator 5.2.1 +# Created by: PyQt5 UI code generator 5.9 # # WARNING! All changes made in this file will be lost! @@ -14,8 +13,8 @@ class Ui_mainEditor(object): mainEditor.setObjectName("mainEditor") mainEditor.resize(791, 319) self.verticalLayout = QtWidgets.QVBoxLayout(mainEditor) - self.verticalLayout.setSpacing(0) self.verticalLayout.setContentsMargins(0, 0, 0, 0) + self.verticalLayout.setSpacing(0) self.verticalLayout.setObjectName("verticalLayout") self.tabSplitter = tabSplitter(mainEditor) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Expanding) @@ -55,14 +54,6 @@ class Ui_mainEditor(object): self.btnRedacFolderOutline.setObjectName("btnRedacFolderOutline") self.buttonGroup.addButton(self.btnRedacFolderOutline) self.horizontalLayout_19.addWidget(self.btnRedacFolderOutline) - self.btnImport = QtWidgets.QPushButton(mainEditor) - self.btnImport.setText("") - icon = QtGui.QIcon.fromTheme("document-open") - self.btnImport.setIcon(icon) - self.btnImport.setFlat(True) - self.btnImport.setObjectName("btnImport") - self.buttonGroup.addButton(self.btnImport) - self.horizontalLayout_19.addWidget(self.btnImport) self.sldCorkSizeFactor = QtWidgets.QSlider(mainEditor) self.sldCorkSizeFactor.setMinimumSize(QtCore.QSize(100, 0)) self.sldCorkSizeFactor.setMaximumSize(QtCore.QSize(200, 16777215)) @@ -117,8 +108,6 @@ class Ui_mainEditor(object): self.btnRedacFolderCork.setText(_translate("mainEditor", "Index cards")) self.btnRedacFolderOutline.setText(_translate("mainEditor", "Outline")) self.btnRedacFullscreen.setShortcut(_translate("mainEditor", "F11")) - # TODO: Translation - self.btnImport.setToolTip(_translate("mainEditor", "Import items from an OPML file into the current folder")) from manuskript.ui.editors.tabSplitter import tabSplitter from manuskript.ui.editors.textFormat import textFormat diff --git a/manuskript/ui/editors/mainEditor_ui.ui b/manuskript/ui/editors/mainEditor_ui.ui index ce73c97..1110a34 100644 --- a/manuskript/ui/editors/mainEditor_ui.ui +++ b/manuskript/ui/editors/mainEditor_ui.ui @@ -50,7 +50,9 @@ - + + + Alt+Up @@ -111,23 +113,6 @@ - - - - - Import items from an OPML file into the current folder - - - - - - - - - true - - - diff --git a/manuskript/ui/exporters/manuskript/plainTextSettings_ui.py b/manuskript/ui/exporters/manuskript/plainTextSettings_ui.py index 77598ba..a6c8f98 100644 --- a/manuskript/ui/exporters/manuskript/plainTextSettings_ui.py +++ b/manuskript/ui/exporters/manuskript/plainTextSettings_ui.py @@ -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") diff --git a/manuskript/ui/exporters/manuskript/plainTextSettings_ui.ui b/manuskript/ui/exporters/manuskript/plainTextSettings_ui.ui index 8064947..15c1a5e 100644 --- a/manuskript/ui/exporters/manuskript/plainTextSettings_ui.ui +++ b/manuskript/ui/exporters/manuskript/plainTextSettings_ui.ui @@ -53,8 +53,8 @@ QToolBox::tab:selected, QToolBox::tab:hover{ 0 0 - 491 - 842 + 497 + 834 @@ -233,7 +233,7 @@ QToolBox::tab:selected, QToolBox::tab:hover{ 0 0 511 - 522 + 534 @@ -773,7 +773,7 @@ QToolBox::tab:selected, QToolBox::tab:hover{ 0 0 511 - 522 + 534 @@ -1162,7 +1162,7 @@ QToolBox::tab:selected, QToolBox::tab:hover{ 0 0 511 - 522 + 534 diff --git a/manuskript/ui/importers/generalSettings.py b/manuskript/ui/importers/generalSettings.py new file mode 100644 index 0000000..7dd96fc --- /dev/null +++ b/manuskript/ui/importers/generalSettings.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- +import json +import os + +from PyQt5.QtCore import Qt, QSize, QSortFilterProxyModel +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 + +class generalSettings(QWidget, Ui_generalSettings): + def __init__(self, parent=None): + QWidget.__init__(self, parent) + self.setupUi(self) + + self.mw = mainWindow() + + # 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() + self.treeGeneralParent.selectionModel().currentChanged.connect( + lambda: print(self.treeGeneralParent.currentIndex().row())) + + 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 + diff --git a/manuskript/ui/importers/generalSettings_ui.py b/manuskript/ui/importers/generalSettings_ui.py new file mode 100644 index 0000000..078ca2b --- /dev/null +++ b/manuskript/ui/importers/generalSettings_ui.py @@ -0,0 +1,67 @@ +# -*- 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(289, 396) + 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, 289, 373)) + self.general.setObjectName("general") + self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.general) + self.verticalLayout_5.setContentsMargins(0, 0, 0, 0) + 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.chkGeneralParent = QtWidgets.QCheckBox(self.general) + self.chkGeneralParent.setObjectName("chkGeneralParent") + self.formLayout_4.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.chkGeneralParent) + self.chkGeneralSplitScenes = QtWidgets.QCheckBox(self.general) + self.chkGeneralSplitScenes.setObjectName("chkGeneralSplitScenes") + self.formLayout_4.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.chkGeneralSplitScenes) + self.txtGeneralSplitScenes = QtWidgets.QLineEdit(self.general) + self.txtGeneralSplitScenes.setObjectName("txtGeneralSplitScenes") + self.formLayout_4.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.txtGeneralSplitScenes) + self.treeGeneralParent = QtWidgets.QTreeView(self.general) + self.treeGeneralParent.setHeaderHidden(True) + self.treeGeneralParent.setObjectName("treeGeneralParent") + self.formLayout_4.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.treeGeneralParent) + 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.chkGeneralParent.setText(_translate("generalSettings", "Import under:")) + self.chkGeneralSplitScenes.setText(_translate("generalSettings", "Split scenes at:")) + self.toolBox.setItemText(self.toolBox.indexOf(self.general), _translate("generalSettings", "General")) + diff --git a/manuskript/ui/importers/generalSettings_ui.ui b/manuskript/ui/importers/generalSettings_ui.ui new file mode 100644 index 0000000..53446d2 --- /dev/null +++ b/manuskript/ui/importers/generalSettings_ui.ui @@ -0,0 +1,103 @@ + + + generalSettings + + + + 0 + 0 + 289 + 396 + + + + Form + + + + 10 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QToolBox::tab{ + background-color: #BBB; + padding: 2px; + border: none; +} + +QToolBox::tab:selected, QToolBox::tab:hover{ + background-color:skyblue; +} + + + 0 + + + 0 + + + + + 0 + 0 + 289 + 373 + + + + General + + + + + + QFormLayout::WrapLongRows + + + + + Import under: + + + + + + + Split scenes at: + + + + + + + + + + true + + + + + + + + + + + + + + diff --git a/manuskript/ui/importers/importer.py b/manuskript/ui/importers/importer.py index 3254a92..4aaad99 100644 --- a/manuskript/ui/importers/importer.py +++ b/manuskript/ui/importers/importer.py @@ -7,15 +7,11 @@ from PyQt5.QtCore import Qt from PyQt5.QtGui import QBrush, QColor, QIcon from PyQt5.QtWidgets import QWidget, QFileDialog -from manuskript import exporter from manuskript.functions import lightBlue, writablePath, appPath from manuskript.ui.importers.importer_ui import Ui_importer - -class importFormat: - def __init__(self, name, icon, fileFormat): - self.name = name - self.icon = icon - self.fileFormat = fileFormat +from manuskript.ui.importers.generalSettings import generalSettings +from manuskript.ui import style +from manuskript import importer class importerDialog(QWidget, Ui_importer): @@ -37,15 +33,10 @@ class importerDialog(QWidget, Ui_importer): # Var self.mw = mw self.fileName = "" + self.setStyleSheet(style.mainWindowSS()) # Register importFormats: - self.formats = [] - self.formats.append(importFormat("OPML", "text-x-opml+xml", - "OPML Files (*.opml)")) - self.formats.append(importFormat("Markdown", "text-x-markdown", - "Markdown files (*.md; *.txt; *)")) - self.formats.append(importFormat("Folder", "folder", - "<>")) + self.importers = importer.importers # Populate combo box with formats self.populateImportList() @@ -54,7 +45,10 @@ class importerDialog(QWidget, Ui_importer): self.btnChoseFile.clicked.connect(self.selectFile) self.btnClearFileName.clicked.connect(self.setFileName) self.btnPreview.clicked.connect(self.preview) - self.setFileName("") + self.cmbImporters.currentTextChanged.connect(self.updateSettings) + + #self.setFileName("") + self.setFileName("/home/olivier/Dropbox/Documents/Travail/Geekeries/Python/PyCharmProjects/manuskript/test-projects/IMPORTS/End Plan 2.opml") ############################################################################ # Combobox / Formats @@ -65,18 +59,25 @@ class importerDialog(QWidget, Ui_importer): def addFormat(name, icon): self.cmbImporters.addItem(QIcon.fromTheme(icon), name) - for f in self.formats: + for f in self.importers: addFormat(f.name, f.icon) + def currentFormat(self): + formatName = self.cmbImporters.currentText() + F = [F for F in self.importers if F.name == formatName][0] + return F + ############################################################################ # Import file ############################################################################ def selectFile(self): + """ + Called to select a file in the file system. Uses QFileDialog. + """ # We find the current selected format - formatName = self.cmbImporters.currentText() - F = [F for F in self.formats if F.name == formatName][0] + F = self.currentFormat() options = QFileDialog.Options() options |= QFileDialog.DontUseNativeDialog @@ -90,47 +91,70 @@ class importerDialog(QWidget, Ui_importer): self.setFileName(fileName) def setFileName(self, fileName): + """ + Updates Ui with given filename. Filename can be empty. + """ if fileName: self.fileName = fileName - self.btnPreview.setEnabled(True) self.lblFileName.setText(os.path.basename(fileName)) self.lblFileName.setToolTip(fileName) - self.btnClearFileName.setVisible(True) ext = os.path.splitext(fileName)[1] if ext and ext in self.formatsIcon: - self.lblIcon.setVisible(True) - h = self.lblFileName.height() - self.lblIcon.setPixmap( - QIcon.fromTheme(self.formatsIcon[ext]).pixmap(h, h) - ) + icon = QIcon.fromTheme(self.formatsIcon[ext]) elif os.path.isdir(fileName): - self.lblIcon.setVisible(True) - h = self.lblFileName.height() - self.lblIcon.setPixmap(QIcon.fromTheme("folder").pixmap(h, h)) + icon = QIcon.fromTheme("folder") + + #self.lblIcon.setVisible(True) + h = self.lblFileName.height() + self.lblIcon.setPixmap(icon.pixmap(h, h)) else: self.fileName = None - self.btnPreview.setEnabled(False) self.lblFileName.setText("") - self.btnClearFileName.setVisible(False) - self.lblIcon.setVisible(False) + + 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) ############################################################################ - # Preview + # UI + ############################################################################ + + def updateSettings(self): + """ + When the current format change (through the combobox), we update the + settings widget using the current format provided settings widget. + """ + + F = self.currentFormat() + self.settingsWidget = generalSettings() + self.setGroupWidget(self.grpSettings, self.settingsWidget) + + #TODO: custom format widget + + 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): # TODO pass - - ############################################################################ - # - ############################################################################ - - def getParentIndex(self): - if len(self.mw.treeRedacOutline.selectionModel(). - selection().indexes()) == 0: - idx = QModelIndex() - else: - idx = self.mw.treeRedacOutline.currentIndex() - return idx diff --git a/manuskript/ui/importers/importer_ui.py b/manuskript/ui/importers/importer_ui.py index b6730a5..7d39329 100644 --- a/manuskript/ui/importers/importer_ui.py +++ b/manuskript/ui/importers/importer_ui.py @@ -22,12 +22,6 @@ class Ui_importer(object): self.cmbImporters = QtWidgets.QComboBox(importer) self.cmbImporters.setObjectName("cmbImporters") self.horizontalLayout.addWidget(self.cmbImporters) - self.btnManageImporters = QtWidgets.QPushButton(importer) - self.btnManageImporters.setText("") - icon = QtGui.QIcon.fromTheme("preferences-system") - self.btnManageImporters.setIcon(icon) - self.btnManageImporters.setObjectName("btnManageImporters") - self.horizontalLayout.addWidget(self.btnManageImporters) spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem) self.btnChoseFile = QtWidgets.QPushButton(importer) @@ -59,8 +53,14 @@ class Ui_importer(object): 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) @@ -77,6 +77,10 @@ class Ui_importer(object): self.verticalLayout_2.setContentsMargins(0, 0, 0, 0) self.verticalLayout_2.setSpacing(0) self.verticalLayout_2.setObjectName("verticalLayout_2") + self.splitter_2 = QtWidgets.QSplitter(self.grpPreview) + self.splitter_2.setOrientation(QtCore.Qt.Horizontal) + self.splitter_2.setObjectName("splitter_2") + self.verticalLayout_2.addWidget(self.splitter_2) self.verticalLayout.addWidget(self.splitter) self.retranslateUi(importer) @@ -86,9 +90,10 @@ class Ui_importer(object): _translate = QtCore.QCoreApplication.translate importer.setWindowTitle(_translate("importer", "Import")) self.label.setText(_translate("importer", "Format:")) - self.btnManageImporters.setToolTip(_translate("importer", "Manage importers")) 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")) diff --git a/manuskript/ui/importers/importer_ui.ui b/manuskript/ui/importers/importer_ui.ui index c7877ab..404db62 100644 --- a/manuskript/ui/importers/importer_ui.ui +++ b/manuskript/ui/importers/importer_ui.ui @@ -26,21 +26,6 @@ - - - - Manage importers - - - - - - - - - - - @@ -60,7 +45,8 @@ Chose file - + + ../../../../../../../../../../.designer/backup../../../../../../../../../../.designer/backup @@ -86,11 +72,15 @@ + + Clear file + - + + ../../../../../../../../../../.designer/backup../../../../../../../../../../.designer/backup true @@ -117,8 +107,20 @@ - - + ../../../../../../../../../../.designer/backup../../../../../../../../../../.designer/backup + + + true + + + + + + + Import + + + @@ -171,6 +173,13 @@ 0 + + + + Qt::Horizontal + + + From d51233ebba295d1f1a0a99af22d8e27706a5bec2 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Tue, 7 Nov 2017 12:02:02 +0100 Subject: [PATCH 18/65] Cleans treeView of some call to mainWindow that should be here --- manuskript/mainWindow.py | 3 ++ manuskript/ui/importers/importer.py | 3 +- manuskript/ui/importers/importer_ui.py | 6 ++++ manuskript/ui/importers/importer_ui.ui | 15 ++++++++ manuskript/ui/views/outlineBasics.py | 49 ++++++++++++++++++++++---- manuskript/ui/views/treeView.py | 24 ++----------- 6 files changed, 71 insertions(+), 29 deletions(-) diff --git a/manuskript/mainWindow.py b/manuskript/mainWindow.py index 6a01d1b..f72bd8b 100644 --- a/manuskript/mainWindow.py +++ b/manuskript/mainWindow.py @@ -383,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 ############################################################################### diff --git a/manuskript/ui/importers/importer.py b/manuskript/ui/importers/importer.py index 4aaad99..72152f3 100644 --- a/manuskript/ui/importers/importer.py +++ b/manuskript/ui/importers/importer.py @@ -12,6 +12,7 @@ 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 class importerDialog(QWidget, Ui_importer): @@ -157,4 +158,4 @@ class importerDialog(QWidget, Ui_importer): def preview(self): # TODO - pass + previewModel = outlineModel(self) diff --git a/manuskript/ui/importers/importer_ui.py b/manuskript/ui/importers/importer_ui.py index 7d39329..44afddc 100644 --- a/manuskript/ui/importers/importer_ui.py +++ b/manuskript/ui/importers/importer_ui.py @@ -80,6 +80,10 @@ class Ui_importer(object): self.splitter_2 = QtWidgets.QSplitter(self.grpPreview) self.splitter_2.setOrientation(QtCore.Qt.Horizontal) self.splitter_2.setObjectName("splitter_2") + self.tree = treeView(self.splitter_2) + self.tree.setObjectName("tree") + self.editor = mainEditor(self.splitter_2) + self.editor.setObjectName("editor") self.verticalLayout_2.addWidget(self.splitter_2) self.verticalLayout.addWidget(self.splitter) @@ -97,3 +101,5 @@ class Ui_importer(object): self.grpSettings.setTitle(_translate("importer", "Settings")) self.grpPreview.setTitle(_translate("importer", "Preview")) +from manuskript.ui.editors.mainEditor import mainEditor +from manuskript.ui.views.treeView import treeView diff --git a/manuskript/ui/importers/importer_ui.ui b/manuskript/ui/importers/importer_ui.ui index 404db62..afb053e 100644 --- a/manuskript/ui/importers/importer_ui.ui +++ b/manuskript/ui/importers/importer_ui.ui @@ -178,6 +178,8 @@ Qt::Horizontal + + @@ -186,6 +188,19 @@ + + + treeView + QTreeView +
manuskript.ui.views.treeView.h
+
+ + mainEditor + QWidget +
manuskript.ui.editors.mainEditor.h
+ 1 +
+
diff --git a/manuskript/ui/views/outlineBasics.py b/manuskript/ui/views/outlineBasics.py index 8c21a8e..7b12f79 100644 --- a/manuskript/ui/views/outlineBasics.py +++ b/manuskript/ui/views/outlineBasics.py @@ -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() diff --git a/manuskript/ui/views/treeView.py b/manuskript/ui/views/treeView.py index 53a063f..4e28d1c 100644 --- a/manuskript/ui/views/treeView.py +++ b/manuskript/ui/views/treeView.py @@ -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() From 316651245c866898a4550adcfe61ca17763683fd Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Tue, 7 Nov 2017 12:50:40 +0100 Subject: [PATCH 19/65] Checkpoint: opml import can be previewed in tree view --- manuskript/importer/abstractImporter.py | 7 +- manuskript/importer/opmlImporter.py | 100 ++++++++++++------ manuskript/ui/importers/generalSettings_ui.py | 4 + manuskript/ui/importers/generalSettings_ui.ui | 7 ++ manuskript/ui/importers/importer.py | 27 ++++- manuskript/ui/importers/importer_ui.py | 5 +- manuskript/ui/importers/importer_ui.ui | 14 +-- 7 files changed, 117 insertions(+), 47 deletions(-) diff --git a/manuskript/importer/abstractImporter.py b/manuskript/importer/abstractImporter.py index 822ebe5..7c75a68 100644 --- a/manuskript/importer/abstractImporter.py +++ b/manuskript/importer/abstractImporter.py @@ -22,10 +22,11 @@ class abstractImporter: # For folder, use "<>" icon = "" - @classmethod - def startImport(cls, filePath): + def startImport(self, filePath, settingsWidget): """ - Takes a str path to the file/folder to import, and return `outlineItem`s. + 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 diff --git a/manuskript/importer/opmlImporter.py b/manuskript/importer/opmlImporter.py index 636d7b4..aff3728 100644 --- a/manuskript/importer/opmlImporter.py +++ b/manuskript/importer/opmlImporter.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # --!-- coding: utf8 --!-- -from PyQt5.QtWidgets import QMessageBox +from PyQt5.QtWidgets import qApp, QMessageBox from manuskript.models.outlineModel import outlineItem from manuskript.enums import Outline from lxml import etree as ET @@ -15,9 +15,56 @@ class opmlImporter(abstractImporter): fileFormat = "OPML Files (*.opml)" icon = "text-x-opml+xml" + @classmethod + def startImport(cls, filePath, parentItem, settingsWidget): + """ + Import/export outline cards in OPML format. + """ + ret = False + + try: + with open(filePath, 'r') as opmlFile: + opmlContent = cls.saveNewlines(opmlFile.read()) + except: + QMessageBox.critical(settingsWidget, + qApp.translate("Import", "OPML Import"), + qApp.translate("Import", "File open failed.")) + return None + + parsed = ET.fromstring(bytes(opmlContent, 'utf-8')) + + 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.append(cls.parseItems(element, parentItem)) + ret = True + + if ret: + #QMessageBox.information( + #settingsWidget, + #qApp.translate("Import", "OPML Import"), + #qApp.translate("Import", "Import Complete.")) + pass + else: + 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 + def importOpml(opmlFilePath, idx): """ Import/export outline cards in OPML format. + #FIXME: delete me when done with startImport """ ret = False mw = mainWindow() @@ -26,7 +73,6 @@ class opmlImporter(abstractImporter): with open(opmlFilePath, 'r') as opmlFile: opmlContent = saveNewlines(opmlFile.read()) except: - # TODO: Translation QMessageBox.critical(mw, mw.tr("OPML Import"), mw.tr("File open failed.")) return False @@ -67,48 +113,30 @@ class opmlImporter(abstractImporter): return ret - - def parseItems(underElement, parentItem): - text = underElement.get('text') - if text is not None: - - # In the case where the title is exceptionally long, trim it so it isn't - # distracting in the tab label - title = text[0:32] - if len(title) < len(text): - title += '...' + @classmethod + def parseItems(cls, underElement, parentItem=None): + title = underElement.get('text') + if title is not None: card = outlineItem(parent=parentItem, title=title) body = "" - summary = "" note = underElement.get('_note') - if note is not None and not isWhitespaceOnly(note): - body = restoreNewLines(note) - summary = body[0:128] - else: - - # There's no note (body), but there is a title. Fill the - # body with the title to support cards that consist only - # of a title. - body = text - - card.setData(Outline.summaryFull.value, summary) + if note is not None and not cls.isWhitespaceOnly(note): + body = cls.restoreNewLines(note) children = underElement.findall('outline') if children is not None and len(children) > 0: for el in children: - parseItems(el, card) + cls.parseItems(el, card) else: card.setData(Outline.type.value, 'md') card.setData(Outline.text.value, body) - # I assume I don't have to do the following - # parentItem.appendChild(card) + return card - return - - def saveNewlines(inString): + @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. @@ -118,17 +146,19 @@ class opmlImporter(abstractImporter): return inString - def restoreNewLines(inString): + @classmethod + def restoreNewLines(cls, inString): """ Restore any significant newlines """ return inString.replace("{{lf}}", "\n") - def isWhitespaceOnly(inString): + @classmethod + def isWhitespaceOnly(cls, inString): """ Determine whether or not a string only contains whitespace. """ - str = restoreNewLines(inString) - str = ''.join(str.split()) + s = cls.restoreNewLines(inString) + s = ''.join(s.split()) - return len(str) is 0 + return len(s) is 0 diff --git a/manuskript/ui/importers/generalSettings_ui.py b/manuskript/ui/importers/generalSettings_ui.py index 078ca2b..0faed4c 100644 --- a/manuskript/ui/importers/generalSettings_ui.py +++ b/manuskript/ui/importers/generalSettings_ui.py @@ -49,6 +49,9 @@ class Ui_generalSettings(object): self.treeGeneralParent.setHeaderHidden(True) self.treeGeneralParent.setObjectName("treeGeneralParent") self.formLayout_4.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.treeGeneralParent) + self.chkGeneralTrimTitles = QtWidgets.QCheckBox(self.general) + self.chkGeneralTrimTitles.setObjectName("chkGeneralTrimTitles") + self.formLayout_4.setWidget(2, QtWidgets.QFormLayout.SpanningRole, self.chkGeneralTrimTitles) self.verticalLayout_5.addLayout(self.formLayout_4) self.toolBox.addItem(self.general, "") self.verticalLayout_2.addWidget(self.toolBox) @@ -63,5 +66,6 @@ class Ui_generalSettings(object): generalSettings.setWindowTitle(_translate("generalSettings", "Form")) self.chkGeneralParent.setText(_translate("generalSettings", "Import under:")) self.chkGeneralSplitScenes.setText(_translate("generalSettings", "Split scenes at:")) + self.chkGeneralTrimTitles.setText(_translate("generalSettings", "Trim long titles (> 32 chars)")) self.toolBox.setItemText(self.toolBox.indexOf(self.general), _translate("generalSettings", "General")) diff --git a/manuskript/ui/importers/generalSettings_ui.ui b/manuskript/ui/importers/generalSettings_ui.ui index 53446d2..45fe6ef 100644 --- a/manuskript/ui/importers/generalSettings_ui.ui +++ b/manuskript/ui/importers/generalSettings_ui.ui @@ -90,6 +90,13 @@ QToolBox::tab:selected, QToolBox::tab:hover{ + + + + Trim long titles (> 32 chars) + + + diff --git a/manuskript/ui/importers/importer.py b/manuskript/ui/importers/importer.py index 72152f3..ea527c4 100644 --- a/manuskript/ui/importers/importer.py +++ b/manuskript/ui/importers/importer.py @@ -157,5 +157,30 @@ class importerDialog(QWidget, Ui_importer): ############################################################################ def preview(self): - # TODO + + # We find the current selected format + F = self.currentFormat() + + # Temporary outlineModel previewModel = outlineModel(self) + + # Calling the importer in a temporary model + items = F.startImport(self.fileName, + previewModel.rootItem, + self.settingsWidget) + + # Do transformations + # TODO + + if items: + self.tree.setModel(previewModel) + for i in range(1, previewModel.columnCount()): + self.tree.hideColumn(i) + + def startImport(self): + pass + + # Note: dont forget to emit: mdl.layoutChanged.emit() + # Maybe: mw.treeRedacOutline.viewport().update() + + diff --git a/manuskript/ui/importers/importer_ui.py b/manuskript/ui/importers/importer_ui.py index 44afddc..67f49db 100644 --- a/manuskript/ui/importers/importer_ui.py +++ b/manuskript/ui/importers/importer_ui.py @@ -80,7 +80,9 @@ class Ui_importer(object): self.splitter_2 = QtWidgets.QSplitter(self.grpPreview) self.splitter_2.setOrientation(QtCore.Qt.Horizontal) self.splitter_2.setObjectName("splitter_2") - self.tree = treeView(self.splitter_2) + self.tree = QtWidgets.QTreeView(self.splitter_2) + self.tree.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) + self.tree.setHeaderHidden(True) self.tree.setObjectName("tree") self.editor = mainEditor(self.splitter_2) self.editor.setObjectName("editor") @@ -102,4 +104,3 @@ class Ui_importer(object): self.grpPreview.setTitle(_translate("importer", "Preview")) from manuskript.ui.editors.mainEditor import mainEditor -from manuskript.ui.views.treeView import treeView diff --git a/manuskript/ui/importers/importer_ui.ui b/manuskript/ui/importers/importer_ui.ui index afb053e..0281cd0 100644 --- a/manuskript/ui/importers/importer_ui.ui +++ b/manuskript/ui/importers/importer_ui.ui @@ -178,7 +178,14 @@ Qt::Horizontal - + + + QAbstractItemView::NoEditTriggers + + + true + + @@ -189,11 +196,6 @@ - - treeView - QTreeView -
manuskript.ui.views.treeView.h
-
mainEditor QWidget From 543d5a232ac28f259bf9fe84db24dfa8d72febbd Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Tue, 7 Nov 2017 14:25:47 +0100 Subject: [PATCH 20/65] Checkpoint: preview seems to work --- manuskript/ui/editors/editorWidget.py | 28 ++++++----- manuskript/ui/importers/generalSettings.py | 21 +++++++-- manuskript/ui/importers/generalSettings_ui.py | 26 +++++------ manuskript/ui/importers/generalSettings_ui.ui | 46 ++++++++++++------- manuskript/ui/importers/importer.py | 41 +++++++++++++++-- manuskript/ui/importers/importer_ui.py | 17 +++---- manuskript/ui/importers/importer_ui.ui | 15 +++--- 7 files changed, 132 insertions(+), 62 deletions(-) diff --git a/manuskript/ui/editors/editorWidget.py b/manuskript/ui/editors/editorWidget.py index 75abb56..60ee400 100644 --- a/manuskript/ui/editors/editorWidget.py +++ b/manuskript/ui/editors/editorWidget.py @@ -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(): diff --git a/manuskript/ui/importers/generalSettings.py b/manuskript/ui/importers/generalSettings.py index 7dd96fc..17d7bcd 100644 --- a/manuskript/ui/importers/generalSettings.py +++ b/manuskript/ui/importers/generalSettings.py @@ -3,7 +3,7 @@ import json import os -from PyQt5.QtCore import Qt, QSize, QSortFilterProxyModel +from PyQt5.QtCore import Qt, QSize, QSortFilterProxyModel, QModelIndex from PyQt5.QtGui import QIcon, QFontMetrics, QFont from PyQt5.QtWidgets import QWidget, QTableWidgetItem, QListWidgetItem, QTreeView @@ -30,8 +30,6 @@ class generalSettings(QWidget, Ui_generalSettings): self.treeGeneralParent.setCurrentIndex(self.getParentIndex()) self.chkGeneralParent.toggled.connect(self.treeGeneralParent.setVisible) self.treeGeneralParent.hide() - self.treeGeneralParent.selectionModel().currentChanged.connect( - lambda: print(self.treeGeneralParent.currentIndex().row())) def getParentIndex(self): """ @@ -44,3 +42,20 @@ class generalSettings(QWidget, Ui_generalSettings): 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 trimLongTitles(self): + return self.chkGeneralTrimTitles.isChecked() + diff --git a/manuskript/ui/importers/generalSettings_ui.py b/manuskript/ui/importers/generalSettings_ui.py index 0faed4c..a07c172 100644 --- a/manuskript/ui/importers/generalSettings_ui.py +++ b/manuskript/ui/importers/generalSettings_ui.py @@ -11,7 +11,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets class Ui_generalSettings(object): def setupUi(self, generalSettings): generalSettings.setObjectName("generalSettings") - generalSettings.resize(289, 396) + generalSettings.resize(267, 401) self.verticalLayout_2 = QtWidgets.QVBoxLayout(generalSettings) self.verticalLayout_2.setContentsMargins(0, 0, 0, 0) self.verticalLayout_2.setSpacing(10) @@ -28,30 +28,30 @@ class Ui_generalSettings(object): "}") self.toolBox.setObjectName("toolBox") self.general = QtWidgets.QWidget() - self.general.setGeometry(QtCore.QRect(0, 0, 289, 373)) + self.general.setGeometry(QtCore.QRect(0, 0, 267, 378)) self.general.setObjectName("general") self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.general) - self.verticalLayout_5.setContentsMargins(0, 0, 0, 0) + 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.chkGeneralParent = QtWidgets.QCheckBox(self.general) - self.chkGeneralParent.setObjectName("chkGeneralParent") - self.formLayout_4.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.chkGeneralParent) self.chkGeneralSplitScenes = QtWidgets.QCheckBox(self.general) self.chkGeneralSplitScenes.setObjectName("chkGeneralSplitScenes") - self.formLayout_4.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.chkGeneralSplitScenes) + self.formLayout_4.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.chkGeneralSplitScenes) self.txtGeneralSplitScenes = QtWidgets.QLineEdit(self.general) self.txtGeneralSplitScenes.setObjectName("txtGeneralSplitScenes") - self.formLayout_4.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.txtGeneralSplitScenes) + self.formLayout_4.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.txtGeneralSplitScenes) + self.chkGeneralTrimTitles = QtWidgets.QCheckBox(self.general) + self.chkGeneralTrimTitles.setObjectName("chkGeneralTrimTitles") + self.formLayout_4.setWidget(3, QtWidgets.QFormLayout.SpanningRole, self.chkGeneralTrimTitles) self.treeGeneralParent = QtWidgets.QTreeView(self.general) self.treeGeneralParent.setHeaderHidden(True) self.treeGeneralParent.setObjectName("treeGeneralParent") - self.formLayout_4.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.treeGeneralParent) - self.chkGeneralTrimTitles = QtWidgets.QCheckBox(self.general) - self.chkGeneralTrimTitles.setObjectName("chkGeneralTrimTitles") - self.formLayout_4.setWidget(2, QtWidgets.QFormLayout.SpanningRole, self.chkGeneralTrimTitles) + 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.verticalLayout_5.addLayout(self.formLayout_4) self.toolBox.addItem(self.general, "") self.verticalLayout_2.addWidget(self.toolBox) @@ -64,8 +64,8 @@ class Ui_generalSettings(object): def retranslateUi(self, generalSettings): _translate = QtCore.QCoreApplication.translate generalSettings.setWindowTitle(_translate("generalSettings", "Form")) - self.chkGeneralParent.setText(_translate("generalSettings", "Import under:")) self.chkGeneralSplitScenes.setText(_translate("generalSettings", "Split scenes at:")) self.chkGeneralTrimTitles.setText(_translate("generalSettings", "Trim long titles (> 32 chars)")) + self.chkGeneralParent.setText(_translate("generalSettings", "Import under:")) self.toolBox.setItemText(self.toolBox.indexOf(self.general), _translate("generalSettings", "General")) diff --git a/manuskript/ui/importers/generalSettings_ui.ui b/manuskript/ui/importers/generalSettings_ui.ui index 45fe6ef..4bc2c4a 100644 --- a/manuskript/ui/importers/generalSettings_ui.ui +++ b/manuskript/ui/importers/generalSettings_ui.ui @@ -6,8 +6,8 @@ 0 0 - 289 - 396 + 267 + 401 @@ -53,47 +53,59 @@ QToolBox::tab:selected, QToolBox::tab:hover{ 0 0 - 289 - 373 + 267 + 378 General + + 6 + + + 6 + + + 6 + + + 6 + QFormLayout::WrapLongRows - - - - Import under: - - - - + Split scenes at: - + - + + + + Trim long titles (> 32 chars) + + + + true - - + + - Trim long titles (> 32 chars) + Import under: diff --git a/manuskript/ui/importers/importer.py b/manuskript/ui/importers/importer.py index ea527c4..721d045 100644 --- a/manuskript/ui/importers/importer.py +++ b/manuskript/ui/importers/importer.py @@ -3,7 +3,7 @@ import json import os -from PyQt5.QtCore import Qt +from PyQt5.QtCore import Qt, QTimer from PyQt5.QtGui import QBrush, QColor, QIcon from PyQt5.QtWidgets import QWidget, QFileDialog @@ -13,6 +13,7 @@ from manuskript.ui.importers.generalSettings import generalSettings from manuskript.ui import style from manuskript import importer from manuskript.models.outlineModel import outlineModel +from manuskript.enums import Outline class importerDialog(QWidget, Ui_importer): @@ -35,6 +36,8 @@ class importerDialog(QWidget, Ui_importer): self.mw = mw self.fileName = "" self.setStyleSheet(style.mainWindowSS()) + self.tree.setStyleSheet("QTreeView{background:transparent;}") + self.editor.setStyleSheet("QWidget{background:transparent;}") # Register importFormats: self.importers = importer.importers @@ -49,7 +52,11 @@ class importerDialog(QWidget, Ui_importer): self.cmbImporters.currentTextChanged.connect(self.updateSettings) #self.setFileName("") - self.setFileName("/home/olivier/Dropbox/Documents/Travail/Geekeries/Python/PyCharmProjects/manuskript/test-projects/IMPORTS/End Plan 2.opml") + QTimer.singleShot(50, lambda: + self.cmbImporters.setCurrentText("OPML")) + QTimer.singleShot(50, lambda: + self.setFileName("/home/olivier/Dropbox/Documents/Travail/Geekeries/Python/PyCharmProjects/manuskript/test-projects/IMPORTS/End Plan 2.opml") + ) ############################################################################ # Combobox / Formats @@ -134,8 +141,13 @@ class importerDialog(QWidget, Ui_importer): F = self.currentFormat() self.settingsWidget = generalSettings() self.setGroupWidget(self.grpSettings, self.settingsWidget) + self.grpSettings.setMinimumWidth(200) #TODO: custom format widget + #toolBox = self.settingsWidget.toolBox + #w = QWidget() + #toolBox.insertItem(toolBox.count(), w, "Pandoc") + #See pandoc's abstractPlainText def setGroupWidget(self, group, widget): """ @@ -163,19 +175,40 @@ class importerDialog(QWidget, Ui_importer): # Temporary outlineModel previewModel = outlineModel(self) + previewModel.loadFromXML( + self.mw.mdlOutline.saveToXML(), + fromString=True) + + # Parent item + ID = self.settingsWidget.importUnderID() + parentItem = previewModel.getItemByID(ID) # Calling the importer in a temporary model items = F.startImport(self.fileName, - previewModel.rootItem, + parentItem, self.settingsWidget) # Do transformations - # TODO + # ------------------ + + # Trim long titles + if self.settingsWidget.trimLongTitles(): + def trim(item): + if len(item.title()) > 32: + item.setData(Outline.title.value, item.title()[:32]) + for c in item.children(): + trim(c) + for i in items: + trim(i) if items: 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 startImport(self): pass diff --git a/manuskript/ui/importers/importer_ui.py b/manuskript/ui/importers/importer_ui.py index 67f49db..d23c8e4 100644 --- a/manuskript/ui/importers/importer_ui.py +++ b/manuskript/ui/importers/importer_ui.py @@ -11,7 +11,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets class Ui_importer(object): def setupUi(self, importer): importer.setObjectName("importer") - importer.resize(694, 489) + importer.resize(867, 560) self.verticalLayout = QtWidgets.QVBoxLayout(importer) self.verticalLayout.setObjectName("verticalLayout") self.horizontalLayout = QtWidgets.QHBoxLayout() @@ -64,6 +64,7 @@ class Ui_importer(object): 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") @@ -77,16 +78,16 @@ class Ui_importer(object): self.verticalLayout_2.setContentsMargins(0, 0, 0, 0) self.verticalLayout_2.setSpacing(0) self.verticalLayout_2.setObjectName("verticalLayout_2") - self.splitter_2 = QtWidgets.QSplitter(self.grpPreview) - self.splitter_2.setOrientation(QtCore.Qt.Horizontal) - self.splitter_2.setObjectName("splitter_2") - self.tree = QtWidgets.QTreeView(self.splitter_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 = mainEditor(self.splitter_2) + self.editor = editorWidget(self.previewSplitter) self.editor.setObjectName("editor") - self.verticalLayout_2.addWidget(self.splitter_2) + self.verticalLayout_2.addWidget(self.previewSplitter) self.verticalLayout.addWidget(self.splitter) self.retranslateUi(importer) @@ -103,4 +104,4 @@ class Ui_importer(object): self.grpSettings.setTitle(_translate("importer", "Settings")) self.grpPreview.setTitle(_translate("importer", "Preview")) -from manuskript.ui.editors.mainEditor import mainEditor +from manuskript.ui.editors.editorWidget import editorWidget diff --git a/manuskript/ui/importers/importer_ui.ui b/manuskript/ui/importers/importer_ui.ui index 0281cd0..a7f2f90 100644 --- a/manuskript/ui/importers/importer_ui.ui +++ b/manuskript/ui/importers/importer_ui.ui @@ -6,8 +6,8 @@ 0 0 - 694 - 489 + 867 + 560 @@ -131,6 +131,9 @@ Qt::Horizontal + + false + Settings @@ -174,7 +177,7 @@ 0 - + Qt::Horizontal @@ -186,7 +189,7 @@ true - + @@ -197,9 +200,9 @@ - mainEditor + editorWidget QWidget -
manuskript.ui.editors.mainEditor.h
+
manuskript.ui.editors.editorWidget.h
1
From fb50d423484b1e8c6d5687c652398f0119a4ea2d Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Tue, 7 Nov 2017 14:40:46 +0100 Subject: [PATCH 21/65] Checkpoint: import now works. --- manuskript/importer/opmlImporter.py | 60 +------------------------ manuskript/ui/importers/importer.py | 69 ++++++++++++++++++++--------- 2 files changed, 48 insertions(+), 81 deletions(-) diff --git a/manuskript/importer/opmlImporter.py b/manuskript/importer/opmlImporter.py index aff3728..6bb372b 100644 --- a/manuskript/importer/opmlImporter.py +++ b/manuskript/importer/opmlImporter.py @@ -45,13 +45,7 @@ class opmlImporter(abstractImporter): items.append(cls.parseItems(element, parentItem)) ret = True - if ret: - #QMessageBox.information( - #settingsWidget, - #qApp.translate("Import", "OPML Import"), - #qApp.translate("Import", "Import Complete.")) - pass - else: + if not ret: QMessageBox.critical( settingsWidget, qApp.translate("Import", "OPML Import"), @@ -61,58 +55,6 @@ class opmlImporter(abstractImporter): return items - def importOpml(opmlFilePath, idx): - """ - Import/export outline cards in OPML format. - #FIXME: delete me when done with startImport - """ - ret = False - mw = mainWindow() - - try: - with open(opmlFilePath, 'r') as opmlFile: - opmlContent = saveNewlines(opmlFile.read()) - except: - QMessageBox.critical(mw, mw.tr("OPML Import"), - mw.tr("File open failed.")) - return False - - mdl = mw.mdlOutline - - if idx.internalPointer() is not None: - parentItem = idx.internalPointer() - else: - parentItem = mdl.rootItem - - try: - parsed = ET.fromstring(bytes(opmlContent, 'utf-8')) - - opmlNode = parsed - bodyNode = opmlNode.find("body") - - if bodyNode is not None: - outlineEls = bodyNode.findall("outline") - - if outlineEls is not None: - for element in outlineEls: - parseItems(element, parentItem) - - mdl.layoutChanged.emit() - mw.treeRedacOutline.viewport().update() - ret = True - except: - pass - - # TODO: Translation - if ret: - QMessageBox.information(mw, mw.tr("OPML Import"), - mw.tr("Import Complete.")) - else: - QMessageBox.critical(mw, mw.tr("OPML Import"), - mw.tr("This does not appear to be a valid OPML file.")) - - return ret - @classmethod def parseItems(cls, underElement, parentItem=None): title = underElement.get('text') diff --git a/manuskript/ui/importers/importer.py b/manuskript/ui/importers/importer.py index 721d045..5984fa4 100644 --- a/manuskript/ui/importers/importer.py +++ b/manuskript/ui/importers/importer.py @@ -5,7 +5,7 @@ import os from PyQt5.QtCore import Qt, QTimer from PyQt5.QtGui import QBrush, QColor, QIcon -from PyQt5.QtWidgets import QWidget, QFileDialog +from PyQt5.QtWidgets import QWidget, QFileDialog, QMessageBox from manuskript.functions import lightBlue, writablePath, appPath from manuskript.ui.importers.importer_ui import Ui_importer @@ -49,6 +49,7 @@ class importerDialog(QWidget, Ui_importer): 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("") @@ -170,26 +171,63 @@ class importerDialog(QWidget, Ui_importer): def preview(self): - # We find the current selected format - F = self.currentFormat() - - # Temporary outlineModel + # 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) + + QMessageBox.information(self, self.tr("Import status"), + self.tr("Import Complete.")) + + 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. + """ + + # We find the current selected format + F = self.currentFormat() + # Parent item ID = self.settingsWidget.importUnderID() - parentItem = previewModel.getItemByID(ID) + parentItem = outlineModel.getItemByID(ID) - # Calling the importer in a temporary model + # Calling the importer items = F.startImport(self.fileName, parentItem, self.settingsWidget) # Do transformations - # ------------------ + items = self.doTransformations(items) + + return True + + def doTransformations(self, items): + """ + Do general transformations. + """ # Trim long titles if self.settingsWidget.trimLongTitles(): @@ -201,19 +239,6 @@ class importerDialog(QWidget, Ui_importer): for i in items: trim(i) - if items: - 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 startImport(self): - pass - - # Note: dont forget to emit: mdl.layoutChanged.emit() - # Maybe: mw.treeRedacOutline.viewport().update() + return items From a29eddabeaf34b9def74eaee807ddad2e79eb108 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Tue, 7 Nov 2017 15:33:18 +0100 Subject: [PATCH 22/65] Adds: OPML import with preview (using @camstevenson's importer) #200 --- manuskript/importer/__init__.py | 4 ++-- manuskript/importer/markdownImporter.py | 2 +- manuskript/importer/opmlImporter.py | 12 ++++++----- manuskript/ui/importers/generalSettings_ui.py | 2 ++ manuskript/ui/importers/generalSettings_ui.ui | 9 ++++++++- manuskript/ui/importers/importer.py | 20 ++++++++++--------- 6 files changed, 31 insertions(+), 18 deletions(-) diff --git a/manuskript/importer/__init__.py b/manuskript/importer/__init__.py index d2f0bc1..8ca6230 100644 --- a/manuskript/importer/__init__.py +++ b/manuskript/importer/__init__.py @@ -6,7 +6,7 @@ from manuskript.importer.markdownImporter import markdownImporter from manuskript.importer.opmlImporter import opmlImporter importers = [ - markdownImporter, + #markdownImporter, opmlImporter, - folderImporter, + #folderImporter, ] diff --git a/manuskript/importer/markdownImporter.py b/manuskript/importer/markdownImporter.py index 0a3a5cd..2567f30 100644 --- a/manuskript/importer/markdownImporter.py +++ b/manuskript/importer/markdownImporter.py @@ -8,5 +8,5 @@ class markdownImporter(abstractImporter): name = "Markdown" description = "" - fileFormat = "Markdown files (*.md; *.txt; *)" + fileFormat = "Markdown files (*.md *.txt *)" icon = "text-x-markdown" diff --git a/manuskript/importer/opmlImporter.py b/manuskript/importer/opmlImporter.py index 6bb372b..e6474f5 100644 --- a/manuskript/importer/opmlImporter.py +++ b/manuskript/importer/opmlImporter.py @@ -12,7 +12,7 @@ class opmlImporter(abstractImporter): name = "OPML" description = "" - fileFormat = "OPML Files (*.opml)" + fileFormat = "OPML Files (*.opml *.xml)" icon = "text-x-opml+xml" @classmethod @@ -23,15 +23,16 @@ class opmlImporter(abstractImporter): ret = False try: - with open(filePath, 'r') as opmlFile: - opmlContent = cls.saveNewlines(opmlFile.read()) + 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 - parsed = ET.fromstring(bytes(opmlContent, 'utf-8')) + parsed = ET.fromstring(opmlContent) opmlNode = parsed bodyNode = opmlNode.find("body") @@ -65,7 +66,8 @@ class opmlImporter(abstractImporter): body = "" note = underElement.get('_note') if note is not None and not cls.isWhitespaceOnly(note): - body = cls.restoreNewLines(note) + #body = cls.restoreNewLines(note) + body = note children = underElement.findall('outline') if children is not None and len(children) > 0: diff --git a/manuskript/ui/importers/generalSettings_ui.py b/manuskript/ui/importers/generalSettings_ui.py index a07c172..1843ee0 100644 --- a/manuskript/ui/importers/generalSettings_ui.py +++ b/manuskript/ui/importers/generalSettings_ui.py @@ -37,9 +37,11 @@ class Ui_generalSettings(object): self.formLayout_4.setRowWrapPolicy(QtWidgets.QFormLayout.WrapLongRows) self.formLayout_4.setObjectName("formLayout_4") self.chkGeneralSplitScenes = QtWidgets.QCheckBox(self.general) + self.chkGeneralSplitScenes.setEnabled(False) self.chkGeneralSplitScenes.setObjectName("chkGeneralSplitScenes") self.formLayout_4.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.chkGeneralSplitScenes) self.txtGeneralSplitScenes = QtWidgets.QLineEdit(self.general) + self.txtGeneralSplitScenes.setEnabled(False) self.txtGeneralSplitScenes.setObjectName("txtGeneralSplitScenes") self.formLayout_4.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.txtGeneralSplitScenes) self.chkGeneralTrimTitles = QtWidgets.QCheckBox(self.general) diff --git a/manuskript/ui/importers/generalSettings_ui.ui b/manuskript/ui/importers/generalSettings_ui.ui index 4bc2c4a..21c4f95 100644 --- a/manuskript/ui/importers/generalSettings_ui.ui +++ b/manuskript/ui/importers/generalSettings_ui.ui @@ -80,13 +80,20 @@ QToolBox::tab:selected, QToolBox::tab:hover{ + + false + Split scenes at: - + + + false + + diff --git a/manuskript/ui/importers/importer.py b/manuskript/ui/importers/importer.py index 5984fa4..915149a 100644 --- a/manuskript/ui/importers/importer.py +++ b/manuskript/ui/importers/importer.py @@ -25,6 +25,7 @@ class importerDialog(QWidget, Ui_importer): ".rst": "text-plain", ".tex": "text-x-tex", ".opml": "text-x-opml+xml", + ".xml": "text-x-opml+xml", ".html": "text-html", } @@ -38,6 +39,7 @@ class importerDialog(QWidget, Ui_importer): 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 @@ -52,12 +54,8 @@ class importerDialog(QWidget, Ui_importer): self.btnImport.clicked.connect(self.doImport) self.cmbImporters.currentTextChanged.connect(self.updateSettings) - #self.setFileName("") - QTimer.singleShot(50, lambda: - self.cmbImporters.setCurrentText("OPML")) - QTimer.singleShot(50, lambda: - self.setFileName("/home/olivier/Dropbox/Documents/Travail/Geekeries/Python/PyCharmProjects/manuskript/test-projects/IMPORTS/End Plan 2.opml") - ) + self.setFileName("") + self.updateSettings() ############################################################################ # Combobox / Formats @@ -108,14 +106,18 @@ class importerDialog(QWidget, Ui_importer): 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") - #self.lblIcon.setVisible(True) - h = self.lblFileName.height() - self.lblIcon.setPixmap(icon.pixmap(h, h)) + 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 From 9eb1402613bf70a4d3e8132ad424c0fdcd80efe6 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Tue, 7 Nov 2017 16:22:59 +0100 Subject: [PATCH 23/65] Fixes two small bugs #200. --- manuskript/ui/importers/importer.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/manuskript/ui/importers/importer.py b/manuskript/ui/importers/importer.py index 915149a..f98c66f 100644 --- a/manuskript/ui/importers/importer.py +++ b/manuskript/ui/importers/importer.py @@ -196,8 +196,14 @@ class importerDialog(QWidget, Ui_importer): """ self.startImport(self.mw.mdlOutline) - QMessageBox.information(self, self.tr("Import status"), - self.tr("Import Complete.")) + # 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() From ccf33b3ccfeaef84f02ca173c759b31624230df5 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Tue, 7 Nov 2017 20:30:39 +0100 Subject: [PATCH 24/65] Adds: import from many formats using pandoc. #200 --- manuskript/exporter/basic.py | 2 +- manuskript/importer/__init__.py | 17 ++++- manuskript/importer/abstractImporter.py | 7 +- manuskript/importer/opmlImporter.py | 32 ++++++--- manuskript/importer/pandocImporters.py | 91 +++++++++++++++++++++++++ manuskript/ui/importers/importer.py | 27 +++++++- 6 files changed, 160 insertions(+), 16 deletions(-) create mode 100644 manuskript/importer/pandocImporters.py diff --git a/manuskript/exporter/basic.py b/manuskript/exporter/basic.py index 5acbe9e..bc1c1fc 100644 --- a/manuskript/exporter/basic.py +++ b/manuskript/exporter/basic.py @@ -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)) diff --git a/manuskript/importer/__init__.py b/manuskript/importer/__init__.py index 8ca6230..3edfeba 100644 --- a/manuskript/importer/__init__.py +++ b/manuskript/importer/__init__.py @@ -4,9 +4,22 @@ 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 importers = [ - #markdownImporter, + # Internal opmlImporter, - #folderImporter, + folderImporter, + markdownImporter, + + # Pandoc + markdownPandocImporter, + odtPandocImporter, + ePubPandocImporter, + docXPandocImporter, + HTMLPandocImporter, + rstPandocImporter, + LaTeXPandocImporter, ] diff --git a/manuskript/importer/abstractImporter.py b/manuskript/importer/abstractImporter.py index 7c75a68..6f146e7 100644 --- a/manuskript/importer/abstractImporter.py +++ b/manuskript/importer/abstractImporter.py @@ -22,7 +22,8 @@ class abstractImporter: # For folder, use "<>" icon = "" - def startImport(self, filePath, settingsWidget): + @classmethod + def startImport(cls, 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, @@ -37,4 +38,8 @@ class abstractImporter: """ return None + @classmethod + def isValid(cls): + return False + diff --git a/manuskript/importer/opmlImporter.py b/manuskript/importer/opmlImporter.py index e6474f5..2fce7bc 100644 --- a/manuskript/importer/opmlImporter.py +++ b/manuskript/importer/opmlImporter.py @@ -16,22 +16,36 @@ class opmlImporter(abstractImporter): icon = "text-x-opml+xml" @classmethod - def startImport(cls, filePath, parentItem, settingsWidget): + def isValid(cls): + return True + + @classmethod + def startImport(cls, filePath, parentItem, settingsWidget, fromString=None): """ Import/export outline cards in OPML format. """ ret = False - 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.")) + 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 diff --git a/manuskript/importer/pandocImporters.py b/manuskript/importer/pandocImporters.py new file mode 100644 index 0000000..c23eefc --- /dev/null +++ b/manuskript/importer/pandocImporters.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- + +from manuskript.importer.abstractImporter import abstractImporter +from manuskript.exporter.pandoc import pandocExporter +from manuskript.importer.opmlImporter import opmlImporter + +class pandocImporter(abstractImporter): + + formatFrom = "" + + @classmethod + def isValid(cls): + return pandocExporter().isValid() + + @classmethod + def startImport(cls, filePath, parentItem, settingsWidget): + + # pandoc --from=markdown filename --to=opml --standalone + args = [ + "--from={}".format(cls.formatFrom), + filePath, + "--to=opml", + "--standalone" + ] + + r = pandocExporter().run(args) + + return opmlImporter.startImport("", parentItem, + settingsWidget, fromString=r) + + +class markdownPandocImporter(pandocImporter): + + name = "Markdown (pandoc)" + description = "Markdown, using pandoc" + fileFormat = "Markdown files (*.md *.txt *)" + icon = "text-x-markdown" + formatFrom = "markdown" + +class ePubPandocImporter(pandocImporter): + + name = "ePub (pandoc)" + description = "" + fileFormat = "ePub files (*.epub)" + icon = "application-epub+zip" + formatFrom = "epub" + +class docXPandocImporter(pandocImporter): + + name = "DocX (pandoc)" + description = "" + fileFormat = "DocX files (*.docx)" + icon = "application-vnd.openxmlformats-officedocument.wordprocessingml.document" + formatFrom = "docx" + +class odtPandocImporter(pandocImporter): + + name = "ODT (pandoc)" + description = "" + fileFormat = "Open Document files (*.odt)" + icon = "application-vnd.oasis.opendocument.text" + formatFrom = "odt" + +class rstPandocImporter(pandocImporter): + + name = "reStructuredText (pandoc)" + description = "" + fileFormat = "reStructuredText files (*.rst)" + icon = "text-plain" + formatFrom = "rst" + +class HTMLPandocImporter(pandocImporter): + + name = "HTML (pandoc)" + description = "" + fileFormat = "HTML files (*.htm *.html)" + icon = "text-html" + formatFrom = "html" + +class LaTeXPandocImporter(pandocImporter): + + name = "LaTeX (pandoc)" + description = "" + fileFormat = "LaTeX files (*.tex)" + icon = "text-x-tex" + formatFrom = "latex" + + + + diff --git a/manuskript/ui/importers/importer.py b/manuskript/ui/importers/importer.py index f98c66f..c8ecf78 100644 --- a/manuskript/ui/importers/importer.py +++ b/manuskript/ui/importers/importer.py @@ -3,9 +3,9 @@ import json import os -from PyQt5.QtCore import Qt, QTimer -from PyQt5.QtGui import QBrush, QColor, QIcon -from PyQt5.QtWidgets import QWidget, QFileDialog, QMessageBox +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 @@ -14,6 +14,7 @@ from manuskript.ui import style from manuskript import importer from manuskript.models.outlineModel import outlineModel from manuskript.enums import Outline +from manuskript.exporter.pandoc import pandocExporter class importerDialog(QWidget, Ui_importer): @@ -68,6 +69,15 @@ class importerDialog(QWidget, Ui_importer): for f in self.importers: addFormat(f.name, f.icon) + 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") def currentFormat(self): formatName = self.cmbImporters.currentText() @@ -141,7 +151,15 @@ class importerDialog(QWidget, Ui_importer): 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.settingsWidget = generalSettings() self.setGroupWidget(self.grpSettings, self.settingsWidget) self.grpSettings.setMinimumWidth(200) @@ -152,6 +170,9 @@ class importerDialog(QWidget, Ui_importer): #toolBox.insertItem(toolBox.count(), w, "Pandoc") #See pandoc's abstractPlainText + # Clear file name + self.setFileName("") + def setGroupWidget(self, group, widget): """ Sets the given widget as main widget for QGroupBox group. From c6391e976cdcfdd1864e407e0559ebad077f1b18 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Wed, 8 Nov 2017 21:35:26 +0100 Subject: [PATCH 25/65] Adds: ability to import from folder structure. #200 --- manuskript/importer/abstractImporter.py | 171 +++++++++++++++++++-- manuskript/importer/folderImporter.py | 123 +++++++++++++++ manuskript/models/outlineModel.py | 5 +- manuskript/ui/importers/generalSettings.py | 3 + manuskript/ui/importers/importer.py | 23 ++- 5 files changed, 307 insertions(+), 18 deletions(-) diff --git a/manuskript/importer/abstractImporter.py b/manuskript/importer/abstractImporter.py index 6f146e7..0e315f7 100644 --- a/manuskript/importer/abstractImporter.py +++ b/manuskript/importer/abstractImporter.py @@ -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 "<>" 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() + + + diff --git a/manuskript/importer/folderImporter.py b/manuskript/importer/folderImporter.py index a961888..a06195b 100644 --- a/manuskript/importer/folderImporter.py +++ b/manuskript/importer/folderImporter.py @@ -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 = "<>" 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", """Info: Imports a whole + directory structure. Folders are added as folders, and + plaintext documents within (you chose which ones by extension) + are added as scene.
 """)) + + 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 + + + + diff --git a/manuskript/models/outlineModel.py b/manuskript/models/outlineModel.py index 9b4f766..1391ba3 100644 --- a/manuskript/models/outlineModel.py +++ b/manuskript/models/outlineModel.py @@ -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) diff --git a/manuskript/ui/importers/generalSettings.py b/manuskript/ui/importers/generalSettings.py index 17d7bcd..e7dc43e 100644 --- a/manuskript/ui/importers/generalSettings.py +++ b/manuskript/ui/importers/generalSettings.py @@ -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 diff --git a/manuskript/ui/importers/importer.py b/manuskript/ui/importers/importer.py index c8ecf78..6314ac6 100644 --- a/manuskript/ui/importers/importer.py +++ b/manuskript/ui/importers/importer.py @@ -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: From bc5c53fe6d6a517d9bccc6de1afd8034f99ade80 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Wed, 8 Nov 2017 22:54:31 +0100 Subject: [PATCH 26/65] Adds: internal markdown importer. #200 --- manuskript/importer/folderImporter.py | 4 - manuskript/importer/markdownImporter.py | 146 ++++++++++++++++++++++++ 2 files changed, 146 insertions(+), 4 deletions(-) diff --git a/manuskript/importer/folderImporter.py b/manuskript/importer/folderImporter.py index a06195b..a8e3f25 100644 --- a/manuskript/importer/folderImporter.py +++ b/manuskript/importer/folderImporter.py @@ -88,7 +88,6 @@ class folderImporter(abstractImporter): return items - def settingsWidget(self, widget): """ Takes a QWidget that can be modified and must be returned. @@ -122,9 +121,6 @@ class folderImporter(abstractImporter): qApp.translate("Import", "Import folder then files"), default=True), - - - for s in self.settings: self.settings[s].widget(group) diff --git a/manuskript/importer/markdownImporter.py b/manuskript/importer/markdownImporter.py index 2567f30..ca4e246 100644 --- a/manuskript/importer/markdownImporter.py +++ b/manuskript/importer/markdownImporter.py @@ -2,6 +2,11 @@ # --!-- coding: utf8 --!-- from manuskript.importer.abstractImporter import abstractImporter +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): @@ -10,3 +15,144 @@ class markdownImporter(abstractImporter): 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 + ... + ``` + """ + + # Read file + with open(filePath, "r") as f: + txt = f.read() + + 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 + + header = re.compile(r"(\#+)\s*(.+?)\s*\#*$") + + # Import in top level folder? + if self.getSetting("topLevelFolder").value(): + parent = addTitle(os.path.basename(filePath), parentItem, 0) + + # We store the level of each item in a temporary var + parent.__miLevel = 0 # markdown importer header level + + for l in txt.split("\n"): + m = header.match(l) + if m: + # Header ! + level = len(m.group(1)) + name = m.group(2) + + # 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() + i.removeChild(0) + + 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", """WARNING: Current + importer only knows ATX-header (# Header), and + not setext headers (underlined with ========).
 """)) + + self.addSetting("topLevelFolder", "checkbox", + qApp.translate("Import", "Import in a top-level folder."), + default=False), + + for s in self.settings: + self.settings[s].widget(group) + + return widget From 34b55b511c545fb30c418be106600dfd4adbfb6d Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Wed, 8 Nov 2017 23:20:13 +0100 Subject: [PATCH 27/65] Adds: support of setext-style headers in markdown import. #200 --- manuskript/importer/markdownImporter.py | 47 +++++++++++++++++++++---- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/manuskript/importer/markdownImporter.py b/manuskript/importer/markdownImporter.py index ca4e246..69df32c 100644 --- a/manuskript/importer/markdownImporter.py +++ b/manuskript/importer/markdownImporter.py @@ -85,7 +85,9 @@ class markdownImporter(abstractImporter): items.append(child) return child - header = re.compile(r"(\#+)\s*(.+?)\s*\#*$") + ATXHeader = re.compile(r"(\#+)\s*(.+?)\s*\#*$") + setextHeader1 = re.compile(r"(.+)\n===+$", re.MULTILINE) + setextHeader2 = re.compile(r"(.+)\n---+$", re.MULTILINE) # Import in top level folder? if self.getSetting("topLevelFolder").value(): @@ -94,13 +96,44 @@ class markdownImporter(abstractImporter): # We store the level of each item in a temporary var parent.__miLevel = 0 # markdown importer header level - for l in txt.split("\n"): - m = header.match(l) + 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 ! + header = True level = len(m.group(1)) name = m.group(2) + # Check setext header + m = setextHeader1.match(l2) + if not header and m: + header = True + level = 1 + name = m.group(1) + skipNextLine = True + + m = setextHeader2.match(l2) + if not header and m: + header = True + level = 2 + name = m.group(1) + skipNextLine = True + + if header: + # save content content = saveContent(content, parent) @@ -144,9 +177,9 @@ class markdownImporter(abstractImporter): #group = cls.addPage(widget, "Folder import") self.addSetting("info", "label", - qApp.translate("Import", """WARNING: Current - importer only knows ATX-header (# Header), and - not setext headers (underlined with ========).
 """)) + qApp.translate("Import", """Info: A very simple + parser that will go through a markdown document and + create items for each titles.
 """)) self.addSetting("topLevelFolder", "checkbox", qApp.translate("Import", "Import in a top-level folder."), From 9c99d186e54c550f7ab0c3d76f31fb4d0ae0ccec Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Wed, 8 Nov 2017 23:46:18 +0100 Subject: [PATCH 28/65] Adds: import with pandoc through either OPML or markdown. #200 --- manuskript/importer/markdownImporter.py | 10 +++-- manuskript/importer/pandocImporters.py | 51 ++++++++++++++++++++++--- 2 files changed, 51 insertions(+), 10 deletions(-) diff --git a/manuskript/importer/markdownImporter.py b/manuskript/importer/markdownImporter.py index 69df32c..089aec0 100644 --- a/manuskript/importer/markdownImporter.py +++ b/manuskript/importer/markdownImporter.py @@ -1,7 +1,6 @@ #!/usr/bin/env python # --!-- coding: utf8 --!-- -from manuskript.importer.abstractImporter import abstractImporter from manuskript.importer.abstractImporter import abstractImporter from manuskript.models.outlineModel import outlineItem from manuskript.enums import Outline @@ -62,9 +61,12 @@ class markdownImporter(abstractImporter): ``` """ - # Read file - with open(filePath, "r") as f: - txt = f.read() + if not fromString: + # Read file + with open(filePath, "r") as f: + txt = f.read() + else: + txt = fromString items = [] diff --git a/manuskript/importer/pandocImporters.py b/manuskript/importer/pandocImporters.py index c23eefc..c81c8c7 100644 --- a/manuskript/importer/pandocImporters.py +++ b/manuskript/importer/pandocImporters.py @@ -4,6 +4,9 @@ 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): @@ -13,21 +16,57 @@ class pandocImporter(abstractImporter): def isValid(cls): return pandocExporter().isValid() - @classmethod - def startImport(cls, filePath, parentItem, settingsWidget): + def startImport(self, filePath, parentItem, settingsWidget): + + formatTo = self.getSetting("formatTo").value().lower() # pandoc --from=markdown filename --to=opml --standalone args = [ - "--from={}".format(cls.formatFrom), + "--from={}".format(self.formatFrom), filePath, - "--to=opml", + "--to={}".format(formatTo), "--standalone" ] r = pandocExporter().run(args) - return opmlImporter.startImport("", parentItem, - settingsWidget, fromString=r) + 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", """Info: Manuskript can + import from markdown or OPML. 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. +
 """)) + + self.addSetting("formatTo", "combo", + qApp.translate("Import", "Import using:"), + vals="markdown|OPML") + + 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): From b3ba8e813d69c73c93f8f840379ef3cfe0ae6b44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20=C5=9Awi=C4=85tkowski?= Date: Thu, 9 Nov 2017 01:44:01 +0100 Subject: [PATCH 29/65] Get default locale in more reliable way --- manuskript/ui/views/textEditView.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/manuskript/ui/views/textEditView.py b/manuskript/ui/views/textEditView.py index d557e4d..ef25ae8 100644 --- a/manuskript/ui/views/textEditView.py +++ b/manuskript/ui/views/textEditView.py @@ -2,7 +2,7 @@ # --!-- coding: utf8 --!-- import re -from PyQt5.QtCore import QTimer, QModelIndex, Qt, QEvent, pyqtSignal, QRegExp +from PyQt5.QtCore import QTimer, QModelIndex, Qt, QEvent, pyqtSignal, QRegExp, QLocale from PyQt5.QtGui import QTextBlockFormat, QTextCharFormat, QFont, QColor, QIcon, QMouseEvent, QTextCursor from PyQt5.QtWidgets import QWidget, QTextEdit, qApp, QAction, QMenu @@ -76,7 +76,7 @@ class textEditView(QTextEdit): # Spellchecking if enchant and self.spellcheck: try: - self._dict = enchant.Dict(self.currentDict if self.currentDict else enchant.get_default_language()) + self._dict = enchant.Dict(self.currentDict if self.currentDict else self.getDefaultLocale()) except enchant.errors.DictNotFoundError: self.spellcheck = False @@ -87,6 +87,15 @@ class textEditView(QTextEdit): self.highlighter = basicHighlighter(self) self.highlighter.setDefaultBlockFormat(self._defaultBlockFormat) + def getDefaultLocale(self): + default_locale = enchant.get_default_language() + if default_locale is None: + default_locale = QLocale.system().name() + if default_locale is None: + default_locale = enchant.list_dicts()[0][0] + + return default_locale + def setModel(self, model): self._model = model try: From 271f467d0ea38be9bb120e8ab330dea8871f16b8 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Thu, 9 Nov 2017 08:46:31 +0100 Subject: [PATCH 30/65] Adds pandoc wrap option to manage non-semantic linebreaks in imports --- manuskript/importer/pandocImporters.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/manuskript/importer/pandocImporters.py b/manuskript/importer/pandocImporters.py index c81c8c7..89548dc 100644 --- a/manuskript/importer/pandocImporters.py +++ b/manuskript/importer/pandocImporters.py @@ -19,13 +19,15 @@ class pandocImporter(abstractImporter): 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), - "--standalone" + "--standalone", + "--wrap={}".format(wrap), ] r = pandocExporter().run(args) @@ -58,6 +60,19 @@ class pandocImporter(abstractImporter): qApp.translate("Import", "Import using:"), vals="markdown|OPML") + self.addSetting("wrap", "combo", + qApp.translate("Import", "Wrap lines:"), + vals="auto|none|preserve") + + self.addSetting("infoWrap", "label", + qApp.translate("Import", """(auto: wraps at + 72 characters.
+ none: no line wrap.
+ preserve: tries to preserves line wrap from + the original document. +
 """), + default="none") + for s in self.settings: self.settings[s].widget(group) From 3cef130bc6e0e9c5509fc00fe2c8ff36fbf24c6e Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Thu, 9 Nov 2017 10:40:54 +0100 Subject: [PATCH 31/65] Fixes a strupid bug recentrly introduced in outlineItem.findUniqueID. --- manuskript/mainWindow.py | 2 +- manuskript/models/outlineModel.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/manuskript/mainWindow.py b/manuskript/mainWindow.py index f72bd8b..4429d01 100644 --- a/manuskript/mainWindow.py +++ b/manuskript/mainWindow.py @@ -413,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) diff --git a/manuskript/models/outlineModel.py b/manuskript/models/outlineModel.py index 1391ba3..ca9eaec 100644 --- a/manuskript/models/outlineModel.py +++ b/manuskript/models/outlineModel.py @@ -914,8 +914,8 @@ class outlineItem(): def findUniqueID(self): IDs = [int(i) for i in self.IDs] - k = 0 - while k in self.IDs: + k = 1 + while k in IDs: k += 1 self.IDs.append(str(k)) return str(k) From 0807b14e5c218fd56f2e06ec30862eedf11ca425 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20=C5=9Awi=C4=85tkowski?= Date: Thu, 9 Nov 2017 10:45:05 +0100 Subject: [PATCH 32/65] Don't pass none to enchant.dict_exists --- manuskript/ui/views/textEditView.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manuskript/ui/views/textEditView.py b/manuskript/ui/views/textEditView.py index ef25ae8..bb98996 100644 --- a/manuskript/ui/views/textEditView.py +++ b/manuskript/ui/views/textEditView.py @@ -393,7 +393,7 @@ class textEditView(QTextEdit): if enchant and self.spellcheck and not self._dict: if self.currentDict: self._dict = enchant.Dict(self.currentDict) - elif enchant.dict_exists(enchant.get_default_language()): + elif enchant.get_default_language() and enchant.dict_exists(enchant.get_default_language()): self._dict = enchant.Dict(enchant.get_default_language()) else: self.spellcheck = False From 24607bca59f1174b797b51f67f5228ca79b6b921 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Thu, 9 Nov 2017 11:25:24 +0100 Subject: [PATCH 33/65] Adds setting to manage word wrap, and enhances UI. #200 --- manuskript/importer/__init__.py | 3 +- manuskript/importer/abstractImporter.py | 3 ++ manuskript/importer/folderImporter.py | 10 ---- manuskript/importer/markdownImporter.py | 8 ---- manuskript/importer/pandocImporters.py | 41 +++++++++------- manuskript/ui/importers/generalSettings.py | 7 +++ manuskript/ui/importers/generalSettings_ui.py | 10 ++-- manuskript/ui/importers/generalSettings_ui.ui | 13 +++-- manuskript/ui/importers/importer.py | 48 ++++++++++++++++--- 9 files changed, 94 insertions(+), 49 deletions(-) diff --git a/manuskript/importer/__init__.py b/manuskript/importer/__init__.py index 3edfeba..196784c 100644 --- a/manuskript/importer/__init__.py +++ b/manuskript/importer/__init__.py @@ -6,7 +6,7 @@ from manuskript.importer.markdownImporter import markdownImporter from manuskript.importer.opmlImporter import opmlImporter from manuskript.importer.pandocImporters import markdownPandocImporter, \ odtPandocImporter, ePubPandocImporter, docXPandocImporter, HTMLPandocImporter, \ - rstPandocImporter, LaTeXPandocImporter + rstPandocImporter, LaTeXPandocImporter, OPMLPandocImporter importers = [ # Internal @@ -22,4 +22,5 @@ importers = [ HTMLPandocImporter, rstPandocImporter, LaTeXPandocImporter, + OPMLPandocImporter, ] diff --git a/manuskript/importer/abstractImporter.py b/manuskript/importer/abstractImporter.py index 0e315f7..440068b 100644 --- a/manuskript/importer/abstractImporter.py +++ b/manuskript/importer/abstractImporter.py @@ -24,6 +24,7 @@ class abstractImporter: fileFormat = "" # File format accepted. For example: "OPML Files (*.opml)" # For folder, use "<>" icon = "" + engine = "Internal" def __init__(self): self.settings = {} @@ -143,6 +144,8 @@ class abstractImporter: 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) diff --git a/manuskript/importer/folderImporter.py b/manuskript/importer/folderImporter.py index a8e3f25..4a3929c 100644 --- a/manuskript/importer/folderImporter.py +++ b/manuskript/importer/folderImporter.py @@ -31,12 +31,6 @@ class folderImporter(abstractImporter): 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: @@ -109,10 +103,6 @@ class folderImporter(abstractImporter): 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), diff --git a/manuskript/importer/markdownImporter.py b/manuskript/importer/markdownImporter.py index 089aec0..2e60f85 100644 --- a/manuskript/importer/markdownImporter.py +++ b/manuskript/importer/markdownImporter.py @@ -91,10 +91,6 @@ class markdownImporter(abstractImporter): setextHeader1 = re.compile(r"(.+)\n===+$", re.MULTILINE) setextHeader2 = re.compile(r"(.+)\n---+$", re.MULTILINE) - # Import in top level folder? - if self.getSetting("topLevelFolder").value(): - parent = addTitle(os.path.basename(filePath), parentItem, 0) - # We store the level of each item in a temporary var parent.__miLevel = 0 # markdown importer header level @@ -183,10 +179,6 @@ class markdownImporter(abstractImporter): parser that will go through a markdown document and create items for each titles.
 """)) - self.addSetting("topLevelFolder", "checkbox", - qApp.translate("Import", "Import in a top-level folder."), - default=False), - for s in self.settings: self.settings[s].widget(group) diff --git a/manuskript/importer/pandocImporters.py b/manuskript/importer/pandocImporters.py index 89548dc..bbdfa9c 100644 --- a/manuskript/importer/pandocImporters.py +++ b/manuskript/importer/pandocImporters.py @@ -11,6 +11,7 @@ from PyQt5.QtWidgets import qApp class pandocImporter(abstractImporter): formatFrom = "" + engine = "Pandoc" @classmethod def isValid(cls): @@ -62,16 +63,14 @@ class pandocImporter(abstractImporter): self.addSetting("wrap", "combo", qApp.translate("Import", "Wrap lines:"), - vals="auto|none|preserve") - - self.addSetting("infoWrap", "label", - qApp.translate("Import", """(auto: wraps at - 72 characters.
- none: no line wrap.
- preserve: tries to preserves line wrap from - the original document. -
 """), - default="none") + vals="auto|none|preserve", + default="none", + tooltip=qApp.translate("Import", """

Should pandoc create + cosmetic / non-semantic line-breaks?

+ auto: wraps at 72 characters.
+ none: no line wrap.
+ preserve: tries to preserves line wrap from the + original document.

""")) for s in self.settings: self.settings[s].widget(group) @@ -86,7 +85,7 @@ class pandocImporter(abstractImporter): class markdownPandocImporter(pandocImporter): - name = "Markdown (pandoc)" + name = "Markdown" description = "Markdown, using pandoc" fileFormat = "Markdown files (*.md *.txt *)" icon = "text-x-markdown" @@ -94,7 +93,7 @@ class markdownPandocImporter(pandocImporter): class ePubPandocImporter(pandocImporter): - name = "ePub (pandoc)" + name = "ePub" description = "" fileFormat = "ePub files (*.epub)" icon = "application-epub+zip" @@ -102,7 +101,7 @@ class ePubPandocImporter(pandocImporter): class docXPandocImporter(pandocImporter): - name = "DocX (pandoc)" + name = "DocX" description = "" fileFormat = "DocX files (*.docx)" icon = "application-vnd.openxmlformats-officedocument.wordprocessingml.document" @@ -110,7 +109,7 @@ class docXPandocImporter(pandocImporter): class odtPandocImporter(pandocImporter): - name = "ODT (pandoc)" + name = "ODT" description = "" fileFormat = "Open Document files (*.odt)" icon = "application-vnd.oasis.opendocument.text" @@ -118,7 +117,7 @@ class odtPandocImporter(pandocImporter): class rstPandocImporter(pandocImporter): - name = "reStructuredText (pandoc)" + name = "reStructuredText" description = "" fileFormat = "reStructuredText files (*.rst)" icon = "text-plain" @@ -126,7 +125,7 @@ class rstPandocImporter(pandocImporter): class HTMLPandocImporter(pandocImporter): - name = "HTML (pandoc)" + name = "HTML" description = "" fileFormat = "HTML files (*.htm *.html)" icon = "text-html" @@ -134,12 +133,20 @@ class HTMLPandocImporter(pandocImporter): class LaTeXPandocImporter(pandocImporter): - name = "LaTeX (pandoc)" + 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" + diff --git a/manuskript/ui/importers/generalSettings.py b/manuskript/ui/importers/generalSettings.py index e7dc43e..874a3fc 100644 --- a/manuskript/ui/importers/generalSettings.py +++ b/manuskript/ui/importers/generalSettings.py @@ -59,6 +59,13 @@ class generalSettings(QWidget, Ui_generalSettings): 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() diff --git a/manuskript/ui/importers/generalSettings_ui.py b/manuskript/ui/importers/generalSettings_ui.py index 1843ee0..0c4d36b 100644 --- a/manuskript/ui/importers/generalSettings_ui.py +++ b/manuskript/ui/importers/generalSettings_ui.py @@ -39,14 +39,14 @@ class Ui_generalSettings(object): self.chkGeneralSplitScenes = QtWidgets.QCheckBox(self.general) self.chkGeneralSplitScenes.setEnabled(False) self.chkGeneralSplitScenes.setObjectName("chkGeneralSplitScenes") - self.formLayout_4.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.chkGeneralSplitScenes) + self.formLayout_4.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.chkGeneralSplitScenes) self.txtGeneralSplitScenes = QtWidgets.QLineEdit(self.general) self.txtGeneralSplitScenes.setEnabled(False) self.txtGeneralSplitScenes.setObjectName("txtGeneralSplitScenes") - self.formLayout_4.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.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(3, QtWidgets.QFormLayout.SpanningRole, self.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") @@ -54,6 +54,9 @@ class Ui_generalSettings(object): 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.LabelRole, self.chkGeneralTopLevel) self.verticalLayout_5.addLayout(self.formLayout_4) self.toolBox.addItem(self.general, "") self.verticalLayout_2.addWidget(self.toolBox) @@ -69,5 +72,6 @@ class Ui_generalSettings(object): self.chkGeneralSplitScenes.setText(_translate("generalSettings", "Split scenes at:")) 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")) diff --git a/manuskript/ui/importers/generalSettings_ui.ui b/manuskript/ui/importers/generalSettings_ui.ui index 21c4f95..a1ea203 100644 --- a/manuskript/ui/importers/generalSettings_ui.ui +++ b/manuskript/ui/importers/generalSettings_ui.ui @@ -78,7 +78,7 @@ QToolBox::tab:selected, QToolBox::tab:hover{ QFormLayout::WrapLongRows - + false @@ -88,14 +88,14 @@ QToolBox::tab:selected, QToolBox::tab:hover{ - + false - + Trim long titles (> 32 chars) @@ -116,6 +116,13 @@ QToolBox::tab:selected, QToolBox::tab:hover{ + + + + Import in a top-level folder + + +
diff --git a/manuskript/ui/importers/importer.py b/manuskript/ui/importers/importer.py index 6314ac6..fff1b6f 100644 --- a/manuskript/ui/importers/importer.py +++ b/manuskript/ui/importers/importer.py @@ -12,7 +12,7 @@ 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 +from manuskript.models.outlineModel import outlineModel, outlineItem from manuskript.enums import Outline from manuskript.exporter.pandoc import pandocExporter @@ -67,7 +67,21 @@ class importerDialog(QWidget, Ui_importer): def addFormat(name, icon): self.cmbImporters.addItem(QIcon.fromTheme(icon), name) + 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) if not f.isValid(): item = self.cmbImporters.model().item(self.cmbImporters.count() - 1) @@ -79,8 +93,14 @@ class importerDialog(QWidget, Ui_importer): "Install pandoc to import from much more formats", "::URL::http://pandoc.org/installing.html") + self.cmbImporters.setCurrentIndex(1) + def currentFormat(self): formatName = self.cmbImporters.currentText() + + if self.cmbImporters.currentData() == "header": + return None + F = [F for F in self.importers if F.name == formatName][0] # We instantiate the class return F() @@ -162,15 +182,18 @@ class importerDialog(QWidget, Ui_importer): 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) - #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) @@ -245,6 +268,8 @@ class importerDialog(QWidget, Ui_importer): necessary. """ + items = [] + # We find the current selected format F = self._format @@ -252,11 +277,20 @@ class importerDialog(QWidget, Ui_importer): 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 - items = F.startImport(self.fileName, + rItems = F.startImport(self.fileName, parentItem, self.settingsWidget) + items.extend(rItems) + # Do transformations items = self.doTransformations(items) From a231721bdbcda2e797f44a0cd084bd428b32951e Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Thu, 9 Nov 2017 15:18:21 +0100 Subject: [PATCH 34/65] Adds: ability to split scenes at custom points. #200 --- manuskript/importer/__init__.py | 4 +- manuskript/importer/markdownImporter.py | 12 ++-- manuskript/importer/opmlImporter.py | 8 ++- manuskript/importer/pandocImporters.py | 8 ++- manuskript/models/outlineModel.py | 59 ++++++++++++++++--- manuskript/ui/importers/generalSettings.py | 19 ++++++ manuskript/ui/importers/generalSettings_ui.py | 6 +- manuskript/ui/importers/generalSettings_ui.ui | 12 ++-- manuskript/ui/importers/importer.py | 28 +++++---- 9 files changed, 115 insertions(+), 41 deletions(-) diff --git a/manuskript/importer/__init__.py b/manuskript/importer/__init__.py index 196784c..d5a0aef 100644 --- a/manuskript/importer/__init__.py +++ b/manuskript/importer/__init__.py @@ -10,9 +10,9 @@ from manuskript.importer.pandocImporters import markdownPandocImporter, \ importers = [ # Internal - opmlImporter, - folderImporter, markdownImporter, + folderImporter, + opmlImporter, # Pandoc markdownPandocImporter, diff --git a/manuskript/importer/markdownImporter.py b/manuskript/importer/markdownImporter.py index 2e60f85..a57b6be 100644 --- a/manuskript/importer/markdownImporter.py +++ b/manuskript/importer/markdownImporter.py @@ -88,8 +88,8 @@ class markdownImporter(abstractImporter): return child ATXHeader = re.compile(r"(\#+)\s*(.+?)\s*\#*$") - setextHeader1 = re.compile(r"(.+)\n===+$", re.MULTILINE) - setextHeader2 = re.compile(r"(.+)\n---+$", re.MULTILINE) + 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 @@ -117,14 +117,15 @@ class markdownImporter(abstractImporter): # Check setext header m = setextHeader1.match(l2) - if not header and m: + + 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: + if not header and m and len(m.group(1)) == len(m.group(2)): header = True level = 2 name = m.group(1) @@ -160,7 +161,8 @@ class markdownImporter(abstractImporter): # So we make it a text item i._data[Outline.type] = "md" i._data[Outline.text] = i.children()[0].text() - i.removeChild(0) + c = i.removeChild(0) + items.remove(c) return items diff --git a/manuskript/importer/opmlImporter.py b/manuskript/importer/opmlImporter.py index 2fce7bc..b7b8b99 100644 --- a/manuskript/importer/opmlImporter.py +++ b/manuskript/importer/opmlImporter.py @@ -57,7 +57,7 @@ class opmlImporter(abstractImporter): if outlineEls is not None: for element in outlineEls: - items.append(cls.parseItems(element, parentItem)) + items.extend(cls.parseItems(element, parentItem)) ret = True if not ret: @@ -72,10 +72,12 @@ class opmlImporter(abstractImporter): @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') @@ -86,12 +88,12 @@ class opmlImporter(abstractImporter): children = underElement.findall('outline') if children is not None and len(children) > 0: for el in children: - cls.parseItems(el, card) + items.extend(cls.parseItems(el, card)) else: card.setData(Outline.type.value, 'md') card.setData(Outline.text.value, body) - return card + return items @classmethod def saveNewlines(cls, inString): diff --git a/manuskript/importer/pandocImporters.py b/manuskript/importer/pandocImporters.py index bbdfa9c..023a4b0 100644 --- a/manuskript/importer/pandocImporters.py +++ b/manuskript/importer/pandocImporters.py @@ -12,6 +12,7 @@ class pandocImporter(abstractImporter): formatFrom = "" engine = "Pandoc" + extraArgs = [] @classmethod def isValid(cls): @@ -27,10 +28,14 @@ class pandocImporter(abstractImporter): "--from={}".format(self.formatFrom), filePath, "--to={}".format(formatTo), - "--standalone", "--wrap={}".format(wrap), ] + if formatTo == "opml": + args.append("--standalone") + + args += self.extraArgs + r = pandocExporter().run(args) if formatTo == "opml": @@ -149,4 +154,3 @@ class OPMLPandocImporter(pandocImporter): - diff --git a/manuskript/models/outlineModel.py b/manuskript/models/outlineModel.py index ca9eaec..a20c2e0 100644 --- a/manuskript/models/outlineModel.py +++ b/manuskript/models/outlineModel.py @@ -816,10 +816,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") @@ -879,10 +925,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()) diff --git a/manuskript/ui/importers/generalSettings.py b/manuskript/ui/importers/generalSettings.py index 874a3fc..3940aab 100644 --- a/manuskript/ui/importers/generalSettings.py +++ b/manuskript/ui/importers/generalSettings.py @@ -69,3 +69,22 @@ class generalSettings(QWidget, Ui_generalSettings): 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 + diff --git a/manuskript/ui/importers/generalSettings_ui.py b/manuskript/ui/importers/generalSettings_ui.py index 0c4d36b..6ca528a 100644 --- a/manuskript/ui/importers/generalSettings_ui.py +++ b/manuskript/ui/importers/generalSettings_ui.py @@ -37,11 +37,10 @@ class Ui_generalSettings(object): self.formLayout_4.setRowWrapPolicy(QtWidgets.QFormLayout.WrapLongRows) self.formLayout_4.setObjectName("formLayout_4") self.chkGeneralSplitScenes = QtWidgets.QCheckBox(self.general) - self.chkGeneralSplitScenes.setEnabled(False) self.chkGeneralSplitScenes.setObjectName("chkGeneralSplitScenes") self.formLayout_4.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.chkGeneralSplitScenes) self.txtGeneralSplitScenes = QtWidgets.QLineEdit(self.general) - self.txtGeneralSplitScenes.setEnabled(False) + self.txtGeneralSplitScenes.setText("") self.txtGeneralSplitScenes.setObjectName("txtGeneralSplitScenes") self.formLayout_4.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.txtGeneralSplitScenes) self.chkGeneralTrimTitles = QtWidgets.QCheckBox(self.general) @@ -56,7 +55,7 @@ class Ui_generalSettings(object): 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.LabelRole, self.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) @@ -70,6 +69,7 @@ class Ui_generalSettings(object): _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")) diff --git a/manuskript/ui/importers/generalSettings_ui.ui b/manuskript/ui/importers/generalSettings_ui.ui index a1ea203..955fc21 100644 --- a/manuskript/ui/importers/generalSettings_ui.ui +++ b/manuskript/ui/importers/generalSettings_ui.ui @@ -80,9 +80,6 @@ QToolBox::tab:selected, QToolBox::tab:hover{ - - false - Split scenes at: @@ -90,8 +87,11 @@ QToolBox::tab:selected, QToolBox::tab:hover{ - - false + + + + + \n---\n @@ -116,7 +116,7 @@ QToolBox::tab:selected, QToolBox::tab:hover{ - + Import in a top-level folder diff --git a/manuskript/ui/importers/importer.py b/manuskript/ui/importers/importer.py index fff1b6f..f09ae44 100644 --- a/manuskript/ui/importers/importer.py +++ b/manuskript/ui/importers/importer.py @@ -64,8 +64,10 @@ class importerDialog(QWidget, Ui_importer): def populateImportList(self): - def addFormat(name, icon): - self.cmbImporters.addItem(QIcon.fromTheme(icon), name) + 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") @@ -82,7 +84,7 @@ class importerDialog(QWidget, Ui_importer): addHeader(f.engine) lastEngine = f.engine - addFormat(f.name, f.icon) + 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) @@ -96,12 +98,13 @@ class importerDialog(QWidget, Ui_importer): self.cmbImporters.setCurrentIndex(1) def currentFormat(self): - formatName = self.cmbImporters.currentText() + formatIdentifier = self.cmbImporters.currentData() - if self.cmbImporters.currentData() == "header": + if formatIdentifier == "header": return None - F = [F for F in self.importers if F.name == formatName][0] + F = [F for F in self.importers + if formatIdentifier == "{}:{}".format(F.engine, F.name)][0] # We instantiate the class return F() @@ -303,15 +306,14 @@ class importerDialog(QWidget, Ui_importer): # Trim long titles if self.settingsWidget.trimLongTitles(): - def trim(item): + for item in items: 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: - trim(i) + + # Split at + if self.settingsWidget.splitScenes(): + for item in items: + item.split(self.settingsWidget.splitScenes(), recursive=False) return items From bc70501373c7ce6b62f2b23e92da8cd6b6865512 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Thu, 9 Nov 2017 20:30:54 +0100 Subject: [PATCH 35/65] 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 b7873ad..04d57b7 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 e0fa719..30857ef 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 5b8a7e1..e4846fa 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 36/65] 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 0000000..6474bf4 --- /dev/null +++ b/icons/NumixMsk/128x128/places/folder-copy.svg @@ -0,0 +1,157 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/NumixMsk/16x16/actions/merge.svg b/icons/NumixMsk/16x16/actions/merge.svg new file mode 100644 index 0000000..34bf799 --- /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 0000000..e495231 --- /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 0000000..b4008db --- /dev/null +++ b/icons/NumixMsk/16x16/places/folder-copy.svg @@ -0,0 +1,117 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/icons/NumixMsk/22x22/actions/merge.svg b/icons/NumixMsk/22x22/actions/merge.svg new file mode 100644 index 0000000..e3996fd --- /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 0000000..f004015 --- /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 0000000..09e1ea9 --- /dev/null +++ b/icons/NumixMsk/22x22/places/folder-copy.svg @@ -0,0 +1,180 @@ + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/NumixMsk/24x24/actions/merge.svg b/icons/NumixMsk/24x24/actions/merge.svg new file mode 100644 index 0000000..96f0ca7 --- /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 0000000..c247872 --- /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 0000000..d86840b --- /dev/null +++ b/icons/NumixMsk/24x24/places/folder-copy.svg @@ -0,0 +1,180 @@ + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/NumixMsk/256x256/places/folder-copy.svg b/icons/NumixMsk/256x256/places/folder-copy.svg new file mode 100644 index 0000000..483a5d8 --- /dev/null +++ b/icons/NumixMsk/256x256/places/folder-copy.svg @@ -0,0 +1,162 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/NumixMsk/32x32/actions/merge.svg b/icons/NumixMsk/32x32/actions/merge.svg new file mode 100644 index 0000000..dbd0db4 --- /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 0000000..04d6458 --- /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 0000000..d9f74d9 --- /dev/null +++ b/icons/NumixMsk/32x32/places/folder-copy.svg @@ -0,0 +1,167 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/NumixMsk/48x48/actions/merge.svg b/icons/NumixMsk/48x48/actions/merge.svg new file mode 100644 index 0000000..d8eb78a --- /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 0000000..6742824 --- /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 0000000..e943f0c --- /dev/null +++ b/icons/NumixMsk/48x48/places/folder-copy.svg @@ -0,0 +1,352 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/NumixMsk/64x64/actions/merge.svg b/icons/NumixMsk/64x64/actions/merge.svg new file mode 100644 index 0000000..c81beca --- /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 0000000..af0d3d0 --- /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 0000000..31c3ab6 --- /dev/null +++ b/icons/NumixMsk/64x64/places/folder-copy.svg @@ -0,0 +1,193 @@ + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/NumixMsk/96x96/places/folder-copy.svg b/icons/NumixMsk/96x96/places/folder-copy.svg new file mode 100644 index 0000000..6fce318 --- /dev/null +++ b/icons/NumixMsk/96x96/places/folder-copy.svg @@ -0,0 +1,350 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/manuskript/mainWindow.py b/manuskript/mainWindow.py index 4429d01..19670e2 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 e3f61e5..8831c55 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 60ee400..309d8ee 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 f4591fa..b4f920f 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 26b92db..d0005c4 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 d13d875..e87bd79 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 7b12f79..36c6c4b 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 37/65] 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 19670e2..5618f25 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 8831c55..c38260a 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 4beb4b5..5cb1bb4 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 309d8ee..1090307 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 b4f920f..3c51f1d 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 0000000..d77456d --- /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 36c6c4b..f43f5b4 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 bb98996..1c2513f 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 38/65] 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 04d57b7..03e2a79 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 5618f25..61773d9 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 c38260a..2748ba2 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 1090307..623b62a 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 3c51f1d..606c466 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 d77456d..544b533 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 f43f5b4..ccd655c 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 1c2513f..a9c7907 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 39/65] 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 2748ba2..140def2 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. From 40b07938d0f77a96f044435a07c5cd212f8553fa Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Sat, 11 Nov 2017 09:55:36 +0100 Subject: [PATCH 40/65] Corrects a typo. #200 --- manuskript/ui/tools/splitDialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manuskript/ui/tools/splitDialog.py b/manuskript/ui/tools/splitDialog.py index 544b533..da950bb 100644 --- a/manuskript/ui/tools/splitDialog.py +++ b/manuskript/ui/tools/splitDialog.py @@ -22,7 +22,7 @@ class splitDialog(QInputDialog):

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: +

The split mark can contain folling escape sequences:

  • \\n: line break
  • \\t: tab
  • From 76be369a7c43e784e92ce5b449ece2a7c95de067 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20=C5=9Awi=C4=85tkowski?= Date: Thu, 2 Nov 2017 14:44:49 +0100 Subject: [PATCH 41/65] Add Travis configuration --- .travis.yml | 23 +++++++++++++++++++++++ package/dependency_test.py | 8 ++++++++ package/prepare_osx.sh | 9 +++++++++ 3 files changed, 40 insertions(+) create mode 100644 .travis.yml create mode 100644 package/dependency_test.py create mode 100755 package/prepare_osx.sh diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..1f95e99 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,23 @@ +# according to the internet Python envs are hard and it's better to build them from scratch +language: cpp +os: +- osx +sudo: required +install: +- if [ "$TRAVIS_OS_NAME" = "osx" ]; then bash package/prepare_osx.sh; fi +script: +- python3 package/dependency_test.py +before_deploy: +- export FILENAME=manuskript-$TRAVIS_BRANCH-$TRAVIS_OS_NAME.zip +- pyinstaller manuskript.spec --clean +- cd dist && zip $FILENAME -r manuskript && cd .. +- ls dist +deploy: + provider: releases + api_key: + secure: XVcO7GsEEQxNymr7h567BxkERBZittpoC3oMZN+F2rzIoeabo/wjjBSyhMvgJKDYm1rFjQro2tr9r1ubMCjUyBP7eioOIY+7clX7pmxKPYosYgrXXx2WnaWgv8zkVk7KkXmGyFxjnp7jyTNytjFlcOeXl1cvLHum+a56vD6pINQft9V1eQ3NuPZnM729pEndL2TkI0o246QNNi1XBxIeX/o/15zx0BTMG5z8M0oGBW7VaZC6IOZ9NED7L4VGAkVizb3ONLypnQRGfFdWWE3o6gfYTLhk9fGekmlykbpOjgQcxpUe29AExKVToXfDWYUhAzaNsrpDaXXn7C0RDBVRiJYN10CiEojaZOTq7F2nrnE5Y5x1k3oXElXVAT9JQoFw935aJpEM7g5WFwmPikXoHkTFtoXWyVHcUyn2ByYuA7wu61u9NVKFvMOeH0Qg/U3IQhXvu5FekMAbDbu2H0VfKCu1+RnCepp3JFVFIPB1nr7TsmAuJDD2Z1qJ1O7S66DFWt05VLh2ruCL/lKjQIY9tP8cSaVrYEiXFBDam5JyhILYMyKWonYZWbZ4B+5Vzersq+ZjXXRkdSDHX4DuKtKiI5z2oP1ijLU0i7gkBK7UMdaKq/2l3mT3JDK88oaL5K6BeB9C50p0k7rlQtC920SvmmqDjiDelJqaCtcqzEw13H4= + file: dist/$FILENAME + overwrite: true + skip_cleanup: true + on: + tags: true diff --git a/package/dependency_test.py b/package/dependency_test.py new file mode 100644 index 0000000..9f0d947 --- /dev/null +++ b/package/dependency_test.py @@ -0,0 +1,8 @@ +import os +import sys + +realpath = os.path.realpath(__file__) + +sys.path.insert(1, os.path.join(os.path.dirname(realpath), '..')) + +from manuskript import main diff --git a/package/prepare_osx.sh b/package/prepare_osx.sh new file mode 100755 index 0000000..9256e1a --- /dev/null +++ b/package/prepare_osx.sh @@ -0,0 +1,9 @@ +#!/bin/bash +set -ev +brew update +brew install python3 # enchant +sudo pip3 install --upgrade pip setuptools wheel +pip3 install pyinstaller PyQt5 lxml # pyenchant +# brew install qt hunspell +# fooling PyEnchant as described in the wiki: https://github.com/olivierkes/manuskript/wiki/Package-manuskript-for-OS-X +# sudo touch /usr/local/share/aspell From 1aa6afc2bcfd80396662ac352efbbd52cea12acb Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Sun, 12 Nov 2017 20:48:27 +0100 Subject: [PATCH 42/65] Fixes: One white pixel visible in full screen mode #210 --- manuskript/ui/editors/fullScreenEditor.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/manuskript/ui/editors/fullScreenEditor.py b/manuskript/ui/editors/fullScreenEditor.py index 52d1eed..cef0ac6 100644 --- a/manuskript/ui/editors/fullScreenEditor.py +++ b/manuskript/ui/editors/fullScreenEditor.py @@ -243,11 +243,12 @@ class fullScreenEditor(QWidget): def hideWidget(self, widget): if widget not in self._geometries: self._geometries[widget] = widget.geometry() - + if hasattr(widget, "_autoHide") and not widget._autoHide: return - - widget.move(self.geometry().bottomRight()) + + # Hides wiget in the bottom right corner + widget.move(self.geometry().bottomRight() + QPoint(1, 1)) def showWidget(self, widget): if widget in self._geometries: @@ -341,7 +342,7 @@ class myPanel(QWidget): self.show() self.setAttribute(Qt.WA_TranslucentBackground) self._autoHide = True - + if not vertical: self.setLayout(QHBoxLayout()) else: @@ -355,10 +356,10 @@ class myPanel(QWidget): r = event.rect() painter = QPainter(self) painter.fillRect(r, self._color) - + def setAutoHide(self, value): self._autoHide = value - + def mouseReleaseEvent(self, event): if event.button() == Qt.RightButton: m = QMenu() From c2a08317ad70cc6f64080a5288655d55b698e8d4 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Sun, 12 Nov 2017 21:26:21 +0100 Subject: [PATCH 43/65] Updates secure token --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 1f95e99..90555f1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,7 +15,7 @@ before_deploy: deploy: provider: releases api_key: - secure: XVcO7GsEEQxNymr7h567BxkERBZittpoC3oMZN+F2rzIoeabo/wjjBSyhMvgJKDYm1rFjQro2tr9r1ubMCjUyBP7eioOIY+7clX7pmxKPYosYgrXXx2WnaWgv8zkVk7KkXmGyFxjnp7jyTNytjFlcOeXl1cvLHum+a56vD6pINQft9V1eQ3NuPZnM729pEndL2TkI0o246QNNi1XBxIeX/o/15zx0BTMG5z8M0oGBW7VaZC6IOZ9NED7L4VGAkVizb3ONLypnQRGfFdWWE3o6gfYTLhk9fGekmlykbpOjgQcxpUe29AExKVToXfDWYUhAzaNsrpDaXXn7C0RDBVRiJYN10CiEojaZOTq7F2nrnE5Y5x1k3oXElXVAT9JQoFw935aJpEM7g5WFwmPikXoHkTFtoXWyVHcUyn2ByYuA7wu61u9NVKFvMOeH0Qg/U3IQhXvu5FekMAbDbu2H0VfKCu1+RnCepp3JFVFIPB1nr7TsmAuJDD2Z1qJ1O7S66DFWt05VLh2ruCL/lKjQIY9tP8cSaVrYEiXFBDam5JyhILYMyKWonYZWbZ4B+5Vzersq+ZjXXRkdSDHX4DuKtKiI5z2oP1ijLU0i7gkBK7UMdaKq/2l3mT3JDK88oaL5K6BeB9C50p0k7rlQtC920SvmmqDjiDelJqaCtcqzEw13H4= + secure: lKuWQ9NWkLfDkkAiSnMh2PYkAGE1xh4pkSN5Ceb2IF9ee9i9YrJ9YFcvh420JSP+BYUl1OKczC5b3d9hUUZcfMwUkuZqPFCehOWP0O8dNs3XKZtmSU4POwR8lx7jRSO132qV/zgthBTK8PbeC2jIiMF4+4ESMsuEDptmGSOhgHtOcdJNDNz8gTbygXZVcl4U04se1ywtL62UQcTNMEKKLeBYQlrAXpcPiw4Htv9spEx6BewgarhRvJ/hysqeJgrH4wUVcjFw6Djppu3fmhrvgtaIU3ONxMLqgCuBZa03Q2LuF/RTYl9/DUgIjqsm1aOVooQZttYsUYWrLfyZNnLGD4WkdILcTMyexEVpQ/ejcEPm8gCf2PtklRtENZIxV2eQkLcPuSAPGWC8ue6a1etIUbYDbMT47SsdwkUsbyPpT8bnBvPf7gfmH/e20b1UQtxgmE5GDpbYZCTHf0kvwIFrBQzNmCtiGsXGJxIVx4msndplh8MdWFDBTEr0Ca8Tt45Fp/QPU7KAmdlQog9fWOfnJezIlBpFAXDa+AN2b/9uE+led5fmqQ62nuvjsYvWmJti2NW0IJ8UI26JGfh4Z1offE2bYp4onimfbRXSXWQs+Dm2l4CdTkc3habSWoUMw2R8mGbEfgfhzTFiAryg1mCtWy8AxUgfcUurd6BCwipH1ck= file: dist/$FILENAME overwrite: true skip_cleanup: true From 7b4ba33d151d1aa4336724838661e332453c3858 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Mon, 13 Nov 2017 00:32:34 +0100 Subject: [PATCH 44/65] Adds: MindMap import format #208 --- manuskript/importer/__init__.py | 2 + manuskript/importer/mindMapImporter.py | 80 ++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 manuskript/importer/mindMapImporter.py diff --git a/manuskript/importer/__init__.py b/manuskript/importer/__init__.py index d5a0aef..16e4b7e 100644 --- a/manuskript/importer/__init__.py +++ b/manuskript/importer/__init__.py @@ -4,6 +4,7 @@ from manuskript.importer.folderImporter import folderImporter from manuskript.importer.markdownImporter import markdownImporter from manuskript.importer.opmlImporter import opmlImporter +from manuskript.importer.mindMapImporter import mindMapImporter from manuskript.importer.pandocImporters import markdownPandocImporter, \ odtPandocImporter, ePubPandocImporter, docXPandocImporter, HTMLPandocImporter, \ rstPandocImporter, LaTeXPandocImporter, OPMLPandocImporter @@ -13,6 +14,7 @@ importers = [ markdownImporter, folderImporter, opmlImporter, + mindMapImporter, # Pandoc markdownPandocImporter, diff --git a/manuskript/importer/mindMapImporter.py b/manuskript/importer/mindMapImporter.py new file mode 100644 index 0000000..af215ab --- /dev/null +++ b/manuskript/importer/mindMapImporter.py @@ -0,0 +1,80 @@ +#!/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 mindMapImporter(abstractImporter): + + name = "Mind Map" + description = "" + fileFormat = "Mind map Files (*.mm)" + 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 mind map free plane. + """ + ret = False + + if filePath != "": + # We have a filePath, so we read the file + try: + with open(filePath, 'rb') as f: + content = f.read() + except: + return None + + elif fromString == "": + # We have neither filePath nor fromString, so we leave + return None + + else: + # We load from string + content = bytes(fromString, "utf-8") + + root = ET.fromstring(content) + + node = root.find("node") + items = [] + + if node is not None: + items.extend(cls.parseItems(node, parentItem)) + ret = True + + if not ret: + QMessageBox.critical( + settingsWidget, + qApp.translate("Import", "Mind Map Import"), + qApp.translate("Import", "This does not appear to be a valid Mind Map file.")) + + return None + + return items + + @classmethod + def parseItems(cls, underElement, parentItem=None): + items = [] + title = underElement.get('TEXT') + if title is not None: + + item = outlineItem(parent=parentItem, title=title) + items.append(item) + + children = underElement.findall('node') + if children is not None and len(children) > 0: + for c in children: + items.extend(cls.parseItems(c, item)) + else: + item.setData(Outline.type.value, 'md') + + return items From d3e724ccb0f764917a4ed3daea3e1989ad357549 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Mon, 13 Nov 2017 00:45:50 +0100 Subject: [PATCH 45/65] Import: preview after chosing file, and add seting to Mind Map importer #208 --- manuskript/importer/mindMapImporter.py | 31 ++++++++++++++++++++------ manuskript/ui/importers/importer.py | 1 + 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/manuskript/importer/mindMapImporter.py b/manuskript/importer/mindMapImporter.py index af215ab..f5599f1 100644 --- a/manuskript/importer/mindMapImporter.py +++ b/manuskript/importer/mindMapImporter.py @@ -19,8 +19,7 @@ class mindMapImporter(abstractImporter): def isValid(cls): return True - @classmethod - def startImport(cls, filePath, parentItem, settingsWidget, fromString=None): + def startImport(self, filePath, parentItem, settingsWidget, fromString=None): """ Import/export outline cards in mind map free plane. """ @@ -48,7 +47,7 @@ class mindMapImporter(abstractImporter): items = [] if node is not None: - items.extend(cls.parseItems(node, parentItem)) + items.extend(self.parseItems(node, parentItem)) ret = True if not ret: @@ -61,8 +60,26 @@ class mindMapImporter(abstractImporter): return items - @classmethod - def parseItems(cls, underElement, parentItem=None): + 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", "Mind Map import")) + + self.addSetting("importTipAs", "combo", + qApp.translate("Import", "Import tip as:"), + vals="Folder|Text", + ) + + for s in self.settings: + self.settings[s].widget(group) + + return widget + + def parseItems(self, underElement, parentItem=None): items = [] title = underElement.get('TEXT') if title is not None: @@ -73,8 +90,8 @@ class mindMapImporter(abstractImporter): children = underElement.findall('node') if children is not None and len(children) > 0: for c in children: - items.extend(cls.parseItems(c, item)) - else: + items.extend(self.parseItems(c, item)) + elif self.getSetting("importTipAs").value() == "Text": item.setData(Outline.type.value, 'md') return items diff --git a/manuskript/ui/importers/importer.py b/manuskript/ui/importers/importer.py index f09ae44..e944183 100644 --- a/manuskript/ui/importers/importer.py +++ b/manuskript/ui/importers/importer.py @@ -130,6 +130,7 @@ class importerDialog(QWidget, Ui_importer): fileName, _ = QFileDialog.getOpenFileName(self, "Import from file", "", F.fileFormat, options=options) self.setFileName(fileName) + self.preview() def setFileName(self, fileName): """ From c4f8d0da60bb3f4db48b9dd14f36c6c17d683236 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Mon, 13 Nov 2017 00:51:08 +0100 Subject: [PATCH 46/65] Fixes: Missing default file extension when Saving As... #211 --- manuskript/ui/welcome.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/manuskript/ui/welcome.py b/manuskript/ui/welcome.py index 4b3286b..e2f98b6 100644 --- a/manuskript/ui/welcome.py +++ b/manuskript/ui/welcome.py @@ -141,6 +141,8 @@ class welcome(QWidget, Ui_welcome): self.tr("Manuskript project (*.msk)"))[0] if filename: + if filename[-4:] != ".msk": + filename += ".msk" self.appendToRecentFiles(filename) loadSave.clearSaveCache() # Ensure all file(s) are saved under new filename self.mw.saveDatas(filename) From 9039fa0220f7c6a2c5925af67c167174ef4d7f9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20=C5=9Awi=C4=85tkowski?= Date: Mon, 13 Nov 2017 10:47:19 +0100 Subject: [PATCH 47/65] Add PyEnchant support to OSX builds --- package/prepare_osx.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package/prepare_osx.sh b/package/prepare_osx.sh index 9256e1a..9529460 100755 --- a/package/prepare_osx.sh +++ b/package/prepare_osx.sh @@ -1,9 +1,9 @@ #!/bin/bash set -ev brew update -brew install python3 # enchant +brew install python3 enchant sudo pip3 install --upgrade pip setuptools wheel -pip3 install pyinstaller PyQt5 lxml # pyenchant -# brew install qt hunspell +pip3 install pyinstaller PyQt5 lxml pyenchant +brew install qt hunspell # fooling PyEnchant as described in the wiki: https://github.com/olivierkes/manuskript/wiki/Package-manuskript-for-OS-X -# sudo touch /usr/local/share/aspell +sudo touch /usr/local/share/aspell From fd0cd2cd4fb265ff0de6c53d0cc42ffd0df90577 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Mon, 13 Nov 2017 22:55:33 +0100 Subject: [PATCH 48/65] Improves the Mind Map importer #208 --- .../16x16/actions/document-import.svg | 6 ++ .../22x22/actions/document-import.svg | 6 ++ .../24x24/actions/document-import.svg | 6 ++ .../32x32/actions/document-import.svg | 6 ++ .../48x48/actions/document-import.svg | 6 ++ .../64x64/actions/document-import.svg | 6 ++ manuskript/converters/__init__.py | 36 ++++++++ manuskript/converters/abstractConverter.py | 18 ++++ manuskript/converters/markdownConverter.py | 38 +++++++++ manuskript/converters/pandocConverter.py | 85 +++++++++++++++++++ manuskript/functions.py | 11 --- manuskript/importer/mindMapImporter.py | 71 +++++++++++++--- manuskript/load_save/version_1.py | 3 +- manuskript/models/outlineModel.py | 3 +- manuskript/ui/importers/importer.py | 3 + manuskript/ui/importers/importer_ui.py | 2 +- manuskript/ui/importers/importer_ui.ui | 3 +- 17 files changed, 282 insertions(+), 27 deletions(-) create mode 100644 icons/NumixMsk/16x16/actions/document-import.svg create mode 100644 icons/NumixMsk/22x22/actions/document-import.svg create mode 100644 icons/NumixMsk/24x24/actions/document-import.svg create mode 100644 icons/NumixMsk/32x32/actions/document-import.svg create mode 100644 icons/NumixMsk/48x48/actions/document-import.svg create mode 100644 icons/NumixMsk/64x64/actions/document-import.svg create mode 100644 manuskript/converters/__init__.py create mode 100644 manuskript/converters/abstractConverter.py create mode 100644 manuskript/converters/markdownConverter.py create mode 100644 manuskript/converters/pandocConverter.py diff --git a/icons/NumixMsk/16x16/actions/document-import.svg b/icons/NumixMsk/16x16/actions/document-import.svg new file mode 100644 index 0000000..8ddaea3 --- /dev/null +++ b/icons/NumixMsk/16x16/actions/document-import.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/icons/NumixMsk/22x22/actions/document-import.svg b/icons/NumixMsk/22x22/actions/document-import.svg new file mode 100644 index 0000000..a78c48c --- /dev/null +++ b/icons/NumixMsk/22x22/actions/document-import.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/icons/NumixMsk/24x24/actions/document-import.svg b/icons/NumixMsk/24x24/actions/document-import.svg new file mode 100644 index 0000000..77e06c3 --- /dev/null +++ b/icons/NumixMsk/24x24/actions/document-import.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/icons/NumixMsk/32x32/actions/document-import.svg b/icons/NumixMsk/32x32/actions/document-import.svg new file mode 100644 index 0000000..53d3682 --- /dev/null +++ b/icons/NumixMsk/32x32/actions/document-import.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/icons/NumixMsk/48x48/actions/document-import.svg b/icons/NumixMsk/48x48/actions/document-import.svg new file mode 100644 index 0000000..b2e1682 --- /dev/null +++ b/icons/NumixMsk/48x48/actions/document-import.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/icons/NumixMsk/64x64/actions/document-import.svg b/icons/NumixMsk/64x64/actions/document-import.svg new file mode 100644 index 0000000..3760178 --- /dev/null +++ b/icons/NumixMsk/64x64/actions/document-import.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/manuskript/converters/__init__.py b/manuskript/converters/__init__.py new file mode 100644 index 0000000..b4c7eb1 --- /dev/null +++ b/manuskript/converters/__init__.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- + +""" +The converters package provide functions to quickly convert on the fly from +one format to another. It is responsible to check what external library are +present, and do the job as best as possible with what we have in hand. +""" + +from manuskript.converters.abstractConverter import abstractConverter +from manuskript.converters.pandocConverter import pandocConverter +#from manuskript.converters.markdownConverter import markdownConverter + + +def HTML2MD(html): + + # Convert using pandoc + if pandocConverter.isValid(): + return pandocConverter.convert(html, _from="html", to="markdown") + + # Convert to plain text using QTextEdit + return HTML2PlainText(html) + + +def HTML2PlainText(html): + """ + Convert from HTML to plain text. + """ + + if pandocConverter.isValid(): + return pandocConverter.convert(html, _from="html", to="plain") + + # Last resort: probably resource ineficient + e = QTextEdit() + e.setHtml(html) + return e.toPlainText() diff --git a/manuskript/converters/abstractConverter.py b/manuskript/converters/abstractConverter.py new file mode 100644 index 0000000..d38c3af --- /dev/null +++ b/manuskript/converters/abstractConverter.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- + + +class abstractConverter: + """ + A convertor is used to convert (duh) between stuff. They provide access + to external libraries that may or may not be present. + + Now, things are a bit messy, since classes in `exporter` (and `importer` to a lesser extent) do + the same. In a better world, classes from `exporter` and `importer` would + use convertors to do their stuff. (TODO) + """ + name = "" + + @classmethod + def isValid(cls): + return False diff --git a/manuskript/converters/markdownConverter.py b/manuskript/converters/markdownConverter.py new file mode 100644 index 0000000..f0e577b --- /dev/null +++ b/manuskript/converters/markdownConverter.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- +import os +import shutil +import subprocess + +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import qApp, QMessageBox +from PyQt5.QtGui import QCursor + +from manuskript.converters import abstractConverter +from manuskript.functions import mainWindow + +try: + import markdown as MD +except ImportError: + MD = None + + +class markdownConverter(abstractConverter): + """ + Converter using python module markdown. + """ + + name = "python module markdown" + + @classmethod + def isValid(self): + return MD is not None + + @classmethod + def convert(self, markdown): + if not self.isValid: + print("ERROR: markdownConverter is called but not valid.") + return "" + + html = MD.markdown(markdown) + return html diff --git a/manuskript/converters/pandocConverter.py b/manuskript/converters/pandocConverter.py new file mode 100644 index 0000000..ea91e9a --- /dev/null +++ b/manuskript/converters/pandocConverter.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- +import os +import shutil +import subprocess + +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import qApp, QMessageBox +from PyQt5.QtGui import QCursor + +from manuskript.converters import abstractConverter +from manuskript.functions import mainWindow + + +class pandocConverter(abstractConverter): + + name = "pandoc" + cmd = "pandoc" + + @classmethod + def isValid(self): + if self.path() != None: + return 2 + elif self.customPath() and os.path.exists(self.customPath): + return 1 + else: + return 0 + + @classmethod + def customPath(self): + settings = QSettings() + return settings.value("Exporters/{}_customPath".format(self.name), "") + + @classmethod + def path(self): + return shutil.which(self.cmd) + + @classmethod + def convert(self, src, _from="markdown", to="html", args=None, outputfile=None): + if not self.isValid: + print("ERROR: pandocConverter is called but not valid.") + return "" + + cmd = [self.runCmd()] + + cmd += ["--from={}".format(_from)] + cmd += ["--to={}".format(to)] + + if args: + cmd += args + + if outputfile: + cmd.append("--output={}".format(outputfile)) + + qApp.setOverrideCursor(QCursor(Qt.WaitCursor)) + + p = subprocess.Popen( + cmd, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE + ) + + if not type(src) == bytes: + src = src.encode("utf-8") # assumes utf-8 + + stdout, stderr = p.communicate(src) + + qApp.restoreOverrideCursor() + + if stderr: + err = stderr.decode("utf-8") + print(err) + QMessageBox.critical(mainWindow().dialog, + qApp.translate("Export", "Error"), err) + return None + + return stdout.decode("utf-8") + + @classmethod + def runCmd(self): + if self.isValid() == 2: + return self.cmd + elif self.isValid() == 1: + return self.customPath diff --git a/manuskript/functions.py b/manuskript/functions.py index 03e2a79..7db44c9 100644 --- a/manuskript/functions.py +++ b/manuskript/functions.py @@ -276,17 +276,6 @@ def findFirstFile(regex, path="resources"): if re.match(regex, l): return os.path.join(p, l) - -def HTML2PlainText(html): - """ - Ressource-inefficient way to convert HTML to plain text. - @param html: - @return: - """ - e = QTextEdit() - e.setHtml(html) - return e.toPlainText() - def customIcons(): """ Returns a list of possible customIcons. String from theme. diff --git a/manuskript/importer/mindMapImporter.py b/manuskript/importer/mindMapImporter.py index f5599f1..6d377ca 100644 --- a/manuskript/importer/mindMapImporter.py +++ b/manuskript/importer/mindMapImporter.py @@ -7,6 +7,7 @@ from manuskript.enums import Outline from lxml import etree as ET from manuskript.functions import mainWindow from manuskript.importer.abstractImporter import abstractImporter +from manuskript.converters import HTML2MD, HTML2PlainText class mindMapImporter(abstractImporter): @@ -71,7 +72,7 @@ class mindMapImporter(abstractImporter): self.addSetting("importTipAs", "combo", qApp.translate("Import", "Import tip as:"), - vals="Folder|Text", + vals="Text|Folder", ) for s in self.settings: @@ -81,17 +82,65 @@ class mindMapImporter(abstractImporter): def parseItems(self, underElement, parentItem=None): items = [] - title = underElement.get('TEXT') - if title is not None: - item = outlineItem(parent=parentItem, title=title) - items.append(item) + # Title + title = underElement.get('TEXT', "").replace("\n", " ") + if not title: + title = qApp.translate("Import", "Untitled") - children = underElement.findall('node') - if children is not None and len(children) > 0: - for c in children: - items.extend(self.parseItems(c, item)) - elif self.getSetting("importTipAs").value() == "Text": - item.setData(Outline.type.value, 'md') + item = outlineItem(parent=parentItem, title=title) + items.append(item) + + # URL + url = underElement.get('LINK', None) + + # Rich text content + content = "" + content = underElement.find("richcontent") + if content is not None: + # In Freemind, can be note or node + # Note: it's a note + # Node: it's the title of the node, in rich text + content_type = content.get("TYPE", "NOTE") + content = ET.tostring(content.find("html")) + + if content and content_type == "NODE": + # Content is title + # convert rich text title (in html) to plain text + title = HTML2PlainText(content) #.replace("\n", " ").strip() + # Count the number of lines + lines = [l.strip() for l in title.split("\n") if l.strip()] + + # If there is one line, we use it as title. + # Otherwise we leave it to be inserted as a note. + if len(lines) == 1: + item.setData(Outline.title.value, "".join(lines)) + content = "" + + if content: + # Set the note content as text value + content = HTML2MD(content) + item.setData(Outline.notes.value, content) + + if url: + # Set the url in notes + item.setData(Outline.notes.value, + item.data(Outline.notes.value) + "\n\n" + url) + + children = underElement.findall('node') + + # Process children + if children is not None and len(children) > 0: + for c in children: + items.extend(self.parseItems(c, item)) + + # Process if no children + elif self.getSetting("importTipAs").value() == "Text": + # Transform item to text + item.setData(Outline.type.value, 'md') + # Move notes to text + if item.data(Outline.notes.value): + item.setData(Outline.text.value, item.data(Outline.notes.value)) + item.setData(Outline.notes.value, "") return items diff --git a/manuskript/load_save/version_1.py b/manuskript/load_save/version_1.py index be1eded..6015a3e 100644 --- a/manuskript/load_save/version_1.py +++ b/manuskript/load_save/version_1.py @@ -18,7 +18,8 @@ from PyQt5.QtGui import QColor, QStandardItem from manuskript import settings from manuskript.enums import Character, World, Plot, PlotStep, Outline -from manuskript.functions import mainWindow, iconColor, iconFromColorString, HTML2PlainText +from manuskript.functions import mainWindow, iconColor, iconFromColorString +from manuskript.converters import HTML2PlainText from lxml import etree as ET from manuskript.load_save.version_0 import loadFilesFromZip diff --git a/manuskript/models/outlineModel.py b/manuskript/models/outlineModel.py index 140def2..4ce6772 100644 --- a/manuskript/models/outlineModel.py +++ b/manuskript/models/outlineModel.py @@ -15,7 +15,8 @@ from manuskript import settings from lxml import etree as ET from manuskript.enums import Outline -from manuskript.functions import mainWindow, toInt, wordCount, HTML2PlainText +from manuskript.functions import mainWindow, toInt, wordCount +from manuskript.converters import HTML2PlainText try: locale.setlocale(locale.LC_ALL, '') diff --git a/manuskript/ui/importers/importer.py b/manuskript/ui/importers/importer.py index e944183..ac03ef5 100644 --- a/manuskript/ui/importers/importer.py +++ b/manuskript/ui/importers/importer.py @@ -226,6 +226,9 @@ class importerDialog(QWidget, Ui_importer): def preview(self): + if not self.fileName: + return + # Creating a temporary outlineModel previewModel = outlineModel(self) previewModel.loadFromXML( diff --git a/manuskript/ui/importers/importer_ui.py b/manuskript/ui/importers/importer_ui.py index d23c8e4..327914c 100644 --- a/manuskript/ui/importers/importer_ui.py +++ b/manuskript/ui/importers/importer_ui.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'manuskript/ui/importers/importer_ui.ui' # -# Created by: PyQt5 UI code generator 5.9 +# Created by: PyQt5 UI code generator 5.5.1 # # WARNING! All changes made in this file will be lost! diff --git a/manuskript/ui/importers/importer_ui.ui b/manuskript/ui/importers/importer_ui.ui index a7f2f90..cbf72b9 100644 --- a/manuskript/ui/importers/importer_ui.ui +++ b/manuskript/ui/importers/importer_ui.ui @@ -45,8 +45,7 @@ Chose file - - ../../../../../../../../../../.designer/backup../../../../../../../../../../.designer/backup + From 5207eddfbbf05ac6e92ecb587d29a146606ee8ee Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Tue, 14 Nov 2017 08:24:43 +0100 Subject: [PATCH 49/65] Fixes 213: Program Crash on Import with images --- manuskript/importer/abstractImporter.py | 11 +++++++++++ manuskript/importer/folderImporter.py | 25 +++++++++++++++---------- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/manuskript/importer/abstractImporter.py b/manuskript/importer/abstractImporter.py index 440068b..ba810af 100644 --- a/manuskript/importer/abstractImporter.py +++ b/manuskript/importer/abstractImporter.py @@ -27,6 +27,7 @@ class abstractImporter: engine = "Internal" def __init__(self): + self.settingsList = [] # Keep the name of the settings in order self.settings = {} def startImport(self, filePath, parentItem, settingsWidget): @@ -73,6 +74,7 @@ class abstractImporter: def addSetting(self, name, type, label, widget=None, default=None, tooltip=None, min=None, max=None, vals=None, suffix=""): + self.settingsList.append(name) self.settings[name] = self.setting(name, type, label, widget, default, tooltip, min, max, vals, suffix) @@ -84,6 +86,15 @@ class abstractImporter: if name in self.settings: return self.settings[name] + def addSettingsTo(self, widget): + """ + Adds all the settings to the given widget. Assume that the settings + have not been called yet, so calling `.widget()` will create their + widgets. + """ + for name in self.settingsList: + self.settings[name].widget(widget) + class setting: """ diff --git a/manuskript/importer/folderImporter.py b/manuskript/importer/folderImporter.py index 4a3929c..6789dc2 100644 --- a/manuskript/importer/folderImporter.py +++ b/manuskript/importer/folderImporter.py @@ -24,7 +24,7 @@ class folderImporter(abstractImporter): Imports from a folder. """ ext = self.getSetting("ext").value() - ext = [e.strip().replace("*", "") for e in ext.split(",")] + ext = [e.strip().replace("*", "").lower() for e in ext.split(",")] sorting = self.getSetting("sortItems").value() @@ -42,11 +42,16 @@ class folderImporter(abstractImporter): 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) + if fExt.lower() in ext: + try: + with open(os.path.join(dirpath, f), "r") as fr: + content = fr.read() + child = outlineItem(title=fName, _type="md", parent=item) + child._data[Outline.text] = content + items.append(child) + except UnicodeDecodeError: + # Probably not a text file + pass def addFolder(d): child = outlineItem(title=d, parent=item) @@ -93,10 +98,11 @@ class folderImporter(abstractImporter): #group = cls.addPage(widget, "Folder import") self.addSetting("info", "label", - qApp.translate("Import", """Info: Imports a whole + qApp.translate("Import", """

    Info: Imports a whole directory structure. Folders are added as folders, and plaintext documents within (you chose which ones by extension) - are added as scene.
     """)) + are added as scene.

    +

    Only text files are supported (not images, binary or others).

    """)) self.addSetting("ext", "text", qApp.translate("Import", "Include only those extensions:"), @@ -111,8 +117,7 @@ class folderImporter(abstractImporter): qApp.translate("Import", "Import folder then files"), default=True), - for s in self.settings: - self.settings[s].widget(group) + self.addSettingsTo(group) return widget From ed864eab1ee6fc97b671ca8bab4abfae59146e37 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Tue, 14 Nov 2017 08:24:43 +0100 Subject: [PATCH 50/65] Fixes #213: Program Crash on Import with images --- manuskript/importer/abstractImporter.py | 11 +++++++++++ manuskript/importer/folderImporter.py | 25 +++++++++++++++---------- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/manuskript/importer/abstractImporter.py b/manuskript/importer/abstractImporter.py index 440068b..ba810af 100644 --- a/manuskript/importer/abstractImporter.py +++ b/manuskript/importer/abstractImporter.py @@ -27,6 +27,7 @@ class abstractImporter: engine = "Internal" def __init__(self): + self.settingsList = [] # Keep the name of the settings in order self.settings = {} def startImport(self, filePath, parentItem, settingsWidget): @@ -73,6 +74,7 @@ class abstractImporter: def addSetting(self, name, type, label, widget=None, default=None, tooltip=None, min=None, max=None, vals=None, suffix=""): + self.settingsList.append(name) self.settings[name] = self.setting(name, type, label, widget, default, tooltip, min, max, vals, suffix) @@ -84,6 +86,15 @@ class abstractImporter: if name in self.settings: return self.settings[name] + def addSettingsTo(self, widget): + """ + Adds all the settings to the given widget. Assume that the settings + have not been called yet, so calling `.widget()` will create their + widgets. + """ + for name in self.settingsList: + self.settings[name].widget(widget) + class setting: """ diff --git a/manuskript/importer/folderImporter.py b/manuskript/importer/folderImporter.py index 4a3929c..6789dc2 100644 --- a/manuskript/importer/folderImporter.py +++ b/manuskript/importer/folderImporter.py @@ -24,7 +24,7 @@ class folderImporter(abstractImporter): Imports from a folder. """ ext = self.getSetting("ext").value() - ext = [e.strip().replace("*", "") for e in ext.split(",")] + ext = [e.strip().replace("*", "").lower() for e in ext.split(",")] sorting = self.getSetting("sortItems").value() @@ -42,11 +42,16 @@ class folderImporter(abstractImporter): 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) + if fExt.lower() in ext: + try: + with open(os.path.join(dirpath, f), "r") as fr: + content = fr.read() + child = outlineItem(title=fName, _type="md", parent=item) + child._data[Outline.text] = content + items.append(child) + except UnicodeDecodeError: + # Probably not a text file + pass def addFolder(d): child = outlineItem(title=d, parent=item) @@ -93,10 +98,11 @@ class folderImporter(abstractImporter): #group = cls.addPage(widget, "Folder import") self.addSetting("info", "label", - qApp.translate("Import", """Info: Imports a whole + qApp.translate("Import", """

    Info: Imports a whole directory structure. Folders are added as folders, and plaintext documents within (you chose which ones by extension) - are added as scene.
     """)) + are added as scene.

    +

    Only text files are supported (not images, binary or others).

    """)) self.addSetting("ext", "text", qApp.translate("Import", "Include only those extensions:"), @@ -111,8 +117,7 @@ class folderImporter(abstractImporter): qApp.translate("Import", "Import folder then files"), default=True), - for s in self.settings: - self.settings[s].widget(group) + self.addSettingsTo(group) return widget From cc84f53f04e09d65cec96865054e7f1724c33f31 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Tue, 14 Nov 2017 09:42:49 +0100 Subject: [PATCH 51/65] Improves readabily of tree and card delegate --- manuskript/functions.py | 7 +++++-- manuskript/ui/style.py | 23 +++++++++++++++++------ manuskript/ui/views/corkDelegate.py | 17 +++++++++++++++-- manuskript/ui/views/treeDelegates.py | 15 +++++++++++---- 4 files changed, 48 insertions(+), 14 deletions(-) diff --git a/manuskript/functions.py b/manuskript/functions.py index 03e2a79..24652ba 100644 --- a/manuskript/functions.py +++ b/manuskript/functions.py @@ -153,6 +153,9 @@ def mixColors(col1, col2, f=.5): def outlineItemColors(item): + + from manuskript.ui import style as S + """Takes an OutlineItem and returns a dict of colors.""" colors = {} mw = mainWindow() @@ -184,9 +187,9 @@ def outlineItemColors(item): # Compile if item.compile() in [0, "0"]: - colors["Compile"] = QColor(Qt.gray) + colors["Compile"] = mixColors(QColor(S.text), QColor(S.window)) else: - colors["Compile"] = QColor(Qt.black) + colors["Compile"] = QColor(S.text) return colors diff --git a/manuskript/ui/style.py b/manuskript/ui/style.py index ad5c907..ba88fc6 100644 --- a/manuskript/ui/style.py +++ b/manuskript/ui/style.py @@ -9,8 +9,19 @@ from PyQt5.QtWidgets import qApp from manuskript import settings +# Loading palette colors. +# Manuskript as to restart to reload +p = qApp.palette() # window = "#d6d2d0" #"#eee" / #eff0f1 -window = qApp.palette().color(QPalette.Window).name() +window = p.color(QPalette.Window).name() # General background +windowText = p.color(QPalette.WindowText).name() # General foregroung +base = p.color(QPalette.Base).name() # Other background +text = p.color(QPalette.Text).name() # Base Text +brightText = p.color(QPalette.BrightText).name() # Contrast Text +button = p.color(QPalette.Window).name() # Button background +buttonText = p.color(QPalette.Window).name() # Button Text +highlight = p.color(QPalette.Highlight).name() # Other background +highlightedText = p.color(QPalette.HighlightedText).name() # Base Text bgHover = "#ccc" bgChecked = "#bbb" @@ -152,7 +163,7 @@ def mainEditorTabSS(): border: none; background: none; }} - + QScrollBar::sub-line:vertical {{ width:0; height: 0; @@ -240,8 +251,8 @@ def lineEditSS(): """.format(window=window, checked=bgChecked, blue=blue) - - + + def transparentSS(): return """ QTextEdit{ @@ -264,10 +275,10 @@ def simpleScrollBarV(): height: 0; border: none; background: none; - } + } QScrollBar::sub-line:vertical { width:0; height: 0; border: none; background: none; - }""" \ No newline at end of file + }""" diff --git a/manuskript/ui/views/corkDelegate.py b/manuskript/ui/views/corkDelegate.py index 6e6eee9..2bb2a4d 100644 --- a/manuskript/ui/views/corkDelegate.py +++ b/manuskript/ui/views/corkDelegate.py @@ -10,6 +10,7 @@ from manuskript.functions import colorifyPixmap from manuskript.functions import mainWindow from manuskript.functions import mixColors from manuskript.functions import outlineItemColors +from manuskript.ui import style as S class corkDelegate(QStyledItemDelegate): @@ -240,9 +241,11 @@ class corkDelegate(QStyledItemDelegate): if c == QColor(Qt.transparent): c = QColor(Qt.white) col = mixColors(c, QColor(Qt.white), .2) + backgroundColor = col p.setBrush(col) else: p.setBrush(Qt.white) + backgroundColor = QColor(Qt.white) p.setPen(Qt.NoPen) p.drawRect(self.cardRect) @@ -302,10 +305,20 @@ class corkDelegate(QStyledItemDelegate): if text: p.setPen(Qt.black) + textColor = QColor(Qt.black) if settings.viewSettings["Cork"]["Text"] != "Nothing": col = colors[settings.viewSettings["Cork"]["Text"]] if col == Qt.transparent: col = Qt.black + + # If title setting is compile, we have to hack the color + # Or we won't see anything in some themes + if settings.viewSettings["Cork"]["Text"] == "Compile": + if item.compile() in [0, "0"]: + col = mixColors(QColor(Qt.black), backgroundColor) + else: + col = Qt.black + textColor = col p.setPen(col) f = QFont(option.font) f.setPointSize(f.pointSize() + 4) @@ -358,7 +371,7 @@ class corkDelegate(QStyledItemDelegate): f = QFont(option.font) f.setBold(True) p.setFont(f) - p.setPen(Qt.black) + p.setPen(textColor) fm = QFontMetrics(f) elidedText = fm.elidedText(lineSummary, Qt.ElideRight, self.mainLineRect.width()) p.drawText(self.mainLineRect, Qt.AlignLeft | Qt.AlignVCenter, elidedText) @@ -368,7 +381,7 @@ class corkDelegate(QStyledItemDelegate): if fullSummary: p.save() p.setFont(option.font) - p.setPen(Qt.black) + p.setPen(textColor) p.drawText(self.mainTextRect, Qt.TextWordWrap, fullSummary) p.restore() diff --git a/manuskript/ui/views/treeDelegates.py b/manuskript/ui/views/treeDelegates.py index a9ee454..4921a7e 100644 --- a/manuskript/ui/views/treeDelegates.py +++ b/manuskript/ui/views/treeDelegates.py @@ -9,6 +9,7 @@ from manuskript.enums import Outline from manuskript.functions import mixColors, colorifyPixmap from manuskript.functions import outlineItemColors from manuskript.functions import toFloat +from manuskript.ui import style as S class treeTitleDelegate(QStyledItemDelegate): @@ -44,7 +45,7 @@ class treeTitleDelegate(QStyledItemDelegate): col = colors[settings.viewSettings["Tree"]["Background"]] if col != QColor(Qt.transparent): - col2 = QColor(Qt.white) + col2 = QColor(S.window) if opt.state & QStyle.State_Selected: col2 = opt.palette.brush(QPalette.Normal, QPalette.Highlight).color() col = mixColors(col, col2, .2) @@ -81,10 +82,13 @@ class treeTitleDelegate(QStyledItemDelegate): # Text if opt.text: painter.save() + if option.state & QStyle.State_Selected: + col = QColor(S.highlightedText) + painter.setPen(col) if settings.viewSettings["Tree"]["Text"] != "Nothing": col = colors[settings.viewSettings["Tree"]["Text"]] if col == Qt.transparent: - col = Qt.black + col = QColor(S.text) painter.setPen(col) f = QFont(opt.font) painter.setFont(f) @@ -131,8 +135,11 @@ class treeTitleDelegate(QStyledItemDelegate): f = painter.font() f.setWeight(QFont.Normal) painter.setFont(f) - painter.setPen(Qt.darkGray) - painter.drawText(r, Qt.AlignLeft | Qt.AlignBottom, extraText) + col = mixColors( + QColor(S.window), + QColor(S.text)) + painter.setPen(col) #Qt.darkGray + painter.drawText(r, Qt.AlignLeft | Qt.AlignVCenter, extraText) painter.restore() painter.restore() From 0c3f96014abe1716fb757a129ed30e3f09e30db3 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Tue, 14 Nov 2017 10:00:35 +0100 Subject: [PATCH 52/65] Improves readabily of outline delegate --- manuskript/functions.py | 2 +- manuskript/ui/views/outlineDelegates.py | 16 +++++++++++++--- manuskript/ui/views/treeDelegates.py | 9 ++++++++- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/manuskript/functions.py b/manuskript/functions.py index 24652ba..cdcd8ca 100644 --- a/manuskript/functions.py +++ b/manuskript/functions.py @@ -189,7 +189,7 @@ def outlineItemColors(item): if item.compile() in [0, "0"]: colors["Compile"] = mixColors(QColor(S.text), QColor(S.window)) else: - colors["Compile"] = QColor(S.text) + colors["Compile"] = QColor(Qt.transparent) # will use default return colors diff --git a/manuskript/ui/views/outlineDelegates.py b/manuskript/ui/views/outlineDelegates.py index 818718d..215f99a 100644 --- a/manuskript/ui/views/outlineDelegates.py +++ b/manuskript/ui/views/outlineDelegates.py @@ -8,7 +8,7 @@ from PyQt5.QtWidgets import qApp from manuskript import settings from manuskript.enums import Character, Outline from manuskript.functions import outlineItemColors, mixColors, colorifyPixmap, toInt, toFloat, drawProgress - +from manuskript.ui import style as S class outlineTitleDelegate(QStyledItemDelegate): def __init__(self, parent=None): @@ -39,7 +39,7 @@ class outlineTitleDelegate(QStyledItemDelegate): col = colors[settings.viewSettings["Outline"]["Background"]] if col != QColor(Qt.transparent): - col2 = QColor(Qt.white) + col2 = QColor(S.base) if opt.state & QStyle.State_Selected: col2 = opt.palette.brush(QPalette.Normal, QPalette.Highlight).color() col = mixColors(col, col2, .2) @@ -76,10 +76,20 @@ class outlineTitleDelegate(QStyledItemDelegate): # Text if opt.text: painter.save() + textColor = QColor(S.text) + if option.state & QStyle.State_Selected: + col = QColor(S.highlightedText) + textColor = col + painter.setPen(col) if settings.viewSettings["Outline"]["Text"] != "Nothing": col = colors[settings.viewSettings["Outline"]["Text"]] if col == Qt.transparent: - col = Qt.black + col = textColor + # If text color is Compile and item is selected, we have + # to change the color + if settings.viewSettings["Outline"]["Text"] == "Compile" and \ + item.compile() in [0, "0"]: + col = mixColors(textColor, QColor(S.window)) painter.setPen(col) f = QFont(opt.font) painter.setFont(f) diff --git a/manuskript/ui/views/treeDelegates.py b/manuskript/ui/views/treeDelegates.py index 4921a7e..6ed7a3b 100644 --- a/manuskript/ui/views/treeDelegates.py +++ b/manuskript/ui/views/treeDelegates.py @@ -82,13 +82,20 @@ class treeTitleDelegate(QStyledItemDelegate): # Text if opt.text: painter.save() + textColor = QColor(S.text) if option.state & QStyle.State_Selected: col = QColor(S.highlightedText) + textColor = col painter.setPen(col) if settings.viewSettings["Tree"]["Text"] != "Nothing": col = colors[settings.viewSettings["Tree"]["Text"]] if col == Qt.transparent: - col = QColor(S.text) + col = textColor + # If text color is Compile and item is selected, we have + # to change the color + if settings.viewSettings["Outline"]["Text"] == "Compile" and \ + item.compile() in [0, "0"]: + col = mixColors(textColor, QColor(S.window)) painter.setPen(col) f = QFont(opt.font) painter.setFont(f) From 845aa8b205567a425d0e449d6117dc53651bf47c Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Tue, 14 Nov 2017 10:12:07 +0100 Subject: [PATCH 53/65] Improves colors of index card delegate editor --- manuskript/ui/views/corkDelegate.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/manuskript/ui/views/corkDelegate.py b/manuskript/ui/views/corkDelegate.py index 2bb2a4d..d3908eb 100644 --- a/manuskript/ui/views/corkDelegate.py +++ b/manuskript/ui/views/corkDelegate.py @@ -21,6 +21,8 @@ class corkDelegate(QStyledItemDelegate): self.editing = None self.margin = 5 + self.bgColors = {} + def newStyle(self): return settings.corkStyle == "new" @@ -43,6 +45,8 @@ class corkDelegate(QStyledItemDelegate): def createEditor(self, parent, option, index): self.updateRects(option, index) + bgColor = self.bgColors.get(index, "white") + if self.mainLineRect.contains(self.lastPos): # One line summary self.editing = Outline.summarySentence @@ -57,6 +61,7 @@ class corkDelegate(QStyledItemDelegate): edt.setAlignment(Qt.AlignCenter) edt.setPlaceholderText(self.tr("One line summary")) edt.setFont(f) + edt.setStyleSheet("background: {}; color: black;".format(bgColor)) return edt elif self.titleRect.contains(self.lastPos): @@ -72,6 +77,7 @@ class corkDelegate(QStyledItemDelegate): edt.setAlignment(Qt.AlignCenter) f.setBold(True) edt.setFont(f) + edt.setStyleSheet("background: {}; color: black;".format(bgColor)) # edt.setGeometry(self.titleRect) return edt @@ -86,6 +92,7 @@ class corkDelegate(QStyledItemDelegate): edt.setPlaceholderText(self.tr("Full summary")) except AttributeError: pass + edt.setStyleSheet("background: {}; color: black;".format(bgColor)) return edt def updateEditorGeometry(self, editor, option, index): @@ -247,6 +254,9 @@ class corkDelegate(QStyledItemDelegate): p.setBrush(Qt.white) backgroundColor = QColor(Qt.white) + # Cache background color + self.bgColors[index] = backgroundColor.name() + p.setPen(Qt.NoPen) p.drawRect(self.cardRect) if item.isFolder(): From 2109f88816d89aefb426af809bad4ef8b56eaa20 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Tue, 14 Nov 2017 10:36:57 +0100 Subject: [PATCH 54/65] Improves colors of goal progress bar --- manuskript/functions.py | 3 ++- manuskript/ui/editors/mainEditor.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/manuskript/functions.py b/manuskript/functions.py index cdcd8ca..8931cc6 100644 --- a/manuskript/functions.py +++ b/manuskript/functions.py @@ -47,8 +47,9 @@ def toString(text): def drawProgress(painter, rect, progress, radius=0): + from manuskript.ui import style as S painter.setPen(Qt.NoPen) - painter.setBrush(QColor("#dddddd")) + painter.setBrush(QColor(S.base)) # "#dddddd" painter.drawRoundedRect(rect, radius, radius) painter.setBrush(QBrush(colorFromProgress(progress))) diff --git a/manuskript/ui/editors/mainEditor.py b/manuskript/ui/editors/mainEditor.py index 606c466..d410e28 100644 --- a/manuskript/ui/editors/mainEditor.py +++ b/manuskript/ui/editors/mainEditor.py @@ -317,12 +317,12 @@ class mainEditor(QWidget, Ui_mainEditor): drawProgress(p, rect, progress, 2) del p self.lblRedacProgress.setPixmap(self.px) - self.lblRedacWC.setText(self.tr("{} words / {}").format( + self.lblRedacWC.setText(self.tr("{} words / {} ").format( locale.format("%d", wc, grouping=True), locale.format("%d", goal, grouping=True))) else: self.lblRedacProgress.hide() - self.lblRedacWC.setText(self.tr("{} words").format( + self.lblRedacWC.setText(self.tr("{} words ").format( locale.format("%d", wc, grouping=True))) ############################################################################### From b0af99eddcaf950e631a7d58b15f2c6e2275bf00 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Tue, 14 Nov 2017 10:50:32 +0100 Subject: [PATCH 55/65] Adds default system style to text editor --- manuskript/mainWindow.py | 1 + manuskript/settings.py | 19 +- manuskript/settingsWindow.py | 19 +- manuskript/ui/settings_ui.py | 244 +++++----- manuskript/ui/settings_ui.ui | 865 ++++++++++++++++++----------------- manuskript/ui/welcome.py | 2 + 6 files changed, 620 insertions(+), 530 deletions(-) diff --git a/manuskript/mainWindow.py b/manuskript/mainWindow.py index 61773d9..e1fce64 100644 --- a/manuskript/mainWindow.py +++ b/manuskript/mainWindow.py @@ -498,6 +498,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): if loadFromFile: # Load empty settings imp.reload(settings) + settings.initDefaultValues() # Load data self.loadEmptyDatas() diff --git a/manuskript/settings.py b/manuskript/settings.py index 5cb1bb4..e387e35 100644 --- a/manuskript/settings.py +++ b/manuskript/settings.py @@ -58,8 +58,8 @@ defaultTextType = "md" fullScreenTheme = "spacedreams" textEditor = { - "background": "#fff", - "fontColor": "#000", + "background": "", + "fontColor": "", "font": qApp.font().toString(), "misspelled": "#F00", "lineSpacing": 100, @@ -73,6 +73,7 @@ textEditor = { "maxWidth": 0, "marginsLR": 0, "marginsTB": 0, + "backgroundTransparent": False, } revisions = { @@ -98,6 +99,19 @@ viewMode = "fiction" # simple, fiction saveToZip = True dontShowDeleteWarning = False +def initDefaultValues(): + """ + Load some default values based on system's settings. + Is called anytime we open/create a project. + """ + global textEditor + if not textEditor["background"]: + from manuskript.ui import style as S + textEditor["background"] = S.base + if not textEditor["fontColor"]: + from manuskript.ui import style as S + textEditor["fontColor"] = S.text + def save(filename=None, protocol=None): global spellcheck, dict, corkSliderFactor, viewSettings, corkSizeFactor, folderView, lastTab, openIndexes, \ @@ -256,6 +270,7 @@ def load(string, fromString=False, protocol=None): "maxWidth": 0, "marginsLR": 0, "marginsTB": 0, + "backgroundTransparent": False, # Added in 0.6.0 } for k in added: diff --git a/manuskript/settingsWindow.py b/manuskript/settingsWindow.py index c505de4..05de838 100644 --- a/manuskript/settingsWindow.py +++ b/manuskript/settingsWindow.py @@ -23,6 +23,7 @@ from manuskript.ui.settings_ui import Ui_Settings from manuskript.ui.views.outlineView import outlineView from manuskript.ui.views.textEditView import textEditView from manuskript.ui.welcome import welcome +from manuskript.ui import style as S try: import enchant @@ -164,6 +165,9 @@ class settingsWindow(QWidget, Ui_Settings): self.btnEditorMisspelledColor.clicked.connect(self.choseEditorMisspelledColor) self.setButtonColor(self.btnEditorBackgroundColor, opt["background"]) self.btnEditorBackgroundColor.clicked.connect(self.choseEditorBackgroundColor) + self.chkEditorBackgroundTransparent.setChecked(opt["backgroundTransparent"]) + self.chkEditorBackgroundTransparent.stateChanged.connect(self.updateEditorSettings) + self.btnEditorColorDefault.clicked.connect(self.restoreEditorColors) f = QFont() f.fromString(opt["font"]) self.cmbEditorFontFamily.setCurrentFont(f) @@ -442,7 +446,13 @@ class settingsWindow(QWidget, Ui_Settings): #################################################################################################### def updateEditorSettings(self): - # Store settings + """ + Stores settings for editor appareance. + """ + + # Background + settings.textEditor["backgroundTransparent"] = True if self.chkEditorBackgroundTransparent.checkState() else False + # Font f = self.cmbEditorFontFamily.currentFont() f.setPointSize(self.spnEditorFontSize.value()) @@ -526,6 +536,13 @@ class settingsWindow(QWidget, Ui_Settings): self.setButtonColor(self.btnEditorBackgroundColor, color.name()) self.updateEditorSettings() + def restoreEditorColors(self): + settings.textEditor["background"] = S.base + self.setButtonColor(self.btnEditorBackgroundColor, S.base) + settings.textEditor["fontColor"] = S.text + self.setButtonColor(self.btnEditorFontColor, S.text) + self.updateEditorSettings() + #################################################################################################### # STATUS # #################################################################################################### diff --git a/manuskript/ui/settings_ui.py b/manuskript/ui/settings_ui.py index 62159dd..ab72553 100644 --- a/manuskript/ui/settings_ui.py +++ b/manuskript/ui/settings_ui.py @@ -11,7 +11,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets class Ui_Settings(object): def setupUi(self, Settings): Settings.setObjectName("Settings") - Settings.resize(658, 530) + Settings.resize(658, 632) self.horizontalLayout_8 = QtWidgets.QHBoxLayout(Settings) self.horizontalLayout_8.setObjectName("horizontalLayout_8") self.lstMenu = QtWidgets.QListWidget(Settings) @@ -953,28 +953,53 @@ class Ui_Settings(object): self.tabViews.addTab(self.tab_3, icon, "") self.tab_4 = QtWidgets.QWidget() self.tab_4.setObjectName("tab_4") - self.verticalLayout_22 = QtWidgets.QVBoxLayout(self.tab_4) - self.verticalLayout_22.setObjectName("verticalLayout_22") - self.horizontalLayout_4 = QtWidgets.QHBoxLayout() + self.horizontalLayout_4 = QtWidgets.QHBoxLayout(self.tab_4) self.horizontalLayout_4.setObjectName("horizontalLayout_4") self.verticalLayout_21 = QtWidgets.QVBoxLayout() self.verticalLayout_21.setObjectName("verticalLayout_21") - self.groupBox_12 = QtWidgets.QGroupBox(self.tab_4) + self.groupBox_17 = QtWidgets.QGroupBox(self.tab_4) font = QtGui.QFont() font.setBold(True) font.setWeight(75) - self.groupBox_12.setFont(font) - self.groupBox_12.setObjectName("groupBox_12") - self.formLayout_8 = QtWidgets.QFormLayout(self.groupBox_12) - self.formLayout_8.setObjectName("formLayout_8") - self.label_37 = QtWidgets.QLabel(self.groupBox_12) + self.groupBox_17.setFont(font) + self.groupBox_17.setObjectName("groupBox_17") + self.formLayout_12 = QtWidgets.QFormLayout(self.groupBox_17) + self.formLayout_12.setObjectName("formLayout_12") + self.label_43 = QtWidgets.QLabel(self.groupBox_17) + font = QtGui.QFont() + font.setBold(False) + font.setWeight(50) + self.label_43.setFont(font) + self.label_43.setObjectName("label_43") + self.formLayout_12.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.label_43) + self.btnEditorBackgroundColor = QtWidgets.QPushButton(self.groupBox_17) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.btnEditorBackgroundColor.sizePolicy().hasHeightForWidth()) + self.btnEditorBackgroundColor.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setBold(False) + font.setWeight(50) + self.btnEditorBackgroundColor.setFont(font) + self.btnEditorBackgroundColor.setText("") + self.btnEditorBackgroundColor.setObjectName("btnEditorBackgroundColor") + self.formLayout_12.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.btnEditorBackgroundColor) + self.chkEditorBackgroundTransparent = QtWidgets.QCheckBox(self.groupBox_17) + font = QtGui.QFont() + font.setBold(False) + font.setWeight(50) + self.chkEditorBackgroundTransparent.setFont(font) + self.chkEditorBackgroundTransparent.setObjectName("chkEditorBackgroundTransparent") + self.formLayout_12.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.chkEditorBackgroundTransparent) + self.label_37 = QtWidgets.QLabel(self.groupBox_17) font = QtGui.QFont() font.setBold(False) font.setWeight(50) self.label_37.setFont(font) self.label_37.setObjectName("label_37") - self.formLayout_8.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.label_37) - self.btnEditorFontColor = QtWidgets.QPushButton(self.groupBox_12) + self.formLayout_12.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.label_37) + self.btnEditorFontColor = QtWidgets.QPushButton(self.groupBox_17) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -986,14 +1011,30 @@ class Ui_Settings(object): self.btnEditorFontColor.setFont(font) self.btnEditorFontColor.setText("") self.btnEditorFontColor.setObjectName("btnEditorFontColor") - self.formLayout_8.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.btnEditorFontColor) + self.formLayout_12.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.btnEditorFontColor) + self.btnEditorColorDefault = QtWidgets.QPushButton(self.groupBox_17) + font = QtGui.QFont() + font.setBold(False) + font.setWeight(50) + self.btnEditorColorDefault.setFont(font) + self.btnEditorColorDefault.setObjectName("btnEditorColorDefault") + self.formLayout_12.setWidget(3, QtWidgets.QFormLayout.SpanningRole, self.btnEditorColorDefault) + self.verticalLayout_21.addWidget(self.groupBox_17) + self.groupBox_12 = QtWidgets.QGroupBox(self.tab_4) + font = QtGui.QFont() + font.setBold(True) + font.setWeight(75) + self.groupBox_12.setFont(font) + self.groupBox_12.setObjectName("groupBox_12") + self.formLayout_8 = QtWidgets.QFormLayout(self.groupBox_12) + self.formLayout_8.setObjectName("formLayout_8") self.label_39 = QtWidgets.QLabel(self.groupBox_12) font = QtGui.QFont() font.setBold(False) font.setWeight(50) self.label_39.setFont(font) self.label_39.setObjectName("label_39") - self.formLayout_8.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.label_39) + self.formLayout_8.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.label_39) self.cmbEditorFontFamily = QtWidgets.QFontComboBox(self.groupBox_12) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) @@ -1006,21 +1047,21 @@ class Ui_Settings(object): font.setWeight(50) self.cmbEditorFontFamily.setFont(font) self.cmbEditorFontFamily.setObjectName("cmbEditorFontFamily") - self.formLayout_8.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.cmbEditorFontFamily) + self.formLayout_8.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.cmbEditorFontFamily) self.label_38 = QtWidgets.QLabel(self.groupBox_12) font = QtGui.QFont() font.setBold(False) font.setWeight(50) self.label_38.setFont(font) self.label_38.setObjectName("label_38") - self.formLayout_8.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.label_38) + self.formLayout_8.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.label_38) self.label_36 = QtWidgets.QLabel(self.groupBox_12) font = QtGui.QFont() font.setBold(False) font.setWeight(50) self.label_36.setFont(font) self.label_36.setObjectName("label_36") - self.formLayout_8.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.label_36) + self.formLayout_8.setWidget(5, QtWidgets.QFormLayout.LabelRole, self.label_36) self.btnEditorMisspelledColor = QtWidgets.QPushButton(self.groupBox_12) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) @@ -1033,7 +1074,7 @@ class Ui_Settings(object): self.btnEditorMisspelledColor.setFont(font) self.btnEditorMisspelledColor.setText("") self.btnEditorMisspelledColor.setObjectName("btnEditorMisspelledColor") - self.formLayout_8.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.btnEditorMisspelledColor) + self.formLayout_8.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.btnEditorMisspelledColor) self.spnEditorFontSize = QtWidgets.QSpinBox(self.groupBox_12) font = QtGui.QFont() font.setBold(False) @@ -1043,61 +1084,8 @@ class Ui_Settings(object): self.spnEditorFontSize.setMaximum(299) self.spnEditorFontSize.setProperty("value", 10) self.spnEditorFontSize.setObjectName("spnEditorFontSize") - self.formLayout_8.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.spnEditorFontSize) - self.label_43 = QtWidgets.QLabel(self.groupBox_12) - font = QtGui.QFont() - font.setBold(False) - font.setWeight(50) - self.label_43.setFont(font) - self.label_43.setObjectName("label_43") - self.formLayout_8.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.label_43) - self.btnEditorBackgroundColor = QtWidgets.QPushButton(self.groupBox_12) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.btnEditorBackgroundColor.sizePolicy().hasHeightForWidth()) - self.btnEditorBackgroundColor.setSizePolicy(sizePolicy) - font = QtGui.QFont() - font.setBold(False) - font.setWeight(50) - self.btnEditorBackgroundColor.setFont(font) - self.btnEditorBackgroundColor.setText("") - self.btnEditorBackgroundColor.setObjectName("btnEditorBackgroundColor") - self.formLayout_8.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.btnEditorBackgroundColor) + self.formLayout_8.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.spnEditorFontSize) self.verticalLayout_21.addWidget(self.groupBox_12) - self.groupBox_15 = QtWidgets.QGroupBox(self.tab_4) - font = QtGui.QFont() - font.setBold(True) - font.setWeight(75) - self.groupBox_15.setFont(font) - self.groupBox_15.setObjectName("groupBox_15") - self.formLayout_10 = QtWidgets.QFormLayout(self.groupBox_15) - self.formLayout_10.setObjectName("formLayout_10") - self.chkEditorCursorWidth = QtWidgets.QCheckBox(self.groupBox_15) - font = QtGui.QFont() - font.setBold(False) - font.setWeight(50) - self.chkEditorCursorWidth.setFont(font) - self.chkEditorCursorWidth.setObjectName("chkEditorCursorWidth") - self.formLayout_10.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.chkEditorCursorWidth) - self.spnEditorCursorWidth = QtWidgets.QSpinBox(self.groupBox_15) - font = QtGui.QFont() - font.setBold(False) - font.setWeight(50) - self.spnEditorCursorWidth.setFont(font) - self.spnEditorCursorWidth.setMinimum(0) - self.spnEditorCursorWidth.setMaximum(99) - self.spnEditorCursorWidth.setProperty("value", 9) - self.spnEditorCursorWidth.setObjectName("spnEditorCursorWidth") - self.formLayout_10.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.spnEditorCursorWidth) - self.chkEditorNoBlinking = QtWidgets.QCheckBox(self.groupBox_15) - font = QtGui.QFont() - font.setBold(False) - font.setWeight(50) - self.chkEditorNoBlinking.setFont(font) - self.chkEditorNoBlinking.setObjectName("chkEditorNoBlinking") - self.formLayout_10.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.chkEditorNoBlinking) - self.verticalLayout_21.addWidget(self.groupBox_15) self.groupBox_161 = QtWidgets.QGroupBox(self.tab_4) font = QtGui.QFont() font.setBold(True) @@ -1154,6 +1142,8 @@ class Ui_Settings(object): self.formLayout_11.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.spnEditorMarginsTB) self.verticalLayout_21.addWidget(self.groupBox_161) self.horizontalLayout_4.addLayout(self.verticalLayout_21) + self.verticalLayout_22 = QtWidgets.QVBoxLayout() + self.verticalLayout_22.setObjectName("verticalLayout_22") self.groupBox_13 = QtWidgets.QGroupBox(self.tab_4) font = QtGui.QFont() font.setBold(True) @@ -1162,6 +1152,28 @@ class Ui_Settings(object): self.groupBox_13.setObjectName("groupBox_13") self.formLayout_9 = QtWidgets.QFormLayout(self.groupBox_13) self.formLayout_9.setObjectName("formLayout_9") + self.label_35 = QtWidgets.QLabel(self.groupBox_13) + font = QtGui.QFont() + font.setBold(False) + font.setWeight(50) + self.label_35.setFont(font) + self.label_35.setObjectName("label_35") + self.formLayout_9.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.label_35) + self.cmbEditorAlignment = QtWidgets.QComboBox(self.groupBox_13) + font = QtGui.QFont() + font.setBold(False) + font.setWeight(50) + self.cmbEditorAlignment.setFont(font) + self.cmbEditorAlignment.setObjectName("cmbEditorAlignment") + icon = QtGui.QIcon.fromTheme("format-justify-left") + self.cmbEditorAlignment.addItem(icon, "") + icon = QtGui.QIcon.fromTheme("format-justify-center") + self.cmbEditorAlignment.addItem(icon, "") + icon = QtGui.QIcon.fromTheme("format-justify-right") + self.cmbEditorAlignment.addItem(icon, "") + icon = QtGui.QIcon.fromTheme("format-justify-fill") + self.cmbEditorAlignment.addItem(icon, "") + self.formLayout_9.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.cmbEditorAlignment) self.label_40 = QtWidgets.QLabel(self.groupBox_13) font = QtGui.QFont() font.setBold(False) @@ -1267,30 +1279,41 @@ class Ui_Settings(object): self.spnEditorParaBelow.setProperty("value", 5) self.spnEditorParaBelow.setObjectName("spnEditorParaBelow") self.formLayout_9.setWidget(6, QtWidgets.QFormLayout.FieldRole, self.spnEditorParaBelow) - self.label_35 = QtWidgets.QLabel(self.groupBox_13) + self.verticalLayout_22.addWidget(self.groupBox_13) + self.groupBox_15 = QtWidgets.QGroupBox(self.tab_4) + font = QtGui.QFont() + font.setBold(True) + font.setWeight(75) + self.groupBox_15.setFont(font) + self.groupBox_15.setObjectName("groupBox_15") + self.formLayout_10 = QtWidgets.QFormLayout(self.groupBox_15) + self.formLayout_10.setObjectName("formLayout_10") + self.chkEditorCursorWidth = QtWidgets.QCheckBox(self.groupBox_15) font = QtGui.QFont() font.setBold(False) font.setWeight(50) - self.label_35.setFont(font) - self.label_35.setObjectName("label_35") - self.formLayout_9.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.label_35) - self.cmbEditorAlignment = QtWidgets.QComboBox(self.groupBox_13) + self.chkEditorCursorWidth.setFont(font) + self.chkEditorCursorWidth.setObjectName("chkEditorCursorWidth") + self.formLayout_10.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.chkEditorCursorWidth) + self.spnEditorCursorWidth = QtWidgets.QSpinBox(self.groupBox_15) font = QtGui.QFont() font.setBold(False) font.setWeight(50) - self.cmbEditorAlignment.setFont(font) - self.cmbEditorAlignment.setObjectName("cmbEditorAlignment") - icon = QtGui.QIcon.fromTheme("format-justify-left") - self.cmbEditorAlignment.addItem(icon, "") - icon = QtGui.QIcon.fromTheme("format-justify-center") - self.cmbEditorAlignment.addItem(icon, "") - icon = QtGui.QIcon.fromTheme("format-justify-right") - self.cmbEditorAlignment.addItem(icon, "") - icon = QtGui.QIcon.fromTheme("format-justify-fill") - self.cmbEditorAlignment.addItem(icon, "") - self.formLayout_9.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.cmbEditorAlignment) - self.horizontalLayout_4.addWidget(self.groupBox_13) - self.verticalLayout_22.addLayout(self.horizontalLayout_4) + self.spnEditorCursorWidth.setFont(font) + self.spnEditorCursorWidth.setMinimum(0) + self.spnEditorCursorWidth.setMaximum(99) + self.spnEditorCursorWidth.setProperty("value", 9) + self.spnEditorCursorWidth.setObjectName("spnEditorCursorWidth") + self.formLayout_10.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.spnEditorCursorWidth) + self.chkEditorNoBlinking = QtWidgets.QCheckBox(self.groupBox_15) + font = QtGui.QFont() + font.setBold(False) + font.setWeight(50) + self.chkEditorNoBlinking.setFont(font) + self.chkEditorNoBlinking.setObjectName("chkEditorNoBlinking") + self.formLayout_10.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.chkEditorNoBlinking) + self.verticalLayout_22.addWidget(self.groupBox_15) + self.horizontalLayout_4.addLayout(self.verticalLayout_22) icon = QtGui.QIcon.fromTheme("view-text") self.tabViews.addTab(self.tab_4, icon, "") self.verticalLayout_9.addWidget(self.tabViews) @@ -1798,7 +1821,7 @@ class Ui_Settings(object): self.retranslateUi(Settings) self.stack.setCurrentIndex(2) - self.tabViews.setCurrentIndex(0) + self.tabViews.setCurrentIndex(3) self.themeStack.setCurrentIndex(1) self.themeEditStack.setCurrentIndex(3) self.lstMenu.currentRowChanged['int'].connect(self.stack.setCurrentIndex) @@ -1873,13 +1896,13 @@ class Ui_Settings(object): self.lblTreeIconSize.setText(_translate("Settings", "TextLabel")) self.groupBox_8.setTitle(_translate("Settings", "Folders")) self.rdoTreeItemCount.setText(_translate("Settings", "Show ite&m count")) - self.rdoTreeWC.setText(_translate("Settings", "Show wordcount")) - self.rdoTreeProgress.setText(_translate("Settings", "Show progress")) - self.rdoTreeSummary.setText(_translate("Settings", "Show summary")) + self.rdoTreeWC.setText(_translate("Settings", "Show &wordcount")) + self.rdoTreeProgress.setText(_translate("Settings", "S&how progress")) + self.rdoTreeSummary.setText(_translate("Settings", "Show summar&y")) self.rdoTreeNothing.setText(_translate("Settings", "&Nothing")) self.groupBox_9.setTitle(_translate("Settings", "Text")) - self.rdoTreeTextWC.setText(_translate("Settings", "Show wordcount")) - self.rdoTreeTextProgress.setText(_translate("Settings", "Show progress")) + self.rdoTreeTextWC.setText(_translate("Settings", "&Show wordcount")) + self.rdoTreeTextProgress.setText(_translate("Settings", "Show p&rogress")) self.rdoTreeTextSummary.setText(_translate("Settings", "Show summary")) self.rdoTreeTextNothing.setText(_translate("Settings", "Nothing")) self.tabViews.setTabText(self.tabViews.indexOf(self.tab), _translate("Settings", "Tree")) @@ -1917,8 +1940,8 @@ class Ui_Settings(object): self.btnCorkColor.setShortcut(_translate("Settings", "Ctrl+S")) self.label_16.setText(_translate("Settings", "Image:")) self.groupBox_11.setTitle(_translate("Settings", "Style")) - self.rdoCorkOldStyle.setText(_translate("Settings", "Old style")) - self.rdoCorkNewStyle.setText(_translate("Settings", "New style")) + self.rdoCorkOldStyle.setText(_translate("Settings", "Old st&yle")) + self.rdoCorkNewStyle.setText(_translate("Settings", "Ne&w style")) self.groupBox_5.setTitle(_translate("Settings", "Item colors")) self.label_9.setText(_translate("Settings", "Icon color:")) self.cmbCorkIcon.setItemText(0, _translate("Settings", "Nothing")) @@ -1951,16 +1974,15 @@ class Ui_Settings(object): self.cmbCorkCorner.setItemText(3, _translate("Settings", "Progress")) self.cmbCorkCorner.setItemText(4, _translate("Settings", "Compile")) self.tabViews.setTabText(self.tabViews.indexOf(self.tab_3), _translate("Settings", "Index cards")) - self.groupBox_12.setTitle(_translate("Settings", "Font")) + self.groupBox_17.setTitle(_translate("Settings", "Colors")) + self.label_43.setText(_translate("Settings", "Background:")) + self.chkEditorBackgroundTransparent.setText(_translate("Settings", "Transparent")) self.label_37.setText(_translate("Settings", "Color:")) + self.btnEditorColorDefault.setText(_translate("Settings", "Restore defaults")) + self.groupBox_12.setTitle(_translate("Settings", "Font")) self.label_39.setText(_translate("Settings", "Family:")) self.label_38.setText(_translate("Settings", "Size:")) self.label_36.setText(_translate("Settings", "Misspelled:")) - self.label_43.setText(_translate("Settings", "Background:")) - self.groupBox_15.setTitle(_translate("Settings", "Cursor")) - self.chkEditorCursorWidth.setText(_translate("Settings", "Use block insertion of")) - self.spnEditorCursorWidth.setSuffix(_translate("Settings", " px")) - self.chkEditorNoBlinking.setText(_translate("Settings", "Disable blinking")) self.groupBox_161.setTitle(_translate("Settings", "Text area")) self.chkEditorMaxWidth.setText(_translate("Settings", "Max width")) self.spnEditorMaxWidth.setSuffix(_translate("Settings", " px")) @@ -1969,6 +1991,11 @@ class Ui_Settings(object): self.label_55.setText(_translate("Settings", "Top/Bottom margins:")) self.spnEditorMarginsTB.setSuffix(_translate("Settings", " px")) self.groupBox_13.setTitle(_translate("Settings", "Paragraphs")) + self.label_35.setText(_translate("Settings", "Alignment:")) + self.cmbEditorAlignment.setItemText(0, _translate("Settings", "Left")) + self.cmbEditorAlignment.setItemText(1, _translate("Settings", "Center")) + self.cmbEditorAlignment.setItemText(2, _translate("Settings", "Right")) + self.cmbEditorAlignment.setItemText(3, _translate("Settings", "Justify")) self.label_40.setText(_translate("Settings", "Line spacing:")) self.cmbEditorLineSpacing.setItemText(0, _translate("Settings", "Single")) self.cmbEditorLineSpacing.setItemText(1, _translate("Settings", "1.5 lines")) @@ -1981,11 +2008,10 @@ class Ui_Settings(object): self.label_41.setText(_translate("Settings", "Spacing:")) self.spnEditorParaAbove.setSuffix(_translate("Settings", " px")) self.spnEditorParaBelow.setSuffix(_translate("Settings", " px")) - self.label_35.setText(_translate("Settings", "Alignment:")) - self.cmbEditorAlignment.setItemText(0, _translate("Settings", "Left")) - self.cmbEditorAlignment.setItemText(1, _translate("Settings", "Center")) - self.cmbEditorAlignment.setItemText(2, _translate("Settings", "Right")) - self.cmbEditorAlignment.setItemText(3, _translate("Settings", "Justify")) + self.groupBox_15.setTitle(_translate("Settings", "Cursor")) + self.chkEditorCursorWidth.setText(_translate("Settings", "Use block insertion of")) + self.spnEditorCursorWidth.setSuffix(_translate("Settings", " px")) + self.chkEditorNoBlinking.setText(_translate("Settings", "Disable blinking")) self.tabViews.setTabText(self.tabViews.indexOf(self.tab_4), _translate("Settings", "Text editor")) self.lblTitleLabels.setText(_translate("Settings", "Labels")) self.btnLabelColor.setShortcut(_translate("Settings", "Ctrl+S")) diff --git a/manuskript/ui/settings_ui.ui b/manuskript/ui/settings_ui.ui index e22e6f3..ce00565 100644 --- a/manuskript/ui/settings_ui.ui +++ b/manuskript/ui/settings_ui.ui @@ -7,7 +7,7 @@ 0 0 658 - 530 + 632 @@ -795,13 +795,12 @@ text-align:center; - 0 + 3 - - + .. Tree @@ -1070,7 +1069,7 @@ text-align:center; - Show wordcount + Show &wordcount @@ -1083,7 +1082,7 @@ text-align:center; - Show progress + S&how progress @@ -1096,7 +1095,7 @@ text-align:center; - Show summary + Show summar&y @@ -1140,7 +1139,7 @@ text-align:center; - Show wordcount + &Show wordcount @@ -1153,7 +1152,7 @@ text-align:center; - Show progress + Show p&rogress @@ -1225,8 +1224,7 @@ text-align:center; - - + .. Outline @@ -1573,8 +1571,7 @@ text-align:center; - - + .. Index cards @@ -1693,7 +1690,7 @@ text-align:center; - Old style + Old st&yle @@ -1706,7 +1703,7 @@ text-align:center; - New style + Ne&w style @@ -1976,365 +1973,342 @@ text-align:center; - - + .. Text editor - + - + - - - - - - 75 - true - - - - Font - - - - - - - 50 - false - - - - Color: - - - - - - - - 0 - 0 - - - - - 50 - false - - - - - - - - - - - - 50 - false - - - - Family: - - - - - - - - 0 - 0 - - - - - 150 - 16777215 - - - - - 50 - false - - - - - - - - - 50 - false - - - - Size: - - - - - - - - 50 - false - - - - Misspelled: - - - - - - - - 0 - 0 - - - - - 50 - false - - - - - - - - - - - - 50 - false - - - - 4 - - - 299 - - - 10 - - - - - - - - 50 - false - - - - Background: - - - - - - - - 0 - 0 - - - - - 50 - false - - - - - - - - - - - - - - - 75 - true - - - - Cursor - - - - - - - 50 - false - - - - Use block insertion of - - - - - - - - 50 - false - - - - px - - - 0 - - - 99 - - - 9 - - - - - - - - 50 - false - - - - Disable blinking - - - - - - - - - - - 75 - true - - - - Text area - - - - - - - 50 - false - - - - Max width - - - - - - - - 50 - false - - - - px - - - 4096 - - - 500 - - - - - - - - 50 - false - - - - Left/Right margins: - - - - - - - - 50 - false - - - - px - - - 2048 - - - - - - - - 50 - false - - - - Top/Bottom margins: - - - - - - - - 50 - false - - - - px - - - 2048 - - - - - - - + + + + 75 + true + + + + Colors + + + + + + + 50 + false + + + + Background: + + + + + + + + 0 + 0 + + + + + 50 + false + + + + + + + + + + + + 50 + false + + + + Transparent + + + + + + + + 50 + false + + + + Color: + + + + + + + + 0 + 0 + + + + + 50 + false + + + + + + + + + + + + 50 + false + + + + Restore defaults + + + + + + + + + + 75 + true + + + + Font + + + + + + + 50 + false + + + + Family: + + + + + + + + 0 + 0 + + + + + 150 + 16777215 + + + + + 50 + false + + + + + + + + + 50 + false + + + + Size: + + + + + + + + 50 + false + + + + Misspelled: + + + + + + + + 0 + 0 + + + + + 50 + false + + + + + + + + + + + + 50 + false + + + + 4 + + + 299 + + + 10 + + + + + + + + + + + 75 + true + + + + Text area + + + + + + + 50 + false + + + + Max width + + + + + + + + 50 + false + + + + px + + + 4096 + + + 500 + + + + + + + + 50 + false + + + + Left/Right margins: + + + + + + + + 50 + false + + + + px + + + 2048 + + + + + + + + 50 + false + + + + Top/Bottom margins: + + + + + + + + 50 + false + + + + px + + + 2048 + + + + + + + + + + @@ -2347,6 +2321,65 @@ text-align:center; Paragraphs + + + + + 50 + false + + + + Alignment: + + + + + + + + 50 + false + + + + + Left + + + + .. + + + + + Center + + + + .. + + + + + Right + + + + .. + + + + + Justify + + + + .. + + + + @@ -2550,8 +2583,23 @@ text-align:center; + + + + + + + + 75 + true + + + + Cursor + + - + 50 @@ -2559,58 +2607,43 @@ text-align:center; - Alignment: + Use block insertion of - + 50 false - - - Left - - - - - - - - - - Center - - - - - - - - - - Right - - - - - - - - - - Justify - - - - - - - + + px + + + 0 + + + 99 + + + 9 + + + + + + + + 50 + false + + + + Disable blinking + @@ -3597,8 +3630,7 @@ text-align:center; - - + .. @@ -3607,8 +3639,7 @@ text-align:center; - - + .. @@ -3617,8 +3648,7 @@ text-align:center; - - + .. @@ -3627,8 +3657,7 @@ text-align:center; - - + .. diff --git a/manuskript/ui/welcome.py b/manuskript/ui/welcome.py index 4b3286b..f0713dc 100644 --- a/manuskript/ui/welcome.py +++ b/manuskript/ui/welcome.py @@ -374,6 +374,8 @@ class welcome(QWidget, Ui_welcome): # Empty settings imp.reload(settings) + settings.initDefaultValues() + if self.template: t = [i for i in self.templates() if i[0] == self.template[0]] if t and t[0][2] == "Non-fiction": From 58bdf35953e0250c0461433bb56e0a45e577eed3 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Tue, 14 Nov 2017 11:23:18 +0100 Subject: [PATCH 56/65] Adds: transparent text editor --- manuskript/ui/style.py | 121 +++++++++++++++++++--------- manuskript/ui/views/textEditView.py | 9 ++- 2 files changed, 91 insertions(+), 39 deletions(-) diff --git a/manuskript/ui/style.py b/manuskript/ui/style.py index ba88fc6..042d7b5 100644 --- a/manuskript/ui/style.py +++ b/manuskript/ui/style.py @@ -18,11 +18,18 @@ windowText = p.color(QPalette.WindowText).name() # General foregroung base = p.color(QPalette.Base).name() # Other background text = p.color(QPalette.Text).name() # Base Text brightText = p.color(QPalette.BrightText).name() # Contrast Text -button = p.color(QPalette.Window).name() # Button background -buttonText = p.color(QPalette.Window).name() # Button Text +button = p.color(QPalette.Button).name() # Button background +buttonText = p.color(QPalette.ButtonText).name() # Button Text highlight = p.color(QPalette.Highlight).name() # Other background highlightedText = p.color(QPalette.HighlightedText).name() # Base Text +light = p.color(QPalette.Light).name() # Lighter than Button color +midlight = p.color(QPalette.Midlight).name() # Between Button and Light +dark = p.color(QPalette.Dark).name() # Darker than Button +mid = p.color(QPalette.Mid).name() # Between Button and Dark +shadow = p.color(QPalette.Shadow).name() # A very dark color + + bgHover = "#ccc" bgChecked = "#bbb" borderColor = "darkGray" @@ -119,43 +126,85 @@ def collapsibleGroupBoxButton(): def mainEditorTabSS(): - return """ - QTabWidget::pane{{ - margin-top: -1px; - border: 1px solid #999; - }} - QTabWidget::tab-bar{{ - left:50px; - }} - QTabBar{{ - background: transparent; - border-radius: 0; - border: 0px; - }} - QTabBar::tab{{ - margin: 3px 0 -3px 0; - padding: 2px 9px; - border: 1px solid #999; - border-bottom: 0px; - }} - QTabBar::tab:selected{{ - border: 1px solid #999; - background: {bgColor}; - border-bottom: 0px; - margin-top: 0px; - color: {foreground}; - }} - QTabBar::tab:!selected:hover{{ - background:#ddd; - }} + if not settings.textEditor["backgroundTransparent"]: + SS = """ + QTabWidget::pane{{ + margin-top: -1px; + border: 1px solid {borderColor}; + }} + QTabWidget::tab-bar{{ + left:50px; + }} + QTabBar{{ + background: transparent; + border-radius: 0; + border: 0px; + }} + QTabBar::tab{{ + padding: 2px 9px; + border: 1px solid {borderColor}; + border-bottom: 0px; + }} + QTabBar::tab:selected{{ + border: 1px solid {borderColor}; + background: {bgColor}; + border-bottom: 0px; + color: {foreground}; + }} + QTabBar::tab:!selected:hover{{ + background:{highlight}; + color: {highlightedText}; + }} + """.format( + bgColor=settings.textEditor["background"], + foreground=settings.textEditor["fontColor"], + borderColor=mid, + highlight=highlight, + highlightedText=highlightedText, + ) + else: + # Transparent text view + SS = """ + QTabWidget::pane{{ + margin-top: -1px; + border: none; + }} + QTabWidget::tab-bar{{ + left:50px; + }} + QTabBar{{ + background: transparent; + border: 0px; + }} + QTabBar::tab{{ + padding: 2px 9px; + border: 1px solid {borderColor}; + }} + QTabBar::tab:selected{{ + border: 1px solid {borderColor}; + background: {highlight}; + color: {text}; + }} + QTabBar::tab:!selected:hover{{ + background:{highlight}; + color: {highlightedText}; + }} + """.format( + highlight=highlight, + highlightedText=highlightedText, + text=text, + borderColor=mid, + ) + # Add scrollbar + SS += """ QScrollBar:vertical {{ border: none; background: transparent; width: 10px; }} QScrollBar::handle {{ - background: rgba(180, 180, 180, 40%); + background: {scrollBG}; }} QScrollBar::add-line:vertical {{ width:0; @@ -170,10 +219,10 @@ def mainEditorTabSS(): border: none; background: none; }} - """.format( - bgColor=settings.textEditor["background"], - foreground=settings.textEditor["fontColor"] - ) + """.format(scrollBG=button) + + return SS + def toolBarSS(): return """ diff --git a/manuskript/ui/views/textEditView.py b/manuskript/ui/views/textEditView.py index a9c7907..bb4a6c4 100644 --- a/manuskript/ui/views/textEditView.py +++ b/manuskript/ui/views/textEditView.py @@ -15,6 +15,7 @@ from manuskript.ui.editors.MDFunctions import MDFormatSelection from manuskript.ui.editors.MMDHighlighter import MMDHighlighter from manuskript.ui.editors.basicHighlighter import basicHighlighter from manuskript.ui.editors.textFormat import textFormat +from manuskript.ui import style as S try: import enchant @@ -205,6 +206,8 @@ class textEditView(QTextEdit): opt = settings.textEditor f = QFont() f.fromString(opt["font"]) + background = opt["background"] if not opt["backgroundTransparent"] else "transparent" + foreground = opt["fontColor"] if not opt["backgroundTransparent"] else S.text # self.setFont(f) self.setStyleSheet("""QTextEdit{{ background: {bg}; @@ -215,8 +218,8 @@ class textEditView(QTextEdit): {maxWidth} }} """.format( - bg=opt["background"], - foreground=opt["fontColor"], + bg=background, + foreground=foreground, ff=f.family(), fs="{}pt".format(str(f.pointSize())), mTB = opt["marginsTB"], @@ -237,7 +240,7 @@ class textEditView(QTextEdit): # We style by name, otherwise all heriting widgets get the same # colored background, for example context menu. name=self.parent().objectName(), - bg=opt["background"], + bg=background, )) cf = QTextCharFormat() From ee212db39c2f95b6b84bec12ab7304491427bd93 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Tue, 14 Nov 2017 14:48:28 +0100 Subject: [PATCH 57/65] Removes most of hard-coded colors --- manuskript/functions.py | 17 +- manuskript/models/persosProxyModel.py | 89 ++++---- manuskript/models/plotsProxyModel.py | 5 +- manuskript/models/worldModel.py | 9 +- manuskript/settingsWindow.py | 9 + manuskript/ui/cheatSheet.py | 11 +- manuskript/ui/collapsibleGroupBox.py | 5 +- manuskript/ui/collapsibleGroupBox2.py | 2 - manuskript/ui/editors/completer.py | 14 +- manuskript/ui/editors/tabSplitter.py | 8 +- manuskript/ui/editors/tabSplitter_ui.py | 5 +- manuskript/ui/exporters/exporter.py | 9 +- manuskript/ui/exporters/exportersManager.py | 3 + .../ui/exporters/exportersManager_ui.py | 12 +- .../ui/exporters/exportersManager_ui.ui | 9 - .../exporters/manuskript/plainTextSettings.py | 2 + .../manuskript/plainTextSettings_ui.py | 17 +- .../manuskript/plainTextSettings_ui.ui | 55 ++--- manuskript/ui/importers/generalSettings.py | 1 + manuskript/ui/importers/generalSettings_ui.py | 11 +- manuskript/ui/importers/generalSettings_ui.ui | 13 +- manuskript/ui/importers/importer.py | 6 +- manuskript/ui/mainWindow.py | 39 ++-- manuskript/ui/mainWindow.ui | 97 ++++----- manuskript/ui/settings_ui.py | 58 +----- manuskript/ui/settings_ui.ui | 58 +----- manuskript/ui/style.py | 194 +++++++++++------- manuskript/ui/views/characterTreeView.py | 5 +- .../ui/views/cmbOutlineCharacterChoser.py | 5 +- manuskript/ui/views/outlineDelegates.py | 5 +- manuskript/ui/views/plotTreeView.py | 5 +- manuskript/ui/views/treeDelegates.py | 9 +- manuskript/ui/welcome.py | 6 +- 33 files changed, 342 insertions(+), 451 deletions(-) diff --git a/manuskript/functions.py b/manuskript/functions.py index 8931cc6..aeb7483 100644 --- a/manuskript/functions.py +++ b/manuskript/functions.py @@ -146,11 +146,18 @@ def randomColor(mix=None): def mixColors(col1, col2, f=.5): + fromString = False + if type(col1) == str: + fromString = True + col1 = QColor(col1) + if type(col2) == str: + col2 = QColor(col2) f2 = 1-f r = col1.red() * f + col2.red() * f2 g = col1.green() * f + col2.green() * f2 b = col1.blue() * f + col2.blue() * f2 - return QColor(r, g, b) + + return QColor(r, g, b) if not fromString else QColor(r, g, b).name() def outlineItemColors(item): @@ -236,14 +243,6 @@ def tempFile(name): return os.path.join(QDir.tempPath(), name) -def lightBlue(): - """ - A light blue used in several places in manuskript. - @return: QColor - """ - return QColor(Qt.blue).lighter(190) - - def totalObjects(): return len(mainWindow().findChildren(QObject)) diff --git a/manuskript/models/persosProxyModel.py b/manuskript/models/persosProxyModel.py index 48aa637..ba3d2e4 100644 --- a/manuskript/models/persosProxyModel.py +++ b/manuskript/models/persosProxyModel.py @@ -2,107 +2,108 @@ #--!-- coding: utf8 --!-- from manuskript import enums +from manuskript.ui import style as S class persosProxyModel(QSortFilterProxyModel): - + newStatuses = pyqtSignal() - + def __init__(self, parent=None): QSortFilterProxyModel.__init__(self, parent) - + #self.rootItem = QStandardItem() self.p1 = QStandardItem(self.tr("Main")) self.p2 = QStandardItem(self.tr("Secundary")) self.p3 = QStandardItem(self.tr("Minors")) - + self._cats = [ self.p1, self.p2, self.p3 ] - + def mapFromSource(self, sourceIndex): if not sourceIndex.isValid(): return QModelIndex() - + row = self._map.index(sourceIndex.row()) #item = sourceIndex.internalPointer() item = self.sourceModel().itemFromIndex(sourceIndex) - + return self.createIndex(row, sourceIndex.column(), item) - + def flags(self, index): if not index.isValid(): return Qt.NoItemFlags - + if index.isValid() and not self.mapToSource(index).isValid(): return Qt.NoItemFlags#Qt.ItemIsEnabled else: return Qt.ItemIsEnabled | Qt.ItemIsSelectable - + def mapToSource(self, proxyIndex): if not proxyIndex.isValid(): return QModelIndex() - + row = self._map[proxyIndex.row()] - + if type(row) != int: return QModelIndex() - + #item = proxyIndex.internalPointer() item = self.sourceModel().item(row, proxyIndex.column()) - + return self.sourceModel().indexFromItem(item) - + def setSourceModel(self, model): QSortFilterProxyModel.setSourceModel(self, model) self.sourceModel().dataChanged.connect(self.mapModelMaybe) self.sourceModel().rowsInserted.connect(self.mapModel) self.sourceModel().rowsRemoved.connect(self.mapModel) self.sourceModel().rowsMoved.connect(self.mapModel) - + self.mapModel() - + def mapModelMaybe(self, topLeft, bottomRight): if topLeft.column() <= Perso.importance.value <= bottomRight.column(): self.mapModel() - + def mapModel(self): self.beginResetModel() src = self.sourceModel() - + self._map = [] - + for i, cat in enumerate(self._cats): self._map.append(cat) - + for p in range(src.rowCount()): item = src.item(p, Perso.importance.value) - + if item and item.text(): imp = int(item.text()) else: imp = 0 - + if 2-imp == i: self._map.append(p) - + self.endResetModel() - - + + def data(self, index, role=Qt.DisplayRole): - + if index.isValid() and not self.mapToSource(index).isValid(): row = index.row() - + if role == Qt.DisplayRole: return self._map[row].text() - + elif role == Qt.ForegroundRole: - return QBrush(Qt.darkBlue) + return QBrush(QColor(S.highlightedTextDark)) elif role == Qt.BackgroundRole: - return QBrush(QColor(Qt.blue).lighter(190)) + return QBrush(QColor(S.highlightLight)) elif role == Qt.TextAlignmentRole: return Qt.AlignCenter elif role == Qt.FontRole: @@ -113,32 +114,32 @@ class persosProxyModel(QSortFilterProxyModel): else: #FIXME: sometimes, the name of the character is not displayed return self.sourceModel().data(self.mapToSource(index), role) - + def index(self, row, column, parent): - + i = self._map[row] - + if type(i) != int: - + return self.createIndex(row, column, i) - + else: - + return self.mapFromSource(self.sourceModel().index(i, column, QModelIndex())) - + def parent(self, index=QModelIndex()): return QModelIndex() - + def rowCount(self, parent=QModelIndex()): return len(self._map) - + def columnCount(self, parent=QModelIndex()): return self.sourceModel().columnCount(QModelIndex()) - + def item(self, row, col, parent=QModelIndex()): idx = self.mapToSource(self.index(row, col, parent)) return self.sourceModel().item(idx.row(), idx.column()) - - + + #def setData(self, index, value, role=Qt.EditRole): - #pass \ No newline at end of file + #pass diff --git a/manuskript/models/plotsProxyModel.py b/manuskript/models/plotsProxyModel.py index f182795..f0063ee 100644 --- a/manuskript/models/plotsProxyModel.py +++ b/manuskript/models/plotsProxyModel.py @@ -9,6 +9,7 @@ from PyQt5.QtGui import QColor from PyQt5.QtGui import QStandardItem from manuskript.enums import Plot +from manuskript.ui import style as S class plotsProxyModel(QSortFilterProxyModel): @@ -105,9 +106,9 @@ class plotsProxyModel(QSortFilterProxyModel): return self._map[row].text() elif role == Qt.ForegroundRole: - return QBrush(Qt.darkBlue) + return QBrush(QColor(S.highlightedTextDark)) elif role == Qt.BackgroundRole: - return QBrush(QColor(Qt.blue).lighter(190)) + return QBrush(QColor(S.highlightLight)) elif role == Qt.TextAlignmentRole: return Qt.AlignCenter elif role == Qt.FontRole: diff --git a/manuskript/models/worldModel.py b/manuskript/models/worldModel.py index 5f600e6..45692d4 100644 --- a/manuskript/models/worldModel.py +++ b/manuskript/models/worldModel.py @@ -4,11 +4,12 @@ from PyQt5.QtCore import QModelIndex from PyQt5.QtCore import QSize from PyQt5.QtCore import Qt from PyQt5.QtGui import QStandardItem, QBrush, QFontMetrics -from PyQt5.QtGui import QStandardItemModel +from PyQt5.QtGui import QStandardItemModel, QColor from PyQt5.QtWidgets import QMenu, QAction, qApp from manuskript.enums import World -from manuskript.functions import mainWindow, lightBlue +from manuskript.functions import mainWindow +from manuskript.ui import style as S class worldModel(QStandardItemModel): @@ -254,7 +255,7 @@ class worldModel(QStandardItemModel): if role == Qt.BackgroundRole: if level == 0: - return QBrush(lightBlue()) + return QBrush(QColor(S.highlightLight)) if role == Qt.TextAlignmentRole: if level == 0: @@ -268,7 +269,7 @@ class worldModel(QStandardItemModel): if role == Qt.ForegroundRole: if level == 0: - return QBrush(Qt.darkBlue) + return QBrush(QColor(S.highlightedTextDark)) if role == Qt.SizeHintRole: fm = QFontMetrics(qApp.font()) diff --git a/manuskript/settingsWindow.py b/manuskript/settingsWindow.py index 05de838..688d7de 100644 --- a/manuskript/settingsWindow.py +++ b/manuskript/settingsWindow.py @@ -38,6 +38,15 @@ class settingsWindow(QWidget, Ui_Settings): self.mw = mainWindow # UI + for l in [self.lblTitleGeneral, + self.lblTitleGeneral_2, + self.lblTitleViews, + self.lblTitleLabels, + self.lblTitleStatus, + self.lblTitleFullscreen, + ]: + l.setStyleSheet(S.titleLabelSS()) + icons = [QIcon.fromTheme("configure"), QIcon.fromTheme("history-view"), QIcon.fromTheme("gnome-settings"), diff --git a/manuskript/ui/cheatSheet.py b/manuskript/ui/cheatSheet.py index 6deb4a5..b44038d 100644 --- a/manuskript/ui/cheatSheet.py +++ b/manuskript/ui/cheatSheet.py @@ -1,14 +1,13 @@ #!/usr/bin/env python # --!-- coding: utf8 --!-- from PyQt5.QtCore import pyqtSignal, Qt, QTimer, QRect -from PyQt5.QtGui import QBrush, QCursor, QPalette, QFontMetrics +from PyQt5.QtGui import QBrush, QCursor, QPalette, QFontMetrics, QColor from PyQt5.QtWidgets import QWidget, QListWidgetItem, QToolTip, QStyledItemDelegate, QStyle from manuskript.enums import Character from manuskript.enums import Plot -from manuskript.functions import lightBlue from manuskript.functions import mainWindow -from manuskript.ui import style +from manuskript.ui import style as S from manuskript.ui.cheatSheet_ui import Ui_cheatSheet from manuskript.models import references as Ref from manuskript.ui.editors.completer import completer @@ -20,7 +19,7 @@ class cheatSheet(QWidget, Ui_cheatSheet): def __init__(self, parent=None): QWidget.__init__(self, parent) self.setupUi(self) - self.txtFilter.setStyleSheet(style.lineEditSS()) + self.txtFilter.setStyleSheet(S.lineEditSS()) self.splitter.setStretchFactor(0, 5) self.splitter.setStretchFactor(1, 70) @@ -122,8 +121,8 @@ class cheatSheet(QWidget, Ui_cheatSheet): def addCategory(self, title): item = QListWidgetItem(title) - item.setBackground(QBrush(lightBlue())) - item.setForeground(QBrush(Qt.darkBlue)) + item.setBackground(QBrush(QColor(S.highlightLight))) + item.setForeground(QBrush(QColor(S.highlightedTextDark))) item.setFlags(Qt.ItemIsEnabled) f = item.font() f.setBold(True) diff --git a/manuskript/ui/collapsibleGroupBox.py b/manuskript/ui/collapsibleGroupBox.py index dac9cfc..2b3cc04 100644 --- a/manuskript/ui/collapsibleGroupBox.py +++ b/manuskript/ui/collapsibleGroupBox.py @@ -4,6 +4,7 @@ from PyQt5.QtCore import Qt, QRect, QRectF from PyQt5.QtGui import QColor, QBrush, QRegion, QTextOption, QFont from PyQt5.QtWidgets import QSizePolicy, QGroupBox, QWidget, QStylePainter, QStyleOptionGroupBox, qApp, QVBoxLayout, \ QStyle, QStyleOptionFrame, QStyleOptionFocusRect +from manuskript.ui import style as S class collapsibleGroupBox(QGroupBox): @@ -57,7 +58,7 @@ class collapsibleGroupBox(QGroupBox): titleRect.setHeight(textRect.height()) titleRect.moveTop(textRect.top()) - p.setBrush(QBrush(QColor(Qt.blue).lighter(190))) + p.setBrush(QBrush(QColor(S.highlightLight))) p.setPen(Qt.NoPen) p.drawRoundedRect(titleRect, 10, 10) p.restore() @@ -105,7 +106,7 @@ class collapsibleGroupBox(QGroupBox): f = QFont() f.setBold(True) p.setFont(f) - p.setPen(Qt.darkBlue) + p.setPen(QColor(S.highlightedTextDark)) p.drawText(QRectF(titleRect), groupBox.text.replace("&", ""), topt) p.restore() diff --git a/manuskript/ui/collapsibleGroupBox2.py b/manuskript/ui/collapsibleGroupBox2.py index 96f594f..b52e1ee 100644 --- a/manuskript/ui/collapsibleGroupBox2.py +++ b/manuskript/ui/collapsibleGroupBox2.py @@ -3,7 +3,6 @@ from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QWidget, QFrame, QPushButton, QVBoxLayout, QSizePolicy, qApp -from manuskript.functions import lightBlue from manuskript.ui import style @@ -16,7 +15,6 @@ class collapsibleGroupBox2(QWidget): self.button.setChecked(True) self.switched = False self.vPolicy = None - # self.button.setStyleSheet("background-color: lightBlue;") self.button.setStyleSheet(style.collapsibleGroupBoxButton()) diff --git a/manuskript/ui/editors/completer.py b/manuskript/ui/editors/completer.py index 70e0b43..0840b9e 100644 --- a/manuskript/ui/editors/completer.py +++ b/manuskript/ui/editors/completer.py @@ -1,13 +1,13 @@ #!/usr/bin/env python # --!-- coding: utf8 --!-- from PyQt5.QtCore import pyqtSignal, Qt, QRect -from PyQt5.QtGui import QBrush, QFontMetrics, QPalette +from PyQt5.QtGui import QBrush, QFontMetrics, QPalette, QColor from PyQt5.QtWidgets import QWidget, QListWidgetItem, QStyledItemDelegate, QStyle -from manuskript.functions import lightBlue from manuskript.functions import mainWindow from manuskript.ui.editors.completer_ui import Ui_completer from manuskript.models import references as Ref +from manuskript.ui import style as S class completer(QWidget, Ui_completer): @@ -33,8 +33,8 @@ class completer(QWidget, Ui_completer): def addCategory(self, title): item = QListWidgetItem(title) - item.setBackground(QBrush(lightBlue())) - item.setForeground(QBrush(Qt.darkBlue)) + item.setBackground(QBrush(QColor(S.highlightLight))) + item.setForeground(QBrush(QColor(S.highlightedTextDark))) item.setFlags(Qt.ItemIsEnabled) self.list.addItem(item) @@ -88,6 +88,10 @@ class listCompleterDelegate(QStyledItemDelegate): r = QRect(option.rect) r.setLeft(r.left() + w) painter.save() - painter.setPen(Qt.gray) + if option.state & QStyle.State_Selected: + painter.setPen(QColor(S.highlightedTextLight)) + else: + painter.setPen(QColor(S.textLight)) + painter.drawText(r, Qt.AlignLeft, extra) painter.restore() diff --git a/manuskript/ui/editors/tabSplitter.py b/manuskript/ui/editors/tabSplitter.py index c543e77..751ea9a 100644 --- a/manuskript/ui/editors/tabSplitter.py +++ b/manuskript/ui/editors/tabSplitter.py @@ -239,7 +239,13 @@ class tabSplitter(QWidget, Ui_tabSplitter): # border:1px solid darkblue; # }}""".format(self.splitter.objectName())) - self.setStyleSheet(style.mainEditorTabSS() + "QWidget{{background:{};}}".format(style.bgHover)) + self.setStyleSheet(style.mainEditorTabSS() + """ + QSplitter#{name}, + QSplitter#{name} > QWidget > QSplitter{{ + border:3px solid {color}; + }}""".format( + name=self.splitter.objectName(), + color=style.highlight)) elif object == self.btnSplit and event.type() == event.HoverLeave: # self.setAutoFillBackground(False) # self.setBackgroundRole(QPalette.Window) diff --git a/manuskript/ui/editors/tabSplitter_ui.py b/manuskript/ui/editors/tabSplitter_ui.py index 3b20b1d..eac583d 100644 --- a/manuskript/ui/editors/tabSplitter_ui.py +++ b/manuskript/ui/editors/tabSplitter_ui.py @@ -2,8 +2,7 @@ # Form implementation generated from reading ui file 'manuskript/ui/editors/tabSplitter_ui.ui' # -# Created: Sun Apr 10 16:27:03 2016 -# by: PyQt5 UI code generator 5.2.1 +# Created by: PyQt5 UI code generator 5.9 # # WARNING! All changes made in this file will be lost! @@ -20,8 +19,8 @@ class Ui_tabSplitter(object): tabSplitter.setSizePolicy(sizePolicy) tabSplitter.setWindowTitle("tabSPlitter") self.verticalLayout = QtWidgets.QVBoxLayout(tabSplitter) - self.verticalLayout.setSpacing(0) self.verticalLayout.setContentsMargins(0, 0, 0, 0) + self.verticalLayout.setSpacing(0) self.verticalLayout.setObjectName("verticalLayout") self.splitter = QtWidgets.QSplitter(tabSplitter) self.splitter.setMinimumSize(QtCore.QSize(30, 30)) diff --git a/manuskript/ui/exporters/exporter.py b/manuskript/ui/exporters/exporter.py index da1628c..3340ae4 100644 --- a/manuskript/ui/exporters/exporter.py +++ b/manuskript/ui/exporters/exporter.py @@ -8,9 +8,10 @@ from PyQt5.QtGui import QBrush, QColor, QIcon from PyQt5.QtWidgets import QWidget from manuskript import exporter -from manuskript.functions import lightBlue, writablePath +from manuskript.functions import writablePath from manuskript.ui.exporters.exporter_ui import Ui_exporter from manuskript.ui.exporters.exportersManager import exportersManager +from manuskript.ui import style as S class exporterDialog(QWidget, Ui_exporter): @@ -46,8 +47,8 @@ class exporterDialog(QWidget, Ui_exporter): continue self.cmbExporters.addItem(QIcon(E.icon), E.name) - self.cmbExporters.setItemData(self.cmbExporters.count() - 1, QBrush(QColor(Qt.darkBlue)), Qt.ForegroundRole) - self.cmbExporters.setItemData(self.cmbExporters.count() - 1, QBrush(lightBlue()), Qt.BackgroundRole) + self.cmbExporters.setItemData(self.cmbExporters.count() - 1, QBrush(QColor(S.highlightedTextDark)), Qt.ForegroundRole) + self.cmbExporters.setItemData(self.cmbExporters.count() - 1, QBrush(QColor(S.highlightLight)), Qt.BackgroundRole) item = self.cmbExporters.model().item(self.cmbExporters.count() - 1) item.setFlags(Qt.ItemIsEnabled) @@ -142,4 +143,4 @@ class exporterDialog(QWidget, Ui_exporter): item.widget().deleteLater() l.addWidget(widget) - widget.setParent(group) \ No newline at end of file + widget.setParent(group) diff --git a/manuskript/ui/exporters/exportersManager.py b/manuskript/ui/exporters/exportersManager.py index bebfc25..60b3ef9 100644 --- a/manuskript/ui/exporters/exportersManager.py +++ b/manuskript/ui/exporters/exportersManager.py @@ -10,6 +10,8 @@ from PyQt5.QtWidgets import QWidget, QListWidgetItem, QFileDialog from manuskript import exporter from manuskript.ui.exporters.exportersManager_ui import Ui_ExportersManager +from manuskript.ui import style as S + class exportersManager(QWidget, Ui_ExportersManager): @@ -18,6 +20,7 @@ class exportersManager(QWidget, Ui_ExportersManager): def __init__(self, parent=None): QWidget.__init__(self, parent) self.setupUi(self) + self.lblExporterName.setStyleSheet(S.titleLabelSS()) # Var self.currentExporter = None diff --git a/manuskript/ui/exporters/exportersManager_ui.py b/manuskript/ui/exporters/exportersManager_ui.py index e98859d..1e4d505 100644 --- a/manuskript/ui/exporters/exportersManager_ui.py +++ b/manuskript/ui/exporters/exportersManager_ui.py @@ -2,8 +2,7 @@ # Form implementation generated from reading ui file 'manuskript/ui/exporters/exportersManager_ui.ui' # -# Created: Fri Apr 8 12:47:11 2016 -# by: PyQt5 UI code generator 5.2.1 +# Created by: PyQt5 UI code generator 5.9 # # WARNING! All changes made in this file will be lost! @@ -31,13 +30,6 @@ class Ui_ExportersManager(object): sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.lblExporterName.sizePolicy().hasHeightForWidth()) self.lblExporterName.setSizePolicy(sizePolicy) - self.lblExporterName.setStyleSheet("background-color:lightBlue;\n" -"border:none;\n" -"padding:10px;\n" -"color:darkBlue;\n" -"font-size:16px;\n" -"font-weight:bold;\n" -"text-align:center;") self.lblExporterName.setText("{Exporter Name}") self.lblExporterName.setAlignment(QtCore.Qt.AlignCenter) self.lblExporterName.setObjectName("lblExporterName") @@ -82,8 +74,8 @@ class Ui_ExportersManager(object): self.frame.setFrameShadow(QtWidgets.QFrame.Raised) self.frame.setObjectName("frame") self.verticalLayout = QtWidgets.QVBoxLayout(self.frame) - self.verticalLayout.setSpacing(0) self.verticalLayout.setContentsMargins(0, 0, 0, 0) + self.verticalLayout.setSpacing(0) self.verticalLayout.setObjectName("verticalLayout") self.lblExportToDescription = QtWidgets.QLabel(self.frame) font = QtGui.QFont() diff --git a/manuskript/ui/exporters/exportersManager_ui.ui b/manuskript/ui/exporters/exportersManager_ui.ui index c5d0c0e..1f275cc 100644 --- a/manuskript/ui/exporters/exportersManager_ui.ui +++ b/manuskript/ui/exporters/exportersManager_ui.ui @@ -46,15 +46,6 @@ 0 - - background-color:lightBlue; -border:none; -padding:10px; -color:darkBlue; -font-size:16px; -font-weight:bold; -text-align:center; - {Exporter Name} diff --git a/manuskript/ui/exporters/manuskript/plainTextSettings.py b/manuskript/ui/exporters/manuskript/plainTextSettings.py index 23a3556..78fd001 100644 --- a/manuskript/ui/exporters/manuskript/plainTextSettings.py +++ b/manuskript/ui/exporters/manuskript/plainTextSettings.py @@ -9,12 +9,14 @@ from PyQt5.QtWidgets import QWidget, QTableWidgetItem, QListWidgetItem, QTreeVie from manuskript.functions import mainWindow, writablePath from manuskript.ui.exporters.manuskript.plainTextSettings_ui import Ui_exporterSettings +from manuskript.ui import style as S class exporterSettings(QWidget, Ui_exporterSettings): def __init__(self, _format, parent=None): QWidget.__init__(self, parent) self.setupUi(self) + self.toolBox.setStyleSheet(S.toolBoxSS()) self.mw = mainWindow() self._format = _format diff --git a/manuskript/ui/exporters/manuskript/plainTextSettings_ui.py b/manuskript/ui/exporters/manuskript/plainTextSettings_ui.py index a6c8f98..e8f36c7 100644 --- a/manuskript/ui/exporters/manuskript/plainTextSettings_ui.py +++ b/manuskript/ui/exporters/manuskript/plainTextSettings_ui.py @@ -17,18 +17,9 @@ class Ui_exporterSettings(object): self.verticalLayout_2.setSpacing(10) self.verticalLayout_2.setObjectName("verticalLayout_2") self.toolBox = QtWidgets.QToolBox(exporterSettings) - 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.content = QtWidgets.QWidget() - self.content.setGeometry(QtCore.QRect(0, 0, 497, 834)) + self.content.setGeometry(QtCore.QRect(0, 0, 349, 842)) self.content.setObjectName("content") self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.content) self.verticalLayout_5.setContentsMargins(0, 0, 0, 0) @@ -112,7 +103,7 @@ 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, 534)) + self.separations.setGeometry(QtCore.QRect(0, 0, 173, 336)) self.separations.setObjectName("separations") self.verticalLayout_8 = QtWidgets.QVBoxLayout(self.separations) self.verticalLayout_8.setContentsMargins(0, 0, 0, 0) @@ -321,7 +312,7 @@ 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, 534)) + self.transformations.setGeometry(QtCore.QRect(0, 0, 511, 522)) self.transformations.setStyleSheet("QGroupBox{font-weight:bold;}") self.transformations.setObjectName("transformations") self.verticalLayout_6 = QtWidgets.QVBoxLayout(self.transformations) @@ -484,7 +475,7 @@ 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, 534)) + self.preview.setGeometry(QtCore.QRect(0, 0, 369, 130)) self.preview.setStyleSheet("QGroupBox{font-weight:bold;}") self.preview.setObjectName("preview") self.verticalLayout_11 = QtWidgets.QVBoxLayout(self.preview) diff --git a/manuskript/ui/exporters/manuskript/plainTextSettings_ui.ui b/manuskript/ui/exporters/manuskript/plainTextSettings_ui.ui index 15c1a5e..1334879 100644 --- a/manuskript/ui/exporters/manuskript/plainTextSettings_ui.ui +++ b/manuskript/ui/exporters/manuskript/plainTextSettings_ui.ui @@ -31,17 +31,6 @@ - - QToolBox::tab{ - background-color: #BBB; - padding: 2px; - border: none; -} - -QToolBox::tab:selected, QToolBox::tab:hover{ - background-color:skyblue; -} - 2 @@ -53,8 +42,8 @@ QToolBox::tab:selected, QToolBox::tab:hover{ 0 0 - 497 - 834 + 349 + 842 @@ -232,8 +221,8 @@ QToolBox::tab:selected, QToolBox::tab:hover{ 0 0 - 511 - 534 + 173 + 336 @@ -295,8 +284,7 @@ QToolBox::tab:selected, QToolBox::tab:hover{ - - + .. true @@ -332,8 +320,7 @@ QToolBox::tab:selected, QToolBox::tab:hover{ - - + .. true @@ -423,8 +410,7 @@ QToolBox::tab:selected, QToolBox::tab:hover{ - - + .. true @@ -460,8 +446,7 @@ QToolBox::tab:selected, QToolBox::tab:hover{ - - + .. true @@ -551,8 +536,7 @@ QToolBox::tab:selected, QToolBox::tab:hover{ - - + .. true @@ -588,8 +572,7 @@ QToolBox::tab:selected, QToolBox::tab:hover{ - - + .. true @@ -679,8 +662,7 @@ QToolBox::tab:selected, QToolBox::tab:hover{ - - + .. true @@ -716,8 +698,7 @@ QToolBox::tab:selected, QToolBox::tab:hover{ - - + .. true @@ -773,7 +754,7 @@ QToolBox::tab:selected, QToolBox::tab:hover{ 0 0 511 - 534 + 522 @@ -1113,8 +1094,7 @@ QToolBox::tab:selected, QToolBox::tab:hover{ - - + .. true @@ -1128,8 +1108,7 @@ QToolBox::tab:selected, QToolBox::tab:hover{ - - + .. true @@ -1161,8 +1140,8 @@ QToolBox::tab:selected, QToolBox::tab:hover{ 0 0 - 511 - 534 + 369 + 130 diff --git a/manuskript/ui/importers/generalSettings.py b/manuskript/ui/importers/generalSettings.py index 3940aab..cd94d2f 100644 --- a/manuskript/ui/importers/generalSettings.py +++ b/manuskript/ui/importers/generalSettings.py @@ -17,6 +17,7 @@ class generalSettings(QWidget, Ui_generalSettings): def __init__(self, parent=None): QWidget.__init__(self, parent) self.setupUi(self) + self.toolBox.setStyleSheet(style.toolBoxSS()) self.mw = mainWindow() self.txtGeneralSplitScenes.setStyleSheet(style.lineEditSS()) diff --git a/manuskript/ui/importers/generalSettings_ui.py b/manuskript/ui/importers/generalSettings_ui.py index 6ca528a..d4bb2c5 100644 --- a/manuskript/ui/importers/generalSettings_ui.py +++ b/manuskript/ui/importers/generalSettings_ui.py @@ -17,18 +17,9 @@ class Ui_generalSettings(object): 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.setGeometry(QtCore.QRect(0, 0, 267, 375)) self.general.setObjectName("general") self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.general) self.verticalLayout_5.setContentsMargins(6, 6, 6, 6) diff --git a/manuskript/ui/importers/generalSettings_ui.ui b/manuskript/ui/importers/generalSettings_ui.ui index 955fc21..8bb2f71 100644 --- a/manuskript/ui/importers/generalSettings_ui.ui +++ b/manuskript/ui/importers/generalSettings_ui.ui @@ -31,17 +31,6 @@ - - QToolBox::tab{ - background-color: #BBB; - padding: 2px; - border: none; -} - -QToolBox::tab:selected, QToolBox::tab:hover{ - background-color:skyblue; -} - 0 @@ -54,7 +43,7 @@ QToolBox::tab:selected, QToolBox::tab:hover{ 0 0 267 - 378 + 375 diff --git a/manuskript/ui/importers/importer.py b/manuskript/ui/importers/importer.py index f09ae44..49d1583 100644 --- a/manuskript/ui/importers/importer.py +++ b/manuskript/ui/importers/importer.py @@ -7,7 +7,7 @@ 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.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 @@ -71,8 +71,8 @@ class importerDialog(QWidget, Ui_importer): 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) + 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) diff --git a/manuskript/ui/mainWindow.py b/manuskript/ui/mainWindow.py index d0005c4..775dca6 100644 --- a/manuskript/ui/mainWindow.py +++ b/manuskript/ui/mainWindow.py @@ -383,7 +383,7 @@ class Ui_MainWindow(object): self.scrollAreaPersoInfos.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) self.scrollAreaPersoInfos.setObjectName("scrollAreaPersoInfos") self.scrollAreaPersoInfosWidget = QtWidgets.QWidget() - self.scrollAreaPersoInfosWidget.setGeometry(QtCore.QRect(0, 0, 444, 709)) + self.scrollAreaPersoInfosWidget.setGeometry(QtCore.QRect(0, 0, 426, 688)) self.scrollAreaPersoInfosWidget.setObjectName("scrollAreaPersoInfosWidget") self.formLayout_8 = QtWidgets.QFormLayout(self.scrollAreaPersoInfosWidget) self.formLayout_8.setFieldGrowthPolicy(QtWidgets.QFormLayout.AllNonFixedFieldsGrow) @@ -1036,7 +1036,7 @@ class Ui_MainWindow(object): self.horizontalLayout_2.addWidget(self.stack) MainWindow.setCentralWidget(self.centralwidget) self.menubar = QtWidgets.QMenuBar(MainWindow) - self.menubar.setGeometry(QtCore.QRect(0, 0, 1112, 20)) + self.menubar.setGeometry(QtCore.QRect(0, 0, 1112, 30)) self.menubar.setObjectName("menubar") self.menuFile = QtWidgets.QMenu(self.menubar) self.menuFile.setObjectName("menuFile") @@ -1101,21 +1101,6 @@ class Ui_MainWindow(object): self.verticalLayout_16.setContentsMargins(0, 0, 0, 0) self.verticalLayout_16.setObjectName("verticalLayout_16") self.lstTabs = QtWidgets.QListWidget(self.dockWidgetContents) - self.lstTabs.setStyleSheet("QListView {\n" -" show-decoration-selected: 0;\n" -" outline: none;\n" -" background-color: transparent;\n" -"}\n" -"\n" -"QListView::item:selected {\n" -" background: #DCDEF1;\n" -" color: black;\n" -"}\n" -"\n" -"QListView::item:hover {\n" -" background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,\n" -" stop: 0 #FAFBFE, stop: 1 #DCDEF1);\n" -"}") self.lstTabs.setFrameShape(QtWidgets.QFrame.NoFrame) self.lstTabs.setObjectName("lstTabs") self.verticalLayout_16.addWidget(self.lstTabs) @@ -1421,27 +1406,27 @@ 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.setText(_translate("MainWindow", "&Import…")) self.actImport.setShortcut(_translate("MainWindow", "F7")) - self.actCopy.setText(_translate("MainWindow", "Copy")) + self.actCopy.setText(_translate("MainWindow", "&Copy")) self.actCopy.setShortcut(_translate("MainWindow", "Ctrl+C")) - self.actCut.setText(_translate("MainWindow", "Cut")) + self.actCut.setText(_translate("MainWindow", "C&ut")) self.actCut.setShortcut(_translate("MainWindow", "Ctrl+X")) - self.actPaste.setText(_translate("MainWindow", "Paste")) + self.actPaste.setText(_translate("MainWindow", "&Paste")) self.actPaste.setShortcut(_translate("MainWindow", "Ctrl+V")) - self.actSplitDialog.setText(_translate("MainWindow", "Split…")) + self.actSplitDialog.setText(_translate("MainWindow", "&Split…")) self.actSplitDialog.setShortcut(_translate("MainWindow", "Ctrl+Shift+K")) - self.actSplitCursor.setText(_translate("MainWindow", "Split at cursor")) + self.actSplitCursor.setText(_translate("MainWindow", "Sp&lit at cursor")) self.actSplitCursor.setShortcut(_translate("MainWindow", "Ctrl+K")) - self.actMerge.setText(_translate("MainWindow", "Merge")) + self.actMerge.setText(_translate("MainWindow", "Me&rge")) 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.setText(_translate("MainWindow", "D&elete")) self.actDelete.setShortcut(_translate("MainWindow", "Del")) - self.actMoveUp.setText(_translate("MainWindow", "Move Up")) + self.actMoveUp.setText(_translate("MainWindow", "&Move Up")) self.actMoveUp.setShortcut(_translate("MainWindow", "Ctrl+Shift+Up")) - self.actMoveDown.setText(_translate("MainWindow", "Move Down")) + self.actMoveDown.setText(_translate("MainWindow", "M&ove Down")) self.actMoveDown.setShortcut(_translate("MainWindow", "Ctrl+Shift+Down")) from manuskript.ui.cheatSheet import cheatSheet diff --git a/manuskript/ui/mainWindow.ui b/manuskript/ui/mainWindow.ui index e87bd79..7a51fa5 100644 --- a/manuskript/ui/mainWindow.ui +++ b/manuskript/ui/mainWindow.ui @@ -815,8 +815,8 @@ 0 0 - 444 - 709 + 426 + 688 @@ -1601,8 +1601,7 @@ - - + .. true @@ -2093,7 +2092,7 @@ 0 0 1112 - 20 + 30 @@ -2106,8 +2105,7 @@ - - + .. @@ -2277,23 +2275,6 @@ - - QListView { - show-decoration-selected: 0; - outline: none; - background-color: transparent; -} - -QListView::item:selected { - background: #DCDEF1; - color: black; -} - -QListView::item:hover { - background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, - stop: 0 #FAFBFE, stop: 1 #DCDEF1); -} - QFrame::NoFrame @@ -2389,8 +2370,7 @@ QListView::item:hover { - - + .. &Labels... @@ -2399,8 +2379,7 @@ QListView::item:hover { - - + .. &Status... @@ -2463,8 +2442,7 @@ QListView::item:hover { - - + .. &Close project @@ -2473,8 +2451,7 @@ QListView::item:hover { - - + .. Co&mpile @@ -2491,8 +2468,7 @@ QListView::item:hover { - - + .. &About @@ -2504,11 +2480,10 @@ QListView::item:hover { - - + .. - Import… + &Import… F7 @@ -2516,10 +2491,11 @@ QListView::item:hover { - + + .. - Copy + &Copy Ctrl+C @@ -2527,10 +2503,11 @@ QListView::item:hover { - + + .. - Cut + C&ut Ctrl+X @@ -2538,10 +2515,11 @@ QListView::item:hover { - + + .. - Paste + &Paste Ctrl+V @@ -2549,10 +2527,11 @@ QListView::item:hover { - + + .. - Split… + &Split… Ctrl+Shift+K @@ -2560,10 +2539,11 @@ QListView::item:hover { - + + .. - Split at cursor + Sp&lit at cursor Ctrl+K @@ -2571,10 +2551,11 @@ QListView::item:hover { - + + .. - Merge + Me&rge Ctrl+M @@ -2582,7 +2563,8 @@ QListView::item:hover { - + + .. &Duplicate @@ -2593,10 +2575,11 @@ QListView::item:hover { - + + .. - Delete + D&elete Del @@ -2604,10 +2587,11 @@ QListView::item:hover { - + + .. - Move Up + &Move Up Ctrl+Shift+Up @@ -2615,10 +2599,11 @@ QListView::item:hover { - + + .. - Move Down + M&ove Down Ctrl+Shift+Down diff --git a/manuskript/ui/settings_ui.py b/manuskript/ui/settings_ui.py index ab72553..1fc7ec6 100644 --- a/manuskript/ui/settings_ui.py +++ b/manuskript/ui/settings_ui.py @@ -43,13 +43,6 @@ class Ui_Settings(object): sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.lblTitleGeneral.sizePolicy().hasHeightForWidth()) self.lblTitleGeneral.setSizePolicy(sizePolicy) - self.lblTitleGeneral.setStyleSheet("background-color:lightBlue;\n" -"border:none;\n" -"padding:10px;\n" -"color:darkBlue;\n" -"font-size:16px;\n" -"font-weight:bold;\n" -"text-align:center;") self.lblTitleGeneral.setAlignment(QtCore.Qt.AlignCenter) self.lblTitleGeneral.setObjectName("lblTitleGeneral") self.verticalLayout_7.addWidget(self.lblTitleGeneral) @@ -230,13 +223,6 @@ class Ui_Settings(object): sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.lblTitleGeneral_2.sizePolicy().hasHeightForWidth()) self.lblTitleGeneral_2.setSizePolicy(sizePolicy) - self.lblTitleGeneral_2.setStyleSheet("background-color:lightBlue;\n" -"border:none;\n" -"padding:10px;\n" -"color:darkBlue;\n" -"font-size:16px;\n" -"font-weight:bold;\n" -"text-align:center;") self.lblTitleGeneral_2.setAlignment(QtCore.Qt.AlignCenter) self.lblTitleGeneral_2.setObjectName("lblTitleGeneral_2") self.verticalLayout.addWidget(self.lblTitleGeneral_2) @@ -397,13 +383,6 @@ class Ui_Settings(object): sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.lblTitleViews.sizePolicy().hasHeightForWidth()) self.lblTitleViews.setSizePolicy(sizePolicy) - self.lblTitleViews.setStyleSheet("background-color:lightBlue;\n" -"border:none;\n" -"padding:10px;\n" -"color:darkBlue;\n" -"font-size:16px;\n" -"font-weight:bold;\n" -"text-align:center;") self.lblTitleViews.setAlignment(QtCore.Qt.AlignCenter) self.lblTitleViews.setObjectName("lblTitleViews") self.verticalLayout_9.addWidget(self.lblTitleViews) @@ -1329,13 +1308,6 @@ class Ui_Settings(object): sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.lblTitleLabels.sizePolicy().hasHeightForWidth()) self.lblTitleLabels.setSizePolicy(sizePolicy) - self.lblTitleLabels.setStyleSheet("background-color:lightBlue;\n" -"border:none;\n" -"padding:10px;\n" -"color:darkBlue;\n" -"font-size:16px;\n" -"font-weight:bold;\n" -"text-align:center;") self.lblTitleLabels.setAlignment(QtCore.Qt.AlignCenter) self.lblTitleLabels.setObjectName("lblTitleLabels") self.verticalLayout_3.addWidget(self.lblTitleLabels) @@ -1392,13 +1364,6 @@ class Ui_Settings(object): sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.lblTitleStatus.sizePolicy().hasHeightForWidth()) self.lblTitleStatus.setSizePolicy(sizePolicy) - self.lblTitleStatus.setStyleSheet("background-color:lightBlue;\n" -"border:none;\n" -"padding:10px;\n" -"color:darkBlue;\n" -"font-size:16px;\n" -"font-weight:bold;\n" -"text-align:center;") self.lblTitleStatus.setAlignment(QtCore.Qt.AlignCenter) self.lblTitleStatus.setObjectName("lblTitleStatus") self.verticalLayout_4.addWidget(self.lblTitleStatus) @@ -1428,22 +1393,15 @@ class Ui_Settings(object): self.verticalLayout_10 = QtWidgets.QVBoxLayout(self.page) self.verticalLayout_10.setContentsMargins(0, 0, 0, 0) self.verticalLayout_10.setObjectName("verticalLayout_10") - self.lblTitleStatus_2 = QtWidgets.QLabel(self.page) + self.lblTitleFullscreen = QtWidgets.QLabel(self.page) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.lblTitleStatus_2.sizePolicy().hasHeightForWidth()) - self.lblTitleStatus_2.setSizePolicy(sizePolicy) - self.lblTitleStatus_2.setStyleSheet("background-color:lightBlue;\n" -"border:none;\n" -"padding:10px;\n" -"color:darkBlue;\n" -"font-size:16px;\n" -"font-weight:bold;\n" -"text-align:center;") - self.lblTitleStatus_2.setAlignment(QtCore.Qt.AlignCenter) - self.lblTitleStatus_2.setObjectName("lblTitleStatus_2") - self.verticalLayout_10.addWidget(self.lblTitleStatus_2) + sizePolicy.setHeightForWidth(self.lblTitleFullscreen.sizePolicy().hasHeightForWidth()) + self.lblTitleFullscreen.setSizePolicy(sizePolicy) + self.lblTitleFullscreen.setAlignment(QtCore.Qt.AlignCenter) + self.lblTitleFullscreen.setObjectName("lblTitleFullscreen") + self.verticalLayout_10.addWidget(self.lblTitleFullscreen) self.themeStack = QtWidgets.QStackedWidget(self.page) self.themeStack.setObjectName("themeStack") self.stackedWidgetPage1_3 = QtWidgets.QWidget() @@ -1820,7 +1778,7 @@ class Ui_Settings(object): self.horizontalLayout_8.addWidget(self.stack) self.retranslateUi(Settings) - self.stack.setCurrentIndex(2) + self.stack.setCurrentIndex(5) self.tabViews.setCurrentIndex(3) self.themeStack.setCurrentIndex(1) self.themeEditStack.setCurrentIndex(3) @@ -2016,7 +1974,7 @@ class Ui_Settings(object): self.lblTitleLabels.setText(_translate("Settings", "Labels")) self.btnLabelColor.setShortcut(_translate("Settings", "Ctrl+S")) self.lblTitleStatus.setText(_translate("Settings", "Status")) - self.lblTitleStatus_2.setText(_translate("Settings", "Fullscreen")) + self.lblTitleFullscreen.setText(_translate("Settings", "Fullscreen")) self.btnThemeAdd.setText(_translate("Settings", "New")) self.btnThemeEdit.setText(_translate("Settings", "Edit")) self.btnThemeRemove.setText(_translate("Settings", "Delete")) diff --git a/manuskript/ui/settings_ui.ui b/manuskript/ui/settings_ui.ui index ce00565..194161d 100644 --- a/manuskript/ui/settings_ui.ui +++ b/manuskript/ui/settings_ui.ui @@ -54,7 +54,7 @@ - 2 + 5 @@ -78,15 +78,6 @@ 0 - - background-color:lightBlue; -border:none; -padding:10px; -color:darkBlue; -font-size:16px; -font-weight:bold; -text-align:center; - General settings @@ -464,15 +455,6 @@ text-align:center; 0 - - background-color:lightBlue; -border:none; -padding:10px; -color:darkBlue; -font-size:16px; -font-weight:bold; -text-align:center; - Revisions @@ -775,15 +757,6 @@ text-align:center; 0 - - background-color:lightBlue; -border:none; -padding:10px; -color:darkBlue; -font-size:16px; -font-weight:bold; -text-align:center; - Views settings @@ -2679,15 +2652,6 @@ text-align:center; 0 - - background-color:lightBlue; -border:none; -padding:10px; -color:darkBlue; -font-size:16px; -font-weight:bold; -text-align:center; - Labels @@ -2820,15 +2784,6 @@ text-align:center; 0 - - background-color:lightBlue; -border:none; -padding:10px; -color:darkBlue; -font-size:16px; -font-weight:bold; -text-align:center; - Status @@ -2896,22 +2851,13 @@ text-align:center; 0 - + 0 0 - - background-color:lightBlue; -border:none; -padding:10px; -color:darkBlue; -font-size:16px; -font-weight:bold; -text-align:center; - Fullscreen diff --git a/manuskript/ui/style.py b/manuskript/ui/style.py index 042d7b5..ba1d72f 100644 --- a/manuskript/ui/style.py +++ b/manuskript/ui/style.py @@ -8,6 +8,7 @@ from PyQt5.QtGui import QColor, QPalette from PyQt5.QtWidgets import qApp from manuskript import settings +from manuskript import functions as F # Loading palette colors. # Manuskript as to restart to reload @@ -16,6 +17,7 @@ p = qApp.palette() window = p.color(QPalette.Window).name() # General background windowText = p.color(QPalette.WindowText).name() # General foregroung base = p.color(QPalette.Base).name() # Other background +alternateBase = p.color(QPalette.AlternateBase).name() # Other background text = p.color(QPalette.Text).name() # Base Text brightText = p.color(QPalette.BrightText).name() # Contrast Text button = p.color(QPalette.Button).name() # Button background @@ -29,11 +31,17 @@ dark = p.color(QPalette.Dark).name() # Darker than Button mid = p.color(QPalette.Mid).name() # Between Button and Dark shadow = p.color(QPalette.Shadow).name() # A very dark color +highlightLight = F.mixColors(highlight, window, .3) +highlightedTextDark = F.mixColors(highlight, text, .3) +highlightedTextLight = F.mixColors(highlight, highlightedText) +midlighter = F.mixColors(mid, window, .4) +textLight = F.mixColors(window, text) + + +#from manuskript.ui import style as S +#QColor(S.highlightedTextDark) +#QColor(S.highlightLight) -bgHover = "#ccc" -bgChecked = "#bbb" -borderColor = "darkGray" -blue = "#268bd2" def mainWindowSS(): return """ @@ -44,13 +52,13 @@ def mainWindowSS(): border: none; }} QPushButton:flat:hover, QToolButton:hover{{ - border: 1px solid {borderColor}; + border: 1px solid {borderHover}; border-radius: 3px; - background: {bgHover}; + background: {backgroundHover}; }} """.format( - bgHover=bgHover, - borderColor=borderColor + backgroundHover=highlightLight, + borderHover=mid ) def styleMainWindow(mw): @@ -58,13 +66,34 @@ def styleMainWindow(mw): mw.lstTabs.verticalScrollBar().setStyleSheet(simpleScrollBarV()) # Custon palette? - qApp.setPalette(appPalette()) + #qApp.setPalette(appPalette()) mw.treeRedacOutline.setStyleSheet(""" QTreeView{ background: transparent; margin-top: 30px; - }""") + }""" + simpleScrollBarV()) + + mw.lstTabs.setStyleSheet(""" + QListView {{ + show-decoration-selected: 0; + outline: none; + background-color: transparent; + }} + + QListView::item:selected {{ + background: {highlight}; + color: {textSelected} + }} + + QListView::item:hover {{ + background: {hover}; + }} + """.format( + hover=highlight, + highlight=highlightLight, + textSelected=text, + )) def appPalette(): @@ -96,30 +125,30 @@ def appPalette(): def collapsibleGroupBoxButton(): s1 = """ - QPushButton{ + QPushButton{{ background-color: #BBB; border: none; padding: 2px; - } - QPushButton:checked, QPushButton:hover{ + }} + QPushButton:checked, QPushButton:hover{{ font-style:italic; - background-color:lightBlue; - }""" + background-color:{bg}; + }}""".format(bg=highlightLight) s2 = """ QPushButton{{ background-color: transparent; border: none; - border-top: 1px solid darkGray; + border-top: 1px solid {border}; padding: 4px 0px; font-weight: bold; }} QPushButton:hover{{ - background-color:{bgHover}; + background-color:{hover}; }} """.format( - bgHover=bgHover, - bgChecked=bgChecked + hover=highlightLight, + border=mid, ) return s2 @@ -183,7 +212,7 @@ def mainEditorTabSS(): QTabBar::tab:selected{{ border: 1px solid {borderColor}; background: {highlight}; - color: {text}; + color: {highlightedText}; }} QTabBar::tab:!selected:hover{{ background:{highlight}; @@ -197,45 +226,25 @@ def mainEditorTabSS(): ) # Add scrollbar - SS += """ - QScrollBar:vertical {{ - border: none; - background: transparent; - width: 10px; - }} - QScrollBar::handle {{ - background: {scrollBG}; - }} - QScrollBar::add-line:vertical {{ - width:0; - height: 0; - border: none; - background: none; - }} - - QScrollBar::sub-line:vertical {{ - width:0; - height: 0; - border: none; - background: none; - }} - """.format(scrollBG=button) + SS += simpleScrollBarV(handle=mid, width=10) return SS def toolBarSS(): return """ - QToolBar{ + QToolBar{{ background:transparent; border: 0; - border-left: 1px solid darkgray; + border-left: 1px solid {border}; spacing: 0px; - } - QToolBar:separator{ + }} + QToolBar:separator{{ border: none; - } - """ + }} + """.format( + border=midlighter, + ) def verticalToolButtonSS(): return """ @@ -255,25 +264,27 @@ def verticalToolButtonSS(): background: {bgHover}; }} """.format( - borderColor=borderColor, - bgChecked=bgChecked, - bgHover=bgHover + borderColor=mid, + bgChecked=midlighter, + bgHover=highlightLight, ) def dockSS(): + return """ QDockWidget::title {{ text-align: left; /* align the text to the left */ - background: {bgChecked}; + background: {header}; padding: 5px; }} QDockWidget::close-button, QDockWidget::float-button {{ - background: {bgChecked}; + background: {header}; }} """.format( - bgChecked=bgChecked + header=highlightLight, + button=button ) @@ -291,15 +302,15 @@ def lineEditSS(): # return "border-radius: 6px;" return """QLineEdit{{ border: none; - border-bottom: 1px solid {checked}; + border-bottom: 1px solid {line}; background:{window}; }} QLineEdit:focus{{ - border-bottom: 1px solid {blue}; + border-bottom: 1px solid {highlight}; }} """.format(window=window, - checked=bgChecked, - blue=blue) + line=mid, + highlight=highlight) def transparentSS(): @@ -309,25 +320,66 @@ def transparentSS(): border:none; }""" -def simpleScrollBarV(): +def simpleScrollBarV(handle=None, width=8): + # system default is (i think): mid background, dark handle + + default = midlighter + + handle = handle or default return """ - QScrollBar:vertical { + QScrollBar:vertical {{ border: none; - background: transparent; - width: 8px; - } - QScrollBar::handle { - background: rgba(180, 180, 180, 60%); - } - QScrollBar::add-line:vertical { + background: {background}; + width: {width}px; + }} + QScrollBar::handle {{ + background: {handle}; + }} + QScrollBar::add-line:vertical {{ width:0; height: 0; border: none; background: none; - } - QScrollBar::sub-line:vertical { + }} + + QScrollBar::sub-line:vertical {{ width:0; height: 0; border: none; background: none; - }""" + }}""".format( + background="transparent", + handle=handle, + width=width) + +def toolBoxSS(): + return """ + QToolBox::tab{{ + background-color: {background}; + padding: 2px; + border: none; + }} + + QToolBox::tab:selected, QToolBox::tab:hover{{ + background-color:{backgroundHover}; + color: {colorHover}; + }}""".format( + background=highlightLight, + backgroundHover=highlight, + colorHover=highlightedText, + ) + +def titleLabelSS(): + return """ + QLabel{{ + background-color:{bg}; + border:none; + padding:10px; + color:{text}; + font-size:16px; + font-weight:bold; + text-align:center; + }}""".format( + bg=highlightLight, + text=highlightedTextDark, + ) diff --git a/manuskript/ui/views/characterTreeView.py b/manuskript/ui/views/characterTreeView.py index 2bfbe55..a2a5025 100644 --- a/manuskript/ui/views/characterTreeView.py +++ b/manuskript/ui/views/characterTreeView.py @@ -6,6 +6,7 @@ from PyQt5.QtWidgets import QTreeWidget, QTreeWidgetItem, QColorDialog from manuskript.enums import Character from manuskript.functions import iconColor, mainWindow +from manuskript.ui import style as S class characterTreeView(QTreeWidget): @@ -90,8 +91,8 @@ class characterTreeView(QTreeWidget): for i in range(3): # Create category item cat = QTreeWidgetItem(self, [h[i]]) - cat.setBackground(0, QBrush(QColor(Qt.blue).lighter(190))) - cat.setForeground(0, QBrush(Qt.darkBlue)) + cat.setBackground(0, QBrush(QColor(S.highlightLight))) + cat.setForeground(0, QBrush(QColor(S.highlightedTextDark))) cat.setTextAlignment(0, Qt.AlignCenter) f = cat.font(0) f.setBold(True) diff --git a/manuskript/ui/views/cmbOutlineCharacterChoser.py b/manuskript/ui/views/cmbOutlineCharacterChoser.py index 4185aca..cfe07f8 100644 --- a/manuskript/ui/views/cmbOutlineCharacterChoser.py +++ b/manuskript/ui/views/cmbOutlineCharacterChoser.py @@ -6,6 +6,7 @@ from PyQt5.QtWidgets import QComboBox from manuskript.enums import Outline from manuskript.functions import toInt +from manuskript.ui import style as S class cmbOutlineCharacterChoser(QComboBox): @@ -36,8 +37,8 @@ class cmbOutlineCharacterChoser(QComboBox): for importance in range(3): self.addItem(l[importance]) - self.setItemData(self.count() - 1, QBrush(QColor(Qt.darkBlue)), Qt.ForegroundRole) - self.setItemData(self.count() - 1, QBrush(QColor(Qt.blue).lighter(190)), Qt.BackgroundRole) + self.setItemData(self.count() - 1, QBrush(QColor(S.highlightedTextDark)), Qt.ForegroundRole) + self.setItemData(self.count() - 1, QBrush(QColor(S.highlightLight)), Qt.BackgroundRole) item = self.model().item(self.count() - 1) item.setFlags(Qt.ItemIsEnabled) for i in range(self.mdlCharacters.rowCount()): diff --git a/manuskript/ui/views/outlineDelegates.py b/manuskript/ui/views/outlineDelegates.py index 215f99a..8995ec9 100644 --- a/manuskript/ui/views/outlineDelegates.py +++ b/manuskript/ui/views/outlineDelegates.py @@ -10,6 +10,7 @@ from manuskript.enums import Character, Outline from manuskript.functions import outlineItemColors, mixColors, colorifyPixmap, toInt, toFloat, drawProgress from manuskript.ui import style as S + class outlineTitleDelegate(QStyledItemDelegate): def __init__(self, parent=None): QStyledItemDelegate.__init__(self, parent) @@ -142,8 +143,8 @@ class outlineCharacterDelegate(QStyledItemDelegate): l = [self.tr("Main"), self.tr("Secondary"), self.tr("Minor")] for importance in range(3): editor.addItem(l[importance]) - editor.setItemData(editor.count() - 1, QBrush(Qt.darkBlue), Qt.ForegroundRole) - editor.setItemData(editor.count() - 1, QBrush(QColor(Qt.blue).lighter(190)), Qt.BackgroundRole) + editor.setItemData(editor.count() - 1, QBrush(QColor(S.highlightedTextDark)), Qt.ForegroundRole) + editor.setItemData(editor.count() - 1, QBrush(QColor(S.highlightLight)), Qt.BackgroundRole) item = editor.model().item(editor.count() - 1) item.setFlags(Qt.ItemIsEnabled) for i in range(self.mdlCharacter.rowCount()): diff --git a/manuskript/ui/views/plotTreeView.py b/manuskript/ui/views/plotTreeView.py index 31d4cfe..460aab1 100644 --- a/manuskript/ui/views/plotTreeView.py +++ b/manuskript/ui/views/plotTreeView.py @@ -8,6 +8,7 @@ from lxml import etree as ET from manuskript import settings from manuskript.enums import Plot, Outline, PlotStep from manuskript.models import references as Ref +from manuskript.ui import style as S class plotTreeView(QTreeWidget): @@ -126,8 +127,8 @@ class plotTreeView(QTreeWidget): h = [self.tr("Main"), self.tr("Secondary"), self.tr("Minor")] for i in range(3): cat = QTreeWidgetItem(self, [h[i]]) - cat.setBackground(0, QBrush(QColor(Qt.blue).lighter(190))) - cat.setForeground(0, QBrush(Qt.darkBlue)) + cat.setBackground(0, QBrush(QColor(S.highlightLight))) + cat.setForeground(0, QBrush(QColor(S.highlightedTextDark))) cat.setTextAlignment(0, Qt.AlignCenter) f = cat.font(0) f.setBold(True) diff --git a/manuskript/ui/views/treeDelegates.py b/manuskript/ui/views/treeDelegates.py index 6ed7a3b..9e8179b 100644 --- a/manuskript/ui/views/treeDelegates.py +++ b/manuskript/ui/views/treeDelegates.py @@ -142,10 +142,11 @@ class treeTitleDelegate(QStyledItemDelegate): f = painter.font() f.setWeight(QFont.Normal) painter.setFont(f) - col = mixColors( - QColor(S.window), - QColor(S.text)) - painter.setPen(col) #Qt.darkGray + if option.state & QStyle.State_Selected: + col = QColor(S.highlightedTextLight) + else: + col = QColor(S.textLight) + painter.setPen(col) painter.drawText(r, Qt.AlignLeft | Qt.AlignVCenter, extraText) painter.restore() diff --git a/manuskript/ui/welcome.py b/manuskript/ui/welcome.py index f0713dc..d48ebf4 100644 --- a/manuskript/ui/welcome.py +++ b/manuskript/ui/welcome.py @@ -20,6 +20,7 @@ from manuskript.models.outlineModel import outlineModel from manuskript.models.plotModel import plotModel from manuskript.models.worldModel import worldModel from manuskript.ui.welcome_ui import Ui_welcome +from manuskript.ui import style as S try: locale.setlocale(locale.LC_ALL, '') @@ -271,6 +272,7 @@ class welcome(QWidget, Ui_welcome): btn = QPushButton("", self) btn.setIcon(QIcon.fromTheme("edit-delete")) btn.setProperty("deleteRow", k) + btn.setFlat(True) btn.clicked.connect(self.deleteTemplateRow) self.lytTemplate.addWidget(btn, k, 3) @@ -335,8 +337,8 @@ class welcome(QWidget, Ui_welcome): def addTopLevelItem(self, name): item = QTreeWidgetItem(self.tree, [name]) - item.setBackground(0, QBrush(QColor(Qt.blue).lighter(190))) - item.setForeground(0, QBrush(Qt.darkBlue)) + item.setBackground(0, QBrush(QColor(S.highlightLight))) + item.setForeground(0, QBrush(QColor(S.highlightedTextDark))) item.setTextAlignment(0, Qt.AlignCenter) item.setFlags(Qt.ItemIsEnabled) f = item.font(0) From 31dcc6d53a7d02445534de6af96ce4ec40c319fa Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Tue, 14 Nov 2017 15:22:16 +0100 Subject: [PATCH 58/65] Adds better style colors of references --- manuskript/models/references.py | 14 ++++++++++++-- manuskript/ui/editors/basicHighlighter.py | 18 +++++++++--------- manuskript/ui/importers/importer_ui.py | 2 +- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/manuskript/models/references.py b/manuskript/models/references.py index 0223b76..66660f7 100644 --- a/manuskript/models/references.py +++ b/manuskript/models/references.py @@ -9,12 +9,16 @@ import re # A regex used to match references from PyQt5.QtWidgets import qApp +from PyQt5.QtGui import QColor +from PyQt5.QtCore import Qt from manuskript.enums import Outline from manuskript.enums import Character from manuskript.enums import Plot from manuskript.enums import PlotStep -from manuskript.functions import mainWindow +from manuskript.functions import mainWindow, mixColors +from manuskript.ui import style as S + RegEx = r"{(\w):(\d+):?.*?}" # A non-capturing regex used to identify references @@ -27,6 +31,12 @@ TextLetter = "T" PlotLetter = "P" WorldLetter = "W" +# Colors +TextHighlightColor = QColor(mixColors(QColor(Qt.blue).name(), S.window, .3)) +CharacterHighlightColor = QColor(mixColors(QColor(Qt.yellow).name(), S.window, .3)) +PlotHighlightColor = QColor(mixColors(QColor(Qt.red).name(), S.window, .3)) +WorldHighlightColor = QColor(mixColors(QColor(Qt.green).name(), S.window, .3)) + def plotReference(ID, searchable=False): """Takes the ID of a plot and returns a reference for that plot. @@ -69,7 +79,7 @@ def worldReference(ID, searchable=False): ############################################################################### def infos(ref): - """Returns a full paragraph in HTML format + """Returns a full paragraph in HTML format containing detailed infos about the reference ``ref``. """ match = re.fullmatch(RegEx, ref) diff --git a/manuskript/ui/editors/basicHighlighter.py b/manuskript/ui/editors/basicHighlighter.py index 1b69033..a09b53a 100644 --- a/manuskript/ui/editors/basicHighlighter.py +++ b/manuskript/ui/editors/basicHighlighter.py @@ -37,7 +37,7 @@ class basicHighlighter(QSyntaxHighlighter): def highlightBlockBefore(self, text): """Highlighting to do before anything else. - + When subclassing basicHighlighter, you must call highlightBlockBefore before you do any custom highlighting. """ @@ -55,7 +55,7 @@ class basicHighlighter(QSyntaxHighlighter): def highlightBlockAfter(self, text): """Highlighting to do after everything else. - + When subclassing basicHighlighter, you must call highlightBlockAfter after your custom highlighting. """ @@ -65,22 +65,22 @@ class basicHighlighter(QSyntaxHighlighter): fmt = self.format(txt.start()) fmt.setFontFixedPitch(True) fmt.setFontWeight(QFont.DemiBold) - fmt.setForeground(Qt.black) # or text becomes unreadable in some color scheme + if txt.group(1) == Ref.TextLetter: - fmt.setBackground(QBrush(QColor(Qt.blue).lighter(190))) + fmt.setBackground(QBrush(Ref.TextHighlightColor)) elif txt.group(1) == Ref.CharacterLetter: - fmt.setBackground(QBrush(QColor(Qt.yellow).lighter(170))) + fmt.setBackground(QBrush(Ref.CharacterHighlightColor)) elif txt.group(1) == Ref.PlotLetter: - fmt.setBackground(QBrush(QColor(Qt.red).lighter(170))) + fmt.setBackground(QBrush(Ref.PlotHighlightColor)) elif txt.group(1) == Ref.WorldLetter: - fmt.setBackground(QBrush(QColor(Qt.green).lighter(170))) + fmt.setBackground(QBrush(Ref.WorldHighlightColor)) self.setFormat(txt.start(), txt.end() - txt.start(), fmt) # Spell checking - + # Following algorithm would not check words at the end of line. # This hacks adds a space to every line where the text cursor is not # So that it doesn't spellcheck while typing, but still spellchecks at @@ -89,7 +89,7 @@ class basicHighlighter(QSyntaxHighlighter): if self.currentBlock().position() + len(text) != \ self.editor.textCursor().position(): textedText = text + " " - + # Based on http://john.nachtimwald.com/2009/08/22/qplaintextedit-with-in-line-spell-check/ WORDS = '(?iu)([\w\']+)[^\'\w]' # (?iu) means case insensitive and unicode if hasattr(self.editor, "spellcheck") and self.editor.spellcheck: diff --git a/manuskript/ui/importers/importer_ui.py b/manuskript/ui/importers/importer_ui.py index 327914c..d23c8e4 100644 --- a/manuskript/ui/importers/importer_ui.py +++ b/manuskript/ui/importers/importer_ui.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'manuskript/ui/importers/importer_ui.ui' # -# Created by: PyQt5 UI code generator 5.5.1 +# Created by: PyQt5 UI code generator 5.9 # # WARNING! All changes made in this file will be lost! From 170c8ef4045ea621fd6f1ecf5259cdbe71feaea7 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Tue, 14 Nov 2017 18:11:50 +0100 Subject: [PATCH 59/65] Fixes #194: Editor tab should trim long titles --- manuskript/ui/editors/editorWidget.py | 12 +++++++++++- manuskript/ui/editors/mainEditor.py | 3 ++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/manuskript/ui/editors/editorWidget.py b/manuskript/ui/editors/editorWidget.py index 623b62a..b23d349 100644 --- a/manuskript/ui/editors/editorWidget.py +++ b/manuskript/ui/editors/editorWidget.py @@ -40,6 +40,8 @@ class editorWidget(QWidget, Ui_editorWidget_ui): toggledSpellcheck = pyqtSignal(bool) dictChanged = pyqtSignal(str) + _maxTabTitleLength = 24 + def __init__(self, parent): QWidget.__init__(self, parent) self.setupUi(self) @@ -113,7 +115,15 @@ class editorWidget(QWidget, Ui_editorWidget_ui): return i = self._tabWidget.indexOf(self) - self._tabWidget.setTabText(i, item.title()) + + self._tabWidget.setTabText(i, self.ellidedTitle(item.title())) + self._tabWidget.setTabToolTip(i, item.title()) + + def ellidedTitle(self, title): + if len(title) > self._maxTabTitleLength: + return "{}…".format(title[:self._maxTabTitleLength]) + else: + return title def setView(self): # index = mainWindow().treeRedacOutline.currentIndex() diff --git a/manuskript/ui/editors/mainEditor.py b/manuskript/ui/editors/mainEditor.py index d410e28..1eafe22 100644 --- a/manuskript/ui/editors/mainEditor.py +++ b/manuskript/ui/editors/mainEditor.py @@ -220,7 +220,8 @@ class mainEditor(QWidget, Ui_mainEditor): editor = editorWidget(self) editor.setCurrentModelIndex(index) editor._tabWidget = tabWidget - tabWidget.addTab(editor, title) + i = tabWidget.addTab(editor, editor.ellidedTitle(title)) + tabWidget.setTabToolTip(i, title) tabWidget.setCurrentIndex(tabWidget.count() - 1) else: self.currentEditor(tabWidget).setCurrentModelIndex(index) From 0dfe3fe6fba1ae891ed6e45067fe7e4dd31dfa3a Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Sat, 18 Nov 2017 09:27:41 +0100 Subject: [PATCH 60/65] Adds manuskript SVG icon --- icons/Manuskript/manuskript.svg | 109 ++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 icons/Manuskript/manuskript.svg diff --git a/icons/Manuskript/manuskript.svg b/icons/Manuskript/manuskript.svg new file mode 100644 index 0000000..596b407 --- /dev/null +++ b/icons/Manuskript/manuskript.svg @@ -0,0 +1,109 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + From ea0504d6a91dd1d498e67137cb9209ec75b79488 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Tue, 21 Nov 2017 22:49:38 +0100 Subject: [PATCH 61/65] Adds: automated script to build deb packages #207 --- package/create_deb.sh | 58 +++++++++++++++++++++++++++ package/create_deb/control | 19 +++++++++ package/create_deb/manuskript | 30 ++++++++++++++ package/create_deb/manuskript.desktop | 9 +++++ 4 files changed, 116 insertions(+) create mode 100755 package/create_deb.sh create mode 100644 package/create_deb/control create mode 100644 package/create_deb/manuskript create mode 100644 package/create_deb/manuskript.desktop diff --git a/package/create_deb.sh b/package/create_deb.sh new file mode 100755 index 0000000..e9f88b9 --- /dev/null +++ b/package/create_deb.sh @@ -0,0 +1,58 @@ +#!/bin/bash + + +# Manuskript Vars +AppName=manuskript +AppVersion=0.5.0 +PkgNumber=1 +PkgVersion=0.5.0-1 +#PkgSizeInKb # find with: du -sk manuskript-0.5.0-1 + +# Program vars +ScriptPath="$( cd "$(dirname "$0")" ; pwd -P )" +Dest="$ScriptPath/../dist/$AppName-$PkgVersion" + +echo Package directory: $Dest + +echo -n Creating folder structure +mkdir -p $Dest/DEBIAN +mkdir -p $Dest/usr/bin +mkdir -p $Dest/usr/share/applications +echo " [✓]" + +# Getting manuskript files, by downloading +# pushd $Dest/usr/share +# wget https://github.com/olivierkes/manuskript/archive/$AppVersion.tar.gz +# tar -xvf $AppVersion.tar.gz +# rm $AppVersion.tar.gz +# mv manuskript-0.5.0 manuskript +# popd + +# Using the current direction as source + +echo -n Copying manuskript content +rsync -a --exclude=.git --include="*.msk" --exclude-from=.gitignore $ScriptPath/../ $Dest/usr/share/manuskript +cp $ScriptPath/create_deb/manuskript $Dest/usr/bin/manuskript +cp $ScriptPath/create_deb/manuskript.desktop $Dest/usr/share/applications/manuskript.desktop +cp $ScriptPath/create_deb/control $Dest/DEBIAN/control + +sed -i "s/{PkgVersion}/$PkgVersion/" $Dest/DEBIAN/control +PkgSizeInKb=$(du -sk $Dest | cut -f 1) +sed -i "s/{PkgSizeInKb}/$PkgSizeInKb/" $Dest/DEBIAN/control +echo " [✓]" + +echo -n Setting permissions +chmod 0755 $Dest/usr/bin/manuskript +echo " [✓]" + +echo Your root password might now be asked to finish setting permissions: +sudo chown root:root -R $Dest + +echo Creating the package… +dpkg -b $Dest + +echo -n Removing build folder +sudo rm -r $Dest +echo " [✓]" + +echo Done ! diff --git a/package/create_deb/control b/package/create_deb/control new file mode 100644 index 0000000..0a4ac12 --- /dev/null +++ b/package/create_deb/control @@ -0,0 +1,19 @@ +Package: manuskript +Version: {PkgVersion} +Maintainer: Curtis Gedak +Description: Manuskript open source tool for writers. + Manuskript is an open source tool for writers. It + provides a rich environment to help writers create + their first draft and then further refine and edit + their masterpiece. +Section: office, text +Priority: optional +Installed-Size: {PkgSizeInKb} +Architecture: all +Origin: Debian 8 Jessie +Bugs: https://github.com/olivierkes/manuskript/issues +Homepage: http://www.theologeek.ch/manuskript/ +Source: https://github.com/olivierkes/manuskript/archive/0.5.0.tar.gz +Depends: python3, python3-pyqt5, python3-pyqt5.qtwebkit, libqt5svg5, + python3-lxml, zlib1g, python3-enchant, python3-markdown, pandoc +Suggests: texlive-latex-recommended diff --git a/package/create_deb/manuskript b/package/create_deb/manuskript new file mode 100644 index 0000000..33e83fc --- /dev/null +++ b/package/create_deb/manuskript @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# manuskript - invocation python script +# +# Copyright (C) 2017 Olivier Keshavjee +# +# This file is part of Manuskript. +# +# Manuskript is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Manuskript is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Manuskript. If not, see . + +import os +import sys + +sys.path.insert(1, '/usr/share/manuskript/') + +from manuskript import main + +main.run() diff --git a/package/create_deb/manuskript.desktop b/package/create_deb/manuskript.desktop new file mode 100644 index 0000000..c3414f8 --- /dev/null +++ b/package/create_deb/manuskript.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Name=Manuskript +Comment=An open source tool for writers +Keywords=manuskript;office;write;edit;novel;text;msk +Exec=/usr/bin/manuskript +Terminal=false +Type=Application +Icon=/usr/share/manuskript/icons/Manuskript/icon-512px.png +Categories=Office;WordProcessor; From 85e7264e1e3c60c196091d3f11b56b5ad4e06a12 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Tue, 21 Nov 2017 23:20:20 +0100 Subject: [PATCH 62/65] Updates deb package origin to Ubuntu 14.04. --- package/create_deb/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/create_deb/control b/package/create_deb/control index 0a4ac12..a8e3296 100644 --- a/package/create_deb/control +++ b/package/create_deb/control @@ -10,7 +10,7 @@ Section: office, text Priority: optional Installed-Size: {PkgSizeInKb} Architecture: all -Origin: Debian 8 Jessie +Origin: Ubuntu 14.04 Bugs: https://github.com/olivierkes/manuskript/issues Homepage: http://www.theologeek.ch/manuskript/ Source: https://github.com/olivierkes/manuskript/archive/0.5.0.tar.gz From 45efab975049c7483182f06008010d000331e88e Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Wed, 22 Nov 2017 00:08:43 +0100 Subject: [PATCH 63/65] Deb build: adds customizable build values --- package/create_deb.sh | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/package/create_deb.sh b/package/create_deb.sh index e9f88b9..f9773f0 100755 --- a/package/create_deb.sh +++ b/package/create_deb.sh @@ -1,11 +1,13 @@ #!/bin/bash +# Can take two parameters: AppVersion PkgNumber +# Default values are: 0.5.0 1 # Manuskript Vars AppName=manuskript -AppVersion=0.5.0 -PkgNumber=1 -PkgVersion=0.5.0-1 +AppVersion=${1:-0.5.0} +PkgNumber=${2:-1} +PkgVersion=$AppVersion-$PkgNumber #PkgSizeInKb # find with: du -sk manuskript-0.5.0-1 # Program vars From 6cc886493756b8108c64e993a33f8d011270c457 Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Wed, 22 Nov 2017 08:52:30 +0100 Subject: [PATCH 64/65] Fixes #225: Manuskript fails to run in Ubuntu 14.04 --- manuskript/ui/welcome.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/manuskript/ui/welcome.py b/manuskript/ui/welcome.py index 9ca5579..d3858e8 100644 --- a/manuskript/ui/welcome.py +++ b/manuskript/ui/welcome.py @@ -68,8 +68,15 @@ class welcome(QWidget, Ui_welcome): self.mw.loadProject(last) def getAutoLoadValues(self): + """ + Reads manuskript system's settings and returns a tupple: + - `bool`: whether manuskript should automatically load + the last openend project or display the + welcome widget. + - `str`: the absolute path to the last opened project. + """ sttgns = QSettings() - autoLoad = sttgns.value("autoLoad", type=bool) + autoLoad = sttgns.value("autoLoad", defaultValue=False, type=bool) if autoLoad and sttgns.contains("lastProject"): last = sttgns.value("lastProject") else: From 550f8892515519eb6c00c6c5eaec0d247a94feec Mon Sep 17 00:00:00 2001 From: Olivier Keshavjee Date: Wed, 22 Nov 2017 09:37:02 +0100 Subject: [PATCH 65/65] Fixes: some Documents menu calls would crash manuskript if in textEdit --- manuskript/ui/views/textEditView.py | 41 ++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/manuskript/ui/views/textEditView.py b/manuskript/ui/views/textEditView.py index bb4a6c4..ce7cf72 100644 --- a/manuskript/ui/views/textEditView.py +++ b/manuskript/ui/views/textEditView.py @@ -8,8 +8,7 @@ from PyQt5.QtWidgets import QWidget, QTextEdit, qApp, QAction, QMenu from manuskript import settings from manuskript.enums import Outline -from manuskript.functions import AUC, themeIcon -from manuskript.functions import toString +from manuskript import functions as F from manuskript.models.outlineModel import outlineModel from manuskript.ui.editors.MDFunctions import MDFormatSelection from manuskript.ui.editors.MMDHighlighter import MMDHighlighter @@ -50,7 +49,7 @@ class textEditView(QTextEdit): self.highligtCS = False self.defaultFontPointSize = qApp.font().pointSize() self._dict = None - # self.document().contentsChanged.connect(self.submit, AUC) + # self.document().contentsChanged.connect(self.submit, F.AUC) # Submit text changed only after 500ms without modifications self.updateTimer = QTimer() @@ -60,7 +59,7 @@ class textEditView(QTextEdit): # self.updateTimer.timeout.connect(lambda: print("Timeout")) self.updateTimer.stop() - self.document().contentsChanged.connect(self.updateTimer.start, AUC) + self.document().contentsChanged.connect(self.updateTimer.start, F.AUC) # self.document().contentsChanged.connect(lambda: print("Document changed")) # self.document().contentsChanged.connect(lambda: print(self.objectName(), "Contents changed")) @@ -100,11 +99,11 @@ class textEditView(QTextEdit): def setModel(self, model): self._model = model try: - self._model.dataChanged.connect(self.update, AUC) + self._model.dataChanged.connect(self.update, F.AUC) except TypeError: pass try: - self._model.rowsAboutToBeRemoved.connect(self.rowsAboutToBeRemoved, AUC) + self._model.rowsAboutToBeRemoved.connect(self.rowsAboutToBeRemoved, F.AUC) except TypeError: pass @@ -308,7 +307,7 @@ class textEditView(QTextEdit): pass def reconnectDocument(self): - self.document().contentsChanged.connect(self.updateTimer.start, AUC) + self.document().contentsChanged.connect(self.updateTimer.start, F.AUC) def updateText(self): if self._updating: @@ -317,9 +316,9 @@ class textEditView(QTextEdit): self._updating = True if self._index: self.disconnectDocument() - if self.toPlainText() != toString(self._model.data(self._index)): + if self.toPlainText() != F.toString(self._model.data(self._index)): # print(" Updating plaintext") - self.document().setPlainText(toString(self._model.data(self._index))) + self.document().setPlainText(F.toString(self._model.data(self._index))) self.reconnectDocument() elif self._indexes: @@ -328,7 +327,7 @@ class textEditView(QTextEdit): same = True for i in self._indexes: item = i.internalPointer() - t.append(toString(item.data(self._column))) + t.append(F.toString(item.data(self._column))) for t2 in t[1:]: if t2 != t[0]: @@ -364,7 +363,7 @@ class textEditView(QTextEdit): self._updating = True for i in self._indexes: item = i.internalPointer() - if self.toPlainText() != toString(item.data(self._column)): + if self.toPlainText() != F.toString(item.data(self._column)): print("Submitting many indexes") self._model.setData(i, self.toPlainText()) self._updating = False @@ -466,7 +465,7 @@ class textEditView(QTextEdit): selectedWord = cursor.selectedText() if not valid: spell_menu = QMenu(self.tr('Spelling Suggestions'), self) - spell_menu.setIcon(themeIcon("spelling")) + spell_menu.setIcon(F.themeIcon("spelling")) for word in self._dict.suggest(text): action = self.SpellAction(word, spell_menu) action.correct.connect(self.correctWord) @@ -553,3 +552,21 @@ class textEditView(QTextEdit): MDFormatSelection(self, 2) elif _format == "Clear": MDFormatSelection(self) + + ############################################################################### + # KEYBOARD SHORTCUTS + ############################################################################### + + def callMainTreeView(self, functionName): + """ + The tree view in mainwindow must have same index as the text + edit that has focus. So we can pass it the call for documents + edits like: duplicate, move up, etc. + """ + if self._index and self._column == Outline.text.value: + function = getattr(F.mainWindow().treeRedacOutline, functionName) + function() + + def duplicate(self): self.callMainTreeView("duplicate") + def moveUp(self): self.callMainTreeView("moveUp") + def moveDown(self): self.callMainTreeView("moveDown")