diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..90555f1 --- /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: lKuWQ9NWkLfDkkAiSnMh2PYkAGE1xh4pkSN5Ceb2IF9ee9i9YrJ9YFcvh420JSP+BYUl1OKczC5b3d9hUUZcfMwUkuZqPFCehOWP0O8dNs3XKZtmSU4POwR8lx7jRSO132qV/zgthBTK8PbeC2jIiMF4+4ESMsuEDptmGSOhgHtOcdJNDNz8gTbygXZVcl4U04se1ywtL62UQcTNMEKKLeBYQlrAXpcPiw4Htv9spEx6BewgarhRvJ/hysqeJgrH4wUVcjFw6Djppu3fmhrvgtaIU3ONxMLqgCuBZa03Q2LuF/RTYl9/DUgIjqsm1aOVooQZttYsUYWrLfyZNnLGD4WkdILcTMyexEVpQ/ejcEPm8gCf2PtklRtENZIxV2eQkLcPuSAPGWC8ue6a1etIUbYDbMT47SsdwkUsbyPpT8bnBvPf7gfmH/e20b1UQtxgmE5GDpbYZCTHf0kvwIFrBQzNmCtiGsXGJxIVx4msndplh8MdWFDBTEr0Ca8Tt45Fp/QPU7KAmdlQog9fWOfnJezIlBpFAXDa+AN2b/9uE+led5fmqQ62nuvjsYvWmJti2NW0IJ8UI26JGfh4Z1offE2bYp4onimfbRXSXWQs+Dm2l4CdTkc3habSWoUMw2R8mGbEfgfhzTFiAryg1mCtWy8AxUgfcUurd6BCwipH1ck= + file: dist/$FILENAME + overwrite: true + skip_cleanup: true + on: + tags: true diff --git a/i18n/manuskript_de.qm b/i18n/manuskript_de.qm index 7c4ce7f..21df451 100644 Binary files a/i18n/manuskript_de.qm and b/i18n/manuskript_de.qm differ 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 diff --git a/i18n/manuskript_sv.qm b/i18n/manuskript_sv.qm index 82eccc8..a4fd370 100644 Binary files a/i18n/manuskript_sv.qm and b/i18n/manuskript_sv.qm differ 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 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 + + + + + + + + + + + + + + + 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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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 "" - diff --git a/manuskript/exporter/pandoc/__init__.py b/manuskript/exporter/pandoc/__init__.py index d36e78d..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) @@ -40,7 +42,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 "" diff --git a/manuskript/functions.py b/manuskript/functions.py index b7873ad..0a94e82 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 @@ -24,9 +26,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): @@ -44,8 +49,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))) @@ -142,14 +148,24 @@ 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): + + from manuskript.ui import style as S + """Takes an OutlineItem and returns a dict of colors.""" colors = {} mw = mainWindow() @@ -181,9 +197,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(Qt.transparent) # will use default return colors @@ -229,14 +245,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)) @@ -273,17 +281,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. @@ -355,3 +352,11 @@ def customIcons(): ] return sorted(r) + + +def statusMessage(message, duration=5000): + mainWindow().statusBar().showMessage(message, duration) + + +def openURL(url): + QDesktopServices.openUrl(QUrl(url)) diff --git a/manuskript/importer/__init__.py b/manuskript/importer/__init__.py new file mode 100644 index 0000000..16e4b7e --- /dev/null +++ b/manuskript/importer/__init__.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- + +from manuskript.importer.folderImporter import folderImporter +from manuskript.importer.markdownImporter import markdownImporter +from manuskript.importer.opmlImporter import opmlImporter +from manuskript.importer.mindMapImporter import mindMapImporter +from manuskript.importer.pandocImporters import markdownPandocImporter, \ + odtPandocImporter, ePubPandocImporter, docXPandocImporter, HTMLPandocImporter, \ + rstPandocImporter, LaTeXPandocImporter, OPMLPandocImporter + +importers = [ + # Internal + markdownImporter, + folderImporter, + opmlImporter, + mindMapImporter, + + # Pandoc + markdownPandocImporter, + odtPandocImporter, + ePubPandocImporter, + docXPandocImporter, + HTMLPandocImporter, + rstPandocImporter, + LaTeXPandocImporter, + OPMLPandocImporter, + ] diff --git a/manuskript/importer/abstractImporter.py b/manuskript/importer/abstractImporter.py new file mode 100644 index 0000000..ba810af --- /dev/null +++ b/manuskript/importer/abstractImporter.py @@ -0,0 +1,210 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- +import os +import shutil +import subprocess + +from PyQt5.QtCore import QSettings +from PyQt5.QtWidgets import QWidget, QVBoxLayout, QCheckBox, QHBoxLayout, \ + QLabel, QSpinBox, QComboBox, QLineEdit +from manuskript.ui.collapsibleGroupBox2 import collapsibleGroupBox2 +from manuskript.ui import style + + +class abstractImporter: + """ + abstractImporter is used to import documents into manuskript. + + The startImport function must be subclassed. It takes a filePath (str to + the document to import), and must return `outlineItem`s. + """ + + name = "" + description = "" + fileFormat = "" # File format accepted. For example: "OPML Files (*.opml)" + # For folder, use "<>" + icon = "" + engine = "Internal" + + def __init__(self): + self.settingsList = [] # Keep the name of the settings in order + self.settings = {} + + def startImport(self, filePath, parentItem, settingsWidget): + """ + Takes a str path to the file/folder to import, and the settingsWidget + returnend by `self.settingsWidget()` containing the user set settings, + and return `outlineItem`s. + """ + pass + + @classmethod + def isValid(cls): + return False + + + def settingsWidget(self, widget): + """ + Takes a QWidget that can be modified and must be returned. + """ + return widget + + def addPage(self, widget, title): + """ + Convenience function to add a page to the settingsWidget `widget`, at + the end. + + Returns the page widget. + """ + w = QWidget(widget) + w.setLayout(QVBoxLayout()) + widget.toolBox.insertItem(widget.toolBox.count(), w, title) + widget.toolBox.layout().setSpacing(0) + return w + + def addGroup(self, parent, title): + """ + Adds a collapsible group to the given widget. + """ + g = collapsibleGroupBox2(title=title) + parent.layout().addWidget(g) + g.setLayout(QVBoxLayout()) + return g + + def addSetting(self, name, type, label, widget=None, default=None, + tooltip=None, min=None, max=None, vals=None, suffix=""): + + self.settingsList.append(name) + 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] + + 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: + """ + A class used to store setting, and display a widget for the user to + modify it. + """ + def __init__(self, name, type, label, widget=None, default=None, + tooltip=None, min=None, max=None, vals=None, suffix=""): + self.name = name + self.type = type + self.label = label + self._widget = widget + self.default = default + self.min = min + self.max = max + self.vals = vals.split("|") if vals else [] + self.suffix = suffix + self.tooltip = tooltip + + def widget(self, parent=None): + """ + Returns the widget used, or creates it if not done yet. If parent + is given, widget is inserted in parent's layout. + """ + if self._widget: + return self._widget + + else: + + if "checkbox" in self.type: + self._widget = QCheckBox(self.label) + if self.default: + self._widget.setChecked(True) + if parent: + parent.layout().addWidget(self._widget) + + elif "number" in self.type: + l = QHBoxLayout() + label = QLabel(self.label, parent) + label.setWordWrap(True) + l.addWidget(label, 8) + self._widget = QSpinBox() + self._widget.setValue(self.default if self.default else 0) + if self.min: + self._widget.setMinimum(self.min) + if self.max: + self._widget.setMaximum(self.max) + if self.suffix: + self._widget.setSuffix(self.suffix) + l.addWidget(self._widget, 2) + if parent: + parent.layout().addLayout(l) + + elif "combo" in self.type: + l = QHBoxLayout() + label = QLabel(self.label, parent) + label.setWordWrap(True) + l.addWidget(label, 6) + self._widget = QComboBox() + self._widget.addItems(self.vals) + if self.default: + self._widget.setCurrentText(self.default) + l.addWidget(self._widget, 2) + if parent: + parent.layout().addLayout(l) + + elif "text" in self.type: + l = QHBoxLayout() + label = QLabel(self.label, parent) + label.setWordWrap(True) + l.addWidget(label, 5) + self._widget = QLineEdit() + self._widget.setStyleSheet(style.lineEditSS()) + if self.default: + self._widget.setText(self.default) + l.addWidget(self._widget, 3) + if parent: + parent.layout().addLayout(l) + + elif "label" in self.type: + self._widget = QLabel(self.label, parent) + self._widget.setWordWrap(True) + if parent: + parent.layout().addWidget(self._widget) + + if self.tooltip: + self._widget.setToolTip(self.tooltip) + + return self._widget + + def value(self): + """ + Return the value contained in the widget. + """ + if not self._widget: + return self.default + + else: + + if "checkbox" in self.type: + return self._widget.isChecked() + + elif "number" in self.type: + return self._widget.value() + + elif "combo" in self.type: + return self._widget.currentText() + + elif "text" in self.type: + return self._widget.text() + + + diff --git a/manuskript/importer/folderImporter.py b/manuskript/importer/folderImporter.py new file mode 100644 index 0000000..6789dc2 --- /dev/null +++ b/manuskript/importer/folderImporter.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- + +import os +from manuskript.importer.abstractImporter import abstractImporter +from manuskript.models.outlineModel import outlineItem +from manuskript.enums import Outline +from PyQt5.QtWidgets import qApp + + +class folderImporter(abstractImporter): + + name = "Folder" + description = "" + fileFormat = "<>" + 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("*", "").lower() for e in ext.split(",")] + + sorting = self.getSetting("sortItems").value() + + items = [] + stack = {} + + for dirpath, dirnames, filenames in os.walk(filePath): + + if dirpath in stack: + item = stack[dirpath] + else: + # It's the parent folder, and we are not including it + # so every item is attached to parentItem + item = parentItem + + def addFile(f): + fName, fExt = os.path.splitext(f) + if fExt.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) + 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.

+

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

""")) + + self.addSetting("ext", "text", + qApp.translate("Import", "Include only those extensions:"), + default="*.txt, *.md", + tooltip=qApp.translate("Import", "Coma separated values")), + + self.addSetting("sortItems", "checkbox", + qApp.translate("Import", "Sort items by name"), + default=True), + + self.addSetting("separateFolderFiles", "checkbox", + qApp.translate("Import", "Import folder then files"), + default=True), + + self.addSettingsTo(group) + + return widget + + + + diff --git a/manuskript/importer/markdownImporter.py b/manuskript/importer/markdownImporter.py new file mode 100644 index 0000000..a57b6be --- /dev/null +++ b/manuskript/importer/markdownImporter.py @@ -0,0 +1,187 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- + +from manuskript.importer.abstractImporter import abstractImporter +from manuskript.models.outlineModel import outlineItem +from manuskript.enums import Outline +from PyQt5.QtWidgets import qApp +import re, os + + +class markdownImporter(abstractImporter): + + name = "Markdown" + description = "" + fileFormat = "Markdown files (*.md *.txt *)" + icon = "text-x-markdown" + + @classmethod + def isValid(cls): + return True + + def startImport(self, filePath, parentItem, settingsWidget, fromString=None): + """ + Very simple import from markdown. We just look at ATX headers (we + ignore setext for the sake of simplicity, for now.) + + **A difficulty:** in the following example, we can do things with + markdown headers (like go from level 1 to level 4 and back to level 2) + that we cannot do in an outline. + + ``` + # Level 1 + # Level 1 + ## Level 2 + ### Level 3 + #### Level 4 + ##### Level 5 + ### Level 3 + # Level 1 + #### Level 4? → Level 2 + ### Level 3? → Level 2 + ## Level 2 → Level 2 + #### Level 4? → Level 3 + ``` + + I think the current version of the imported manages that quite well. + + **A question:** In the following sample, the first Level 1 becomes a + text element, because it has no other sub elements. But the content of + second Level 1 becomes a text element, with no name. What name should + we give it? + + ``` + # Level 1 + Some texte content. + Level 1 will become a text element. + # Level 1 + This content has no name. + ## Level 2 + ... + ``` + """ + + if not fromString: + # Read file + with open(filePath, "r") as f: + txt = f.read() + else: + txt = fromString + + items = [] + + parent = parentItem + lastLevel = 0 + content = "" + + def saveContent(content, parent): + if content.strip(): + child = outlineItem(title=parent.title(), parent=parent, _type="md") + child._data[Outline.text] = content + items.append(child) + return "" + + def addTitle(name, parent, level): + child = outlineItem(title=name, parent=parent) + child.__miLevel = level + items.append(child) + return child + + ATXHeader = re.compile(r"(\#+)\s*(.+?)\s*\#*$") + setextHeader1 = re.compile(r"([^\#-=].+)\n(===+)$", re.MULTILINE) + setextHeader2 = re.compile(r"([^\#-=].+)\n(---+)$", re.MULTILINE) + + # We store the level of each item in a temporary var + parent.__miLevel = 0 # markdown importer header level + + txt = txt.split("\n") + skipNextLine = False + for i in range(len(txt)): + + l = txt[i] + l2 = "\n".join(txt[i:i+2]) + + header = False + + if skipNextLine: + # Last line was a setext-style header. + skipNextLine = False + continue + + # Check ATX Header + m = ATXHeader.match(l) + if m: + header = True + level = len(m.group(1)) + name = m.group(2) + + # Check setext header + m = setextHeader1.match(l2) + + if not header and m and len(m.group(1)) == len(m.group(2)): + header = True + level = 1 + name = m.group(1) + skipNextLine = True + + m = setextHeader2.match(l2) + if not header and m and len(m.group(1)) == len(m.group(2)): + header = True + level = 2 + name = m.group(1) + skipNextLine = True + + if header: + + # save content + content = saveContent(content, parent) + + # get parent level + while parent.__miLevel >= level: + parent = parent.parent() + + # create title + child = addTitle(name, parent, level) + child.__miLevel = level + + # title becomes the new parent + parent = child + + lastLevel = level + + else: + content += l + "\n" + + saveContent(content, parent) + + # Clean up + for i in items: + if i.childCount() == 1 and i.children()[0].isText(): + # We have a folder with only one text item + # So we make it a text item + i._data[Outline.type] = "md" + i._data[Outline.text] = i.children()[0].text() + c = i.removeChild(0) + items.remove(c) + + return items + + def settingsWidget(self, widget): + """ + Takes a QWidget that can be modified and must be returned. + """ + + # Add group + group = self.addGroup(widget.toolBox.widget(0), + qApp.translate("Import", "Markdown import")) + #group = cls.addPage(widget, "Folder import") + + self.addSetting("info", "label", + qApp.translate("Import", """Info: A very simple + parser that will go through a markdown document and + create items for each titles.
 """)) + + for s in self.settings: + self.settings[s].widget(group) + + return widget diff --git a/manuskript/importer/mindMapImporter.py b/manuskript/importer/mindMapImporter.py new file mode 100644 index 0000000..6d377ca --- /dev/null +++ b/manuskript/importer/mindMapImporter.py @@ -0,0 +1,146 @@ +#!/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 +from manuskript.converters import HTML2MD, HTML2PlainText + +class mindMapImporter(abstractImporter): + + name = "Mind Map" + description = "" + fileFormat = "Mind map Files (*.mm)" + icon = "text-x-opml+xml" + + @classmethod + def isValid(cls): + return True + + def startImport(self, 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(self.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 + + 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="Text|Folder", + ) + + for s in self.settings: + self.settings[s].widget(group) + + return widget + + def parseItems(self, underElement, parentItem=None): + items = [] + + # Title + title = underElement.get('TEXT', "").replace("\n", " ") + if not title: + title = qApp.translate("Import", "Untitled") + + 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/importer/opmlImporter.py b/manuskript/importer/opmlImporter.py new file mode 100644 index 0000000..b7b8b99 --- /dev/null +++ b/manuskript/importer/opmlImporter.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- + +from PyQt5.QtWidgets import qApp, QMessageBox +from manuskript.models.outlineModel import outlineItem +from manuskript.enums import Outline +from lxml import etree as ET +from manuskript.functions import mainWindow +from manuskript.importer.abstractImporter import abstractImporter + +class opmlImporter(abstractImporter): + + name = "OPML" + description = "" + fileFormat = "OPML Files (*.opml *.xml)" + icon = "text-x-opml+xml" + + @classmethod + def isValid(cls): + return True + + @classmethod + def startImport(cls, filePath, parentItem, settingsWidget, fromString=None): + """ + Import/export outline cards in OPML format. + """ + ret = False + + if filePath != "": + # We have a filePath, so we read the file + try: + with open(filePath, 'rb') as opmlFile: + #opmlContent = cls.saveNewlines(opmlFile.read()) + opmlContent = opmlFile.read() + except: + QMessageBox.critical(settingsWidget, + qApp.translate("Import", "OPML Import"), + qApp.translate("Import", "File open failed.")) + return None + + elif fromString == "": + # We have neither filePath nor fromString, so we leave + return None + + else: + # We load from string + opmlContent = bytes(fromString, "utf-8") + + parsed = ET.fromstring(opmlContent) + + opmlNode = parsed + bodyNode = opmlNode.find("body") + items = [] + + if bodyNode is not None: + outlineEls = bodyNode.findall("outline") + + if outlineEls is not None: + for element in outlineEls: + items.extend(cls.parseItems(element, parentItem)) + ret = True + + if not ret: + QMessageBox.critical( + settingsWidget, + qApp.translate("Import", "OPML Import"), + qApp.translate("Import", "This does not appear to be a valid OPML file.")) + + return None + + return items + + @classmethod + def parseItems(cls, underElement, parentItem=None): + items = [] + title = underElement.get('text') + if title is not None: + + card = outlineItem(parent=parentItem, title=title) + items.append(card) + + body = "" + note = underElement.get('_note') + if note is not None and not cls.isWhitespaceOnly(note): + #body = cls.restoreNewLines(note) + body = note + + children = underElement.findall('outline') + if children is not None and len(children) > 0: + for el in children: + items.extend(cls.parseItems(el, card)) + else: + card.setData(Outline.type.value, 'md') + card.setData(Outline.text.value, body) + + return items + + @classmethod + def saveNewlines(cls, inString): + """ + Since XML parsers are notorious for stripping out significant newlines, + save them in a form we can restore after the parse. + """ + inString = inString.replace("\r\n", "\n") + inString = inString.replace("\n", "{{lf}}") + + return inString + + @classmethod + def restoreNewLines(cls, inString): + """ + Restore any significant newlines + """ + return inString.replace("{{lf}}", "\n") + + @classmethod + def isWhitespaceOnly(cls, inString): + """ + Determine whether or not a string only contains whitespace. + """ + s = cls.restoreNewLines(inString) + s = ''.join(s.split()) + + return len(s) is 0 diff --git a/manuskript/importer/pandocImporters.py b/manuskript/importer/pandocImporters.py new file mode 100644 index 0000000..023a4b0 --- /dev/null +++ b/manuskript/importer/pandocImporters.py @@ -0,0 +1,156 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- + +from manuskript.importer.abstractImporter import abstractImporter +from manuskript.exporter.pandoc import pandocExporter +from manuskript.importer.opmlImporter import opmlImporter +from manuskript.importer.markdownImporter import markdownImporter +from PyQt5.QtWidgets import qApp + + +class pandocImporter(abstractImporter): + + formatFrom = "" + engine = "Pandoc" + extraArgs = [] + + @classmethod + def isValid(cls): + return pandocExporter().isValid() + + def startImport(self, filePath, parentItem, settingsWidget): + + formatTo = self.getSetting("formatTo").value().lower() + wrap = self.getSetting("wrap").value().lower() + + # pandoc --from=markdown filename --to=opml --standalone + args = [ + "--from={}".format(self.formatFrom), + filePath, + "--to={}".format(formatTo), + "--wrap={}".format(wrap), + ] + + if formatTo == "opml": + args.append("--standalone") + + args += self.extraArgs + + r = pandocExporter().run(args) + + if formatTo == "opml": + return self.opmlImporter.startImport("", parentItem, + settingsWidget, fromString=r) + elif formatTo == "markdown": + return self.mdImporter.startImport(filePath, parentItem, + settingsWidget, fromString=r) + + def settingsWidget(self, widget): + """ + Takes a QWidget that can be modified and must be returned. + """ + + # Add group + group = self.addGroup(widget.toolBox.widget(0), + qApp.translate("Import", "Pandoc import")) + + self.addSetting("info", "label", + qApp.translate("Import", """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") + + self.addSetting("wrap", "combo", + qApp.translate("Import", "Wrap lines:"), + 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) + + self.mdImporter = markdownImporter() + widget = self.mdImporter.settingsWidget(widget) + self.opmlImporter = opmlImporter() + widget = self.opmlImporter.settingsWidget(widget) + + return widget + + +class markdownPandocImporter(pandocImporter): + + name = "Markdown" + description = "Markdown, using pandoc" + fileFormat = "Markdown files (*.md *.txt *)" + icon = "text-x-markdown" + formatFrom = "markdown" + +class ePubPandocImporter(pandocImporter): + + name = "ePub" + description = "" + fileFormat = "ePub files (*.epub)" + icon = "application-epub+zip" + formatFrom = "epub" + +class docXPandocImporter(pandocImporter): + + name = "DocX" + description = "" + fileFormat = "DocX files (*.docx)" + icon = "application-vnd.openxmlformats-officedocument.wordprocessingml.document" + formatFrom = "docx" + +class odtPandocImporter(pandocImporter): + + name = "ODT" + description = "" + fileFormat = "Open Document files (*.odt)" + icon = "application-vnd.oasis.opendocument.text" + formatFrom = "odt" + +class rstPandocImporter(pandocImporter): + + name = "reStructuredText" + description = "" + fileFormat = "reStructuredText files (*.rst)" + icon = "text-plain" + formatFrom = "rst" + +class HTMLPandocImporter(pandocImporter): + + name = "HTML" + description = "" + fileFormat = "HTML files (*.htm *.html)" + icon = "text-html" + formatFrom = "html" + +class LaTeXPandocImporter(pandocImporter): + + name = "LaTeX" + description = "" + fileFormat = "LaTeX files (*.tex)" + icon = "text-x-tex" + formatFrom = "latex" + +class OPMLPandocImporter(pandocImporter): + + name = "OPML" + description = "" + fileFormat = "OPML files (*.opml *.xml)" + icon = "text-x-opml+xml" + formatFrom = "opml" + + + 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/mainWindow.py b/manuskript/mainWindow.py index b47892e..a2f68b5 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 @@ -52,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() @@ -97,22 +101,35 @@ 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) + # Main Menu:: File 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) 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) @@ -121,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")) @@ -180,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 view 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 ############################################################################### @@ -381,6 +429,56 @@ 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) + + # 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): + "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): + "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): + """ + 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): + "Merges selected item(s)." + if self._lastFocus: self._lastFocus.merge() + ############################################################################### # LOAD AND SAVE ############################################################################### @@ -400,6 +498,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): if loadFromFile: # Load empty settings imp.reload(settings) + settings.initDefaultValues() # Load data self.loadEmptyDatas() @@ -408,7 +507,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.makeConnections() # Load settings - if settings.openIndexes: + if settings.openIndexes and settings.openIndexes != [""]: self.mainEditor.tabSplitter.restoreOpenIndexes(settings.openIndexes) self.generateViewMenu() self.mainEditor.sldCorkSizeFactor.setValue(settings.corkSizeFactor) @@ -460,7 +559,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 +603,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 @@ -662,6 +761,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): @@ -983,16 +1085,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 @@ -1142,8 +1234,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 @@ -1306,9 +1397,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/models/outlineModel.py b/manuskript/models/outlineModel.py index 9b4f766..4ce6772 100644 --- a/manuskript/models/outlineModel.py +++ b/manuskript/models/outlineModel.py @@ -15,9 +15,15 @@ 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 -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 @@ -268,6 +274,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 @@ -281,9 +300,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"): @@ -340,12 +359,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: @@ -361,14 +381,41 @@ 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 - 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() + 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 @@ -618,6 +665,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) @@ -816,10 +864,103 @@ 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) + 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()) + + 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 + ############################################################################### def toXML(self): item = ET.Element("outlineItem") @@ -879,14 +1020,17 @@ class outlineItem(): elif child.tag == "revision": self.appendRevision(child.attrib["timestamp"], child.attrib["text"]) + ############################################################################### + # IDS + ############################################################################### - ############################################################################### - # 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. @@ -895,7 +1039,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 +1057,9 @@ class outlineItem(): return IDs def findUniqueID(self): - k = 0 - while str(k) in self.IDs: + IDs = [int(i) for i in self.IDs] + k = 1 + while k in IDs: k += 1 self.IDs.append(str(k)) return str(k) 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/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/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/settings.py b/manuskript/settings.py index 4beb4b5..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 = { @@ -96,13 +97,27 @@ frequencyAnalyzer = { 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, \ autoSave, autoSaveDelay, saveOnQuit, autoSaveNoChanges, autoSaveNoChangesDelay, outlineViewColumns, \ corkBackground, corkStyle, fullScreenTheme, defaultTextType, textEditor, revisions, frequencyAnalyzer, viewMode, \ - saveToZip + saveToZip, dontShowDeleteWarning allSettings = { "viewSettings": viewSettings, @@ -127,6 +142,7 @@ def save(filename=None, protocol=None): "frequencyAnalyzer": frequencyAnalyzer, "viewMode": viewMode, "saveToZip": saveToZip, + "dontShowDeleteWarning": dontShowDeleteWarning, } #pp=pprint.PrettyPrinter(indent=4, compact=False) @@ -254,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: @@ -294,3 +311,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/settingsWindow.py b/manuskript/settingsWindow.py index c505de4..688d7de 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 @@ -37,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"), @@ -164,6 +174,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 +455,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 +545,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/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/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/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/editorWidget.py b/manuskript/ui/editors/editorWidget.py index 75abb56..b23d349 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): @@ -39,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) @@ -55,6 +58,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 +88,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,11 +109,21 @@ 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()) + + 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() @@ -202,7 +219,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 +228,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 +242,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 +259,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,20 +271,21 @@ 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 - self.setView() + if self._model: + self.setView() def updateIndexFromID(self): """ 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(): @@ -317,3 +335,92 @@ class editorWidget(QWidget, Ui_editorWidget_ui): def setDict(self, dct): self.currentDict = dct self.dictChanged.emit(dct) + + ############################################################################### + # FUNCTIONS FOR MENU ACCESS + ############################################################################### + + 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": + 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 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 views + 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 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/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() diff --git a/manuskript/ui/editors/mainEditor.py b/manuskript/ui/editors/mainEditor.py index 16358b0..1eafe22 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): """ @@ -170,7 +172,6 @@ class mainEditor(QWidget, Ui_mainEditor): ts = ts.secondTab return r - ############################################################################### # SELECTION AND UPDATES ############################################################################### @@ -219,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) @@ -243,6 +245,21 @@ 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 splitDialog(self): self.currentEditor().splitDialog() + def splitCursor(self): self.currentEditor().splitCursor() + def merge(self): self.currentEditor().merge() + ############################################################################### # UI ############################################################################### @@ -301,12 +318,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))) ############################################################################### 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/editors/mainEditor_ui.ui b/manuskript/ui/editors/mainEditor_ui.ui index 68a3a68..1110a34 100644 --- a/manuskript/ui/editors/mainEditor_ui.ui +++ b/manuskript/ui/editors/mainEditor_ui.ui @@ -50,7 +50,9 @@ - + + + Alt+Up 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..7a954bc 100644 --- a/manuskript/ui/exporters/exporter.py +++ b/manuskript/ui/exporters/exporter.py @@ -5,12 +5,13 @@ 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 writablePath, openURL 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): @@ -42,15 +43,19 @@ 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) - 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) + 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 +65,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: 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 77598ba..e8f36c7 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! @@ -17,20 +17,12 @@ 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, 491, 842)) + 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) self.verticalLayout_5.setObjectName("verticalLayout_5") self.label = QtWidgets.QLabel(self.content) self.label.setObjectName("label") @@ -111,9 +103,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, 173, 336)) 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() @@ -323,6 +316,7 @@ class Ui_exporterSettings(object): 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 +475,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, 369, 130)) 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..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,7 +42,7 @@ QToolBox::tab:selected, QToolBox::tab:hover{ 0 0 - 491 + 349 842 @@ -232,8 +221,8 @@ QToolBox::tab:selected, QToolBox::tab:hover{ 0 0 - 511 - 522 + 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 @@ -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 - 522 + 369 + 130 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/generalSettings.py b/manuskript/ui/importers/generalSettings.py new file mode 100644 index 0000000..cd94d2f --- /dev/null +++ b/manuskript/ui/importers/generalSettings.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- +import json +import os + +from PyQt5.QtCore import Qt, QSize, QSortFilterProxyModel, QModelIndex +from PyQt5.QtGui import QIcon, QFontMetrics, QFont +from PyQt5.QtWidgets import QWidget, QTableWidgetItem, QListWidgetItem, QTreeView + +from manuskript.functions import mainWindow, writablePath +from manuskript.ui.importers.generalSettings_ui import Ui_generalSettings +from manuskript.enums import Outline +from manuskript.ui import style + + +class generalSettings(QWidget, Ui_generalSettings): + def __init__(self, parent=None): + QWidget.__init__(self, parent) + self.setupUi(self) + self.toolBox.setStyleSheet(style.toolBoxSS()) + + self.mw = mainWindow() + self.txtGeneralSplitScenes.setStyleSheet(style.lineEditSS()) + + # TreeView to select parent + # We use a proxy to display only folders + proxy = QSortFilterProxyModel() + proxy.setFilterKeyColumn(Outline.type.value) + proxy.setFilterFixedString("folder") + proxy.setSourceModel(self.mw.mdlOutline) + self.treeGeneralParent.setModel(proxy) + for i in range(1, self.mw.mdlOutline.columnCount()): + self.treeGeneralParent.hideColumn(i) + self.treeGeneralParent.setCurrentIndex(self.getParentIndex()) + self.chkGeneralParent.toggled.connect(self.treeGeneralParent.setVisible) + self.treeGeneralParent.hide() + + def getParentIndex(self): + """ + Returns the currently selected index in the mainWindow. + """ + if len(self.mw.treeRedacOutline.selectionModel(). + selection().indexes()) == 0: + idx = QModelIndex() + else: + idx = self.mw.treeRedacOutline.currentIndex() + return idx + + def importUnderID(self): + """ + Returns the ID of the item selected in treeGeneralParent, if checked. + """ + if self.chkGeneralParent.isChecked(): + idx = self.treeGeneralParent.currentIndex() + # We used a filter proxy model, so we have to map back to source + # to get an index from mdlOutline + idx = self.treeGeneralParent.model().mapToSource(idx) + if idx.isValid(): + return idx.internalPointer().ID() + + return "0" # 0 is root's ID + + def importInTopLevelFolder(self): + """ + Should the import be flat in the parent folder, or create a top-level + folder? + """ + return self.chkGeneralTopLevel.isChecked() + + def trimLongTitles(self): + return self.chkGeneralTrimTitles.isChecked() + + def splitScenes(self): + """ + Return wheter the user wants to split scenes. + If unchecked, returns False. + If checked, returns the escaped split mark, or default (in placeholderText). + """ + if self.chkGeneralSplitScenes.isChecked(): + split = self.txtGeneralSplitScenes.text() + + if not split: + split = self.txtGeneralSplitScenes.placeholderText() + + split = split.replace("\\n", "\n") + split = split.replace("\\t", "\t") + return split + + else: + return False + diff --git a/manuskript/ui/importers/generalSettings_ui.py b/manuskript/ui/importers/generalSettings_ui.py new file mode 100644 index 0000000..d4bb2c5 --- /dev/null +++ b/manuskript/ui/importers/generalSettings_ui.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'manuskript/ui/importers/generalSettings_ui.ui' +# +# Created by: PyQt5 UI code generator 5.9 +# +# WARNING! All changes made in this file will be lost! + +from PyQt5 import QtCore, QtGui, QtWidgets + +class Ui_generalSettings(object): + def setupUi(self, generalSettings): + generalSettings.setObjectName("generalSettings") + generalSettings.resize(267, 401) + self.verticalLayout_2 = QtWidgets.QVBoxLayout(generalSettings) + self.verticalLayout_2.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_2.setSpacing(10) + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.toolBox = QtWidgets.QToolBox(generalSettings) + self.toolBox.setObjectName("toolBox") + self.general = QtWidgets.QWidget() + 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) + self.verticalLayout_5.setObjectName("verticalLayout_5") + self.formLayout_4 = QtWidgets.QFormLayout() + self.formLayout_4.setRowWrapPolicy(QtWidgets.QFormLayout.WrapLongRows) + self.formLayout_4.setObjectName("formLayout_4") + self.chkGeneralSplitScenes = QtWidgets.QCheckBox(self.general) + self.chkGeneralSplitScenes.setObjectName("chkGeneralSplitScenes") + self.formLayout_4.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.chkGeneralSplitScenes) + self.txtGeneralSplitScenes = QtWidgets.QLineEdit(self.general) + self.txtGeneralSplitScenes.setText("") + self.txtGeneralSplitScenes.setObjectName("txtGeneralSplitScenes") + self.formLayout_4.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.txtGeneralSplitScenes) + self.chkGeneralTrimTitles = QtWidgets.QCheckBox(self.general) + self.chkGeneralTrimTitles.setObjectName("chkGeneralTrimTitles") + self.formLayout_4.setWidget(4, QtWidgets.QFormLayout.SpanningRole, self.chkGeneralTrimTitles) + self.treeGeneralParent = QtWidgets.QTreeView(self.general) + self.treeGeneralParent.setHeaderHidden(True) + self.treeGeneralParent.setObjectName("treeGeneralParent") + self.formLayout_4.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.treeGeneralParent) + self.chkGeneralParent = QtWidgets.QCheckBox(self.general) + self.chkGeneralParent.setObjectName("chkGeneralParent") + self.formLayout_4.setWidget(0, QtWidgets.QFormLayout.SpanningRole, self.chkGeneralParent) + self.chkGeneralTopLevel = QtWidgets.QCheckBox(self.general) + self.chkGeneralTopLevel.setObjectName("chkGeneralTopLevel") + self.formLayout_4.setWidget(2, QtWidgets.QFormLayout.SpanningRole, self.chkGeneralTopLevel) + self.verticalLayout_5.addLayout(self.formLayout_4) + self.toolBox.addItem(self.general, "") + self.verticalLayout_2.addWidget(self.toolBox) + + self.retranslateUi(generalSettings) + self.toolBox.setCurrentIndex(0) + self.toolBox.layout().setSpacing(0) + QtCore.QMetaObject.connectSlotsByName(generalSettings) + + def retranslateUi(self, generalSettings): + _translate = QtCore.QCoreApplication.translate + generalSettings.setWindowTitle(_translate("generalSettings", "Form")) + self.chkGeneralSplitScenes.setText(_translate("generalSettings", "Split scenes at:")) + self.txtGeneralSplitScenes.setPlaceholderText(_translate("generalSettings", "\\n---\\n")) + self.chkGeneralTrimTitles.setText(_translate("generalSettings", "Trim long titles (> 32 chars)")) + self.chkGeneralParent.setText(_translate("generalSettings", "Import under:")) + self.chkGeneralTopLevel.setText(_translate("generalSettings", "Import in a top-level folder")) + self.toolBox.setItemText(self.toolBox.indexOf(self.general), _translate("generalSettings", "General")) + diff --git a/manuskript/ui/importers/generalSettings_ui.ui b/manuskript/ui/importers/generalSettings_ui.ui new file mode 100644 index 0000000..8bb2f71 --- /dev/null +++ b/manuskript/ui/importers/generalSettings_ui.ui @@ -0,0 +1,125 @@ + + + generalSettings + + + + 0 + 0 + 267 + 401 + + + + Form + + + + 10 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + 0 + + + + + 0 + 0 + 267 + 375 + + + + General + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + QFormLayout::WrapLongRows + + + + + Split scenes at: + + + + + + + + + + \n---\n + + + + + + + Trim long titles (> 32 chars) + + + + + + + true + + + + + + + Import under: + + + + + + + Import in a top-level folder + + + + + + + + + + + + + + diff --git a/manuskript/ui/importers/importer.py b/manuskript/ui/importers/importer.py new file mode 100644 index 0000000..2068451 --- /dev/null +++ b/manuskript/ui/importers/importer.py @@ -0,0 +1,324 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- +import json +import os + +from PyQt5.QtCore import Qt, QTimer +from PyQt5.QtGui import QBrush, QColor, QIcon +from PyQt5.QtWidgets import QWidget, QFileDialog, QMessageBox, QStyle + +from manuskript.functions import writablePath, appPath, openURL +from manuskript.ui.importers.importer_ui import Ui_importer +from manuskript.ui.importers.generalSettings import generalSettings +from manuskript.ui import style +from manuskript import importer +from manuskript.models.outlineModel import outlineModel, outlineItem +from manuskript.enums import Outline +from manuskript.exporter.pandoc import pandocExporter + +class importerDialog(QWidget, Ui_importer): + + formatsIcon = { + ".epub": "application-epub+zip", + ".odt": "application-vnd.oasis.opendocument.text", + ".docx": "application-vnd.openxmlformats-officedocument.wordprocessingml.document", + ".md": "text-x-markdown", + ".rst": "text-plain", + ".tex": "text-x-tex", + ".opml": "text-x-opml+xml", + ".xml": "text-x-opml+xml", + ".html": "text-html", + } + + def __init__(self, parent=None, mw=None): + QWidget.__init__(self, parent) + self.setupUi(self) + + # Var + self.mw = mw + self.fileName = "" + self.setStyleSheet(style.mainWindowSS()) + self.tree.setStyleSheet("QTreeView{background:transparent;}") + self.editor.setStyleSheet("QWidget{background:transparent;}") + self.editor.toggleSpellcheck(False) + + # Register importFormats: + self.importers = importer.importers + + # Populate combo box with formats + self.populateImportList() + + # Connections + self.btnChoseFile.clicked.connect(self.selectFile) + self.btnClearFileName.clicked.connect(self.setFileName) + self.btnPreview.clicked.connect(self.preview) + self.btnImport.clicked.connect(self.doImport) + self.cmbImporters.currentTextChanged.connect(self.updateSettings) + + self.setFileName("") + self.updateSettings() + + ############################################################################ + # Combobox / Formats + ############################################################################ + + def populateImportList(self): + + def addFormat(name, icon, identifier): + # Identifier serves to distingues 2 importers that would have the + # same name. + self.cmbImporters.addItem(QIcon.fromTheme(icon), name, identifier) + + def addHeader(name): + self.cmbImporters.addItem(name, "header") + self.cmbImporters.setItemData(self.cmbImporters.count() - 1, QBrush(QColor(style.highlightedTextDark)), Qt.ForegroundRole) + self.cmbImporters.setItemData(self.cmbImporters.count() - 1, QBrush(QColor(style.highlightLight)), Qt.BackgroundRole) + item = self.cmbImporters.model().item(self.cmbImporters.count() - 1) + item.setFlags(Qt.ItemIsEnabled) + + lastEngine = "" + + for f in self.importers: + # Header + if f.engine != lastEngine: + addHeader(f.engine) + lastEngine = f.engine + + addFormat(f.name, f.icon, "{}:{}".format(f.engine, f.name)) + if not f.isValid(): + item = self.cmbImporters.model().item(self.cmbImporters.count() - 1) + item.setFlags(Qt.NoItemFlags) + + if not pandocExporter().isValid(): + self.cmbImporters.addItem( + self.style().standardIcon(QStyle.SP_MessageBoxWarning), + "Install pandoc to import from much more formats", + "::URL::http://pandoc.org/installing.html") + + self.cmbImporters.setCurrentIndex(1) + + def currentFormat(self): + formatIdentifier = self.cmbImporters.currentData() + + if formatIdentifier == "header": + return None + + F = [F for F in self.importers + if formatIdentifier == "{}:{}".format(F.engine, F.name)][0] + # We instantiate the class + return F() + + ############################################################################ + # Import file + ############################################################################ + + def selectFile(self): + """ + Called to select a file in the file system. Uses QFileDialog. + """ + + # We find the current selected format + F = self._format + + options = QFileDialog.Options() + options |= QFileDialog.DontUseNativeDialog + if F.fileFormat == "<>": + options = QFileDialog.DontUseNativeDialog | QFileDialog.ShowDirsOnly + fileName = QFileDialog.getExistingDirectory(self, "Select import folder", + "", options=options) + else: + fileName, _ = QFileDialog.getOpenFileName(self, "Import from file", "", + F.fileFormat, options=options) + self.setFileName(fileName) + self.preview() + + def setFileName(self, fileName): + """ + Updates Ui with given filename. Filename can be empty. + """ + if fileName: + self.fileName = fileName + self.lblFileName.setText(os.path.basename(fileName)) + self.lblFileName.setToolTip(fileName) + ext = os.path.splitext(fileName)[1] + icon = None + if ext and ext in self.formatsIcon: + icon = QIcon.fromTheme(self.formatsIcon[ext]) + elif os.path.isdir(fileName): + icon = QIcon.fromTheme("folder") + + if icon: + self.lblIcon.setVisible(True) + h = self.lblFileName.height() + self.lblIcon.setPixmap(icon.pixmap(h, h)) + else: + self.lblIcon.hide() + + else: + self.fileName = None + self.lblFileName.setText("") + + hasFile = True if fileName else False + + self.btnClearFileName.setVisible(hasFile) + self.lblIcon.setVisible(hasFile) + self.btnChoseFile.setVisible(not hasFile) + self.btnPreview.setEnabled(hasFile) + self.btnImport.setEnabled(hasFile) + + ############################################################################ + # UI + ############################################################################ + + def updateSettings(self): + """ + When the current format change (through the combobox), we update the + settings widget using the current format provided settings widget. + """ + + # We check if we have to open an URL + data = self.cmbImporters.currentData() + if data and data[:7] == "::URL::" and data[7:]: + # FIXME: use functions.openURL after merge with feature/Exporters + openURL(data[7:]) + return + + F = self.currentFormat() + self._format = F + + # Checking if we have a valid importer (otherwise a header) + if not F: + self.grpSettings.setEnabled(False) + self.grpPreview.setEnabled(False) + return + self.grpSettings.setEnabled(True) + self.grpPreview.setEnabled(True) + + self.settingsWidget = generalSettings() + #TODO: custom format widget + self.settingsWidget = F.settingsWidget(self.settingsWidget) + + # Set the settings widget in place + self.setGroupWidget(self.grpSettings, self.settingsWidget) + self.grpSettings.setMinimumWidth(200) + + # Clear file name + self.setFileName("") + + def setGroupWidget(self, group, widget): + """ + Sets the given widget as main widget for QGroupBox group. + """ + + # Removes every items from given layout. + l = group.layout() + while l.count(): + item = l.itemAt(0) + l.removeItem(item) + item.widget().deleteLater() + + l.addWidget(widget) + widget.setParent(group) + + ############################################################################ + # Preview / Import + ############################################################################ + + def preview(self): + + if not self.fileName: + return + + # Creating a temporary outlineModel + previewModel = outlineModel(self) + previewModel.loadFromXML( + self.mw.mdlOutline.saveToXML(), + fromString=True) + + # Inserting elements + result = self.startImport(previewModel) + + if result: + self.tree.setModel(previewModel) + for i in range(1, previewModel.columnCount()): + self.tree.hideColumn(i) + self.tree.selectionModel().currentChanged.connect(self.editor.setCurrentModelIndex) + self.previewSplitter.setStretchFactor(0, 10) + self.previewSplitter.setStretchFactor(1, 40) + + def doImport(self): + """ + Called by the Import button. + """ + self.startImport(self.mw.mdlOutline) + + # Signal every views that important model changes have happened. + self.mw.mdlOutline.layoutChanged.emit() + + # I'm getting seg fault over this message sometimes... + # Using status bar message instead... + #QMessageBox.information(self, self.tr("Import status"), + #self.tr("Import Complete.")) + self.mw.statusBar().showMessage("Import complete!", 5000) + + self.close() + + def startImport(self, outlineModel): + """ + Where most of the magic happens. + Is used by preview and by doImport (actual import). + + `outlineModel` is the model where the imported items are added. + + FIXME: Optimisation: when adding many outlineItems, outlineItem.updateWordCount + is a bottleneck. It gets called a crazy number of time, and its not + necessary. + """ + + items = [] + + # We find the current selected format + F = self._format + + # Parent item + ID = self.settingsWidget.importUnderID() + parentItem = outlineModel.getItemByID(ID) + + # Import in top-level folder? + if self.settingsWidget.importInTopLevelFolder(): + parent = outlineItem(title=os.path.basename(self.fileName), + parent=parentItem) + parentItem = parent + items.append(parent) + + # Calling the importer + rItems = F.startImport(self.fileName, + parentItem, + self.settingsWidget) + + items.extend(rItems) + + # Do transformations + items = self.doTransformations(items) + + return True + + def doTransformations(self, items): + """ + Do general transformations. + """ + + # Trim long titles + if self.settingsWidget.trimLongTitles(): + for item in items: + if len(item.title()) > 32: + item.setData(Outline.title.value, item.title()[:32]) + + # Split at + if self.settingsWidget.splitScenes(): + for item in items: + item.split(self.settingsWidget.splitScenes(), recursive=False) + + return items + + diff --git a/manuskript/ui/importers/importer_ui.py b/manuskript/ui/importers/importer_ui.py new file mode 100644 index 0000000..d23c8e4 --- /dev/null +++ b/manuskript/ui/importers/importer_ui.py @@ -0,0 +1,107 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'manuskript/ui/importers/importer_ui.ui' +# +# Created by: PyQt5 UI code generator 5.9 +# +# WARNING! All changes made in this file will be lost! + +from PyQt5 import QtCore, QtGui, QtWidgets + +class Ui_importer(object): + def setupUi(self, importer): + importer.setObjectName("importer") + importer.resize(867, 560) + self.verticalLayout = QtWidgets.QVBoxLayout(importer) + self.verticalLayout.setObjectName("verticalLayout") + self.horizontalLayout = QtWidgets.QHBoxLayout() + self.horizontalLayout.setObjectName("horizontalLayout") + self.label = QtWidgets.QLabel(importer) + self.label.setObjectName("label") + self.horizontalLayout.addWidget(self.label) + self.cmbImporters = QtWidgets.QComboBox(importer) + self.cmbImporters.setObjectName("cmbImporters") + self.horizontalLayout.addWidget(self.cmbImporters) + spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout.addItem(spacerItem) + self.btnChoseFile = QtWidgets.QPushButton(importer) + icon = QtGui.QIcon.fromTheme("document-import") + self.btnChoseFile.setIcon(icon) + self.btnChoseFile.setObjectName("btnChoseFile") + self.horizontalLayout.addWidget(self.btnChoseFile) + self.lblIcon = QtWidgets.QLabel(importer) + self.lblIcon.setText("") + self.lblIcon.setObjectName("lblIcon") + self.horizontalLayout.addWidget(self.lblIcon) + self.lblFileName = QtWidgets.QLabel(importer) + font = QtGui.QFont() + font.setBold(True) + font.setWeight(75) + self.lblFileName.setFont(font) + self.lblFileName.setText("") + self.lblFileName.setObjectName("lblFileName") + self.horizontalLayout.addWidget(self.lblFileName) + self.btnClearFileName = QtWidgets.QPushButton(importer) + self.btnClearFileName.setText("") + icon = QtGui.QIcon.fromTheme("edit-clear") + self.btnClearFileName.setIcon(icon) + self.btnClearFileName.setFlat(True) + self.btnClearFileName.setObjectName("btnClearFileName") + self.horizontalLayout.addWidget(self.btnClearFileName) + spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout.addItem(spacerItem1) + self.btnPreview = QtWidgets.QPushButton(importer) + icon = QtGui.QIcon.fromTheme("document-print-preview") + self.btnPreview.setIcon(icon) + self.btnPreview.setFlat(True) + self.btnPreview.setObjectName("btnPreview") + self.horizontalLayout.addWidget(self.btnPreview) + self.btnImport = QtWidgets.QPushButton(importer) + icon = QtGui.QIcon.fromTheme("document-import") + self.btnImport.setIcon(icon) + self.btnImport.setObjectName("btnImport") + self.horizontalLayout.addWidget(self.btnImport) + self.verticalLayout.addLayout(self.horizontalLayout) + self.splitter = QtWidgets.QSplitter(importer) + self.splitter.setOrientation(QtCore.Qt.Horizontal) + self.splitter.setChildrenCollapsible(False) + self.splitter.setObjectName("splitter") + self.grpSettings = QtWidgets.QGroupBox(self.splitter) + self.grpSettings.setObjectName("grpSettings") + self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.grpSettings) + self.verticalLayout_3.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_3.setSpacing(0) + self.verticalLayout_3.setObjectName("verticalLayout_3") + self.grpPreview = QtWidgets.QGroupBox(self.splitter) + self.grpPreview.setObjectName("grpPreview") + self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.grpPreview) + self.verticalLayout_2.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_2.setSpacing(0) + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.previewSplitter = QtWidgets.QSplitter(self.grpPreview) + self.previewSplitter.setOrientation(QtCore.Qt.Horizontal) + self.previewSplitter.setObjectName("previewSplitter") + self.tree = QtWidgets.QTreeView(self.previewSplitter) + self.tree.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) + self.tree.setHeaderHidden(True) + self.tree.setObjectName("tree") + self.editor = editorWidget(self.previewSplitter) + self.editor.setObjectName("editor") + self.verticalLayout_2.addWidget(self.previewSplitter) + self.verticalLayout.addWidget(self.splitter) + + self.retranslateUi(importer) + QtCore.QMetaObject.connectSlotsByName(importer) + + def retranslateUi(self, importer): + _translate = QtCore.QCoreApplication.translate + importer.setWindowTitle(_translate("importer", "Import")) + self.label.setText(_translate("importer", "Format:")) + self.btnChoseFile.setText(_translate("importer", "Chose file")) + self.btnClearFileName.setToolTip(_translate("importer", "Clear file")) + self.btnPreview.setText(_translate("importer", "Preview")) + self.btnImport.setText(_translate("importer", "Import")) + self.grpSettings.setTitle(_translate("importer", "Settings")) + self.grpPreview.setTitle(_translate("importer", "Preview")) + +from manuskript.ui.editors.editorWidget import editorWidget diff --git a/manuskript/ui/importers/importer_ui.ui b/manuskript/ui/importers/importer_ui.ui new file mode 100644 index 0000000..cbf72b9 --- /dev/null +++ b/manuskript/ui/importers/importer_ui.ui @@ -0,0 +1,210 @@ + + + importer + + + + 0 + 0 + 867 + 560 + + + + Import + + + + + + + + Format: + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Chose file + + + + + + + + + + + + + + + + + + 75 + true + + + + + + + + + + + Clear file + + + + + + + ../../../../../../../../../../.designer/backup../../../../../../../../../../.designer/backup + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Preview + + + + ../../../../../../../../../../.designer/backup../../../../../../../../../../.designer/backup + + + true + + + + + + + Import + + + + + + + + + + + + Qt::Horizontal + + + false + + + + Settings + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + Preview + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + QAbstractItemView::NoEditTriggers + + + true + + + + + + + + + + + + + + editorWidget + QWidget +
manuskript.ui.editors.editorWidget.h
+ 1 +
+
+ + +
diff --git a/manuskript/ui/mainWindow.py b/manuskript/ui/mainWindow.py index 7323796..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") @@ -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") @@ -1099,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) @@ -1189,12 +1176,57 @@ 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.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) 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) @@ -1210,8 +1242,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()) @@ -1329,6 +1374,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")) @@ -1360,6 +1406,28 @@ class Ui_MainWindow(object): self.actToolFrequency.setText(_translate("MainWindow", "&Frequency Analyzer")) self.actAbout.setText(_translate("MainWindow", "&About")) self.actAbout.setToolTip(_translate("MainWindow", "About Manuskript")) + self.actImport.setText(_translate("MainWindow", "&Import…")) + self.actImport.setShortcut(_translate("MainWindow", "F7")) + self.actCopy.setText(_translate("MainWindow", "&Copy")) + self.actCopy.setShortcut(_translate("MainWindow", "Ctrl+C")) + self.actCut.setText(_translate("MainWindow", "C&ut")) + 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", "Sp&lit at cursor")) + self.actSplitCursor.setShortcut(_translate("MainWindow", "Ctrl+K")) + 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", "D&elete")) + 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", "M&ove 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 05063d7..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 @@ - - + .. @@ -2116,6 +2114,7 @@ + @@ -2157,8 +2156,26 @@
+ + + &Documents + + + + + + + + + + + + + + + @@ -2258,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 @@ -2370,8 +2370,7 @@ QListView::item:hover { - - + .. &Labels... @@ -2380,8 +2379,7 @@ QListView::item:hover { - - + .. &Status... @@ -2444,8 +2442,7 @@ QListView::item:hover { - - + .. &Close project @@ -2454,8 +2451,7 @@ QListView::item:hover { - - + .. Co&mpile @@ -2472,8 +2468,7 @@ QListView::item:hover { - - + .. &About @@ -2482,6 +2477,138 @@ QListView::item:hover { About Manuskript + + + + .. + + + &Import… + + + F7 + + + + + + .. + + + &Copy + + + Ctrl+C + + + + + + .. + + + C&ut + + + Ctrl+X + + + + + + .. + + + &Paste + + + Ctrl+V + + + + + + .. + + + &Split… + + + Ctrl+Shift+K + + + + + + .. + + + Sp&lit at cursor + + + Ctrl+K + + + + + + .. + + + Me&rge + + + Ctrl+M + + + + + + .. + + + &Duplicate + + + Ctrl+D + + + + + + .. + + + D&elete + + + Del + + + + + + .. + + + &Move Up + + + Ctrl+Shift+Up + + + + + + .. + + + M&ove Down + + + Ctrl+Shift+Down + + diff --git a/manuskript/ui/settings_ui.py b/manuskript/ui/settings_ui.py index 62159dd..1fc7ec6 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) @@ -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) @@ -953,28 +932,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 +990,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 +1026,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 +1053,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 +1063,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 +1121,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 +1131,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 +1258,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) @@ -1306,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) @@ -1369,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) @@ -1405,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() @@ -1797,8 +1778,8 @@ class Ui_Settings(object): self.horizontalLayout_8.addWidget(self.stack) self.retranslateUi(Settings) - self.stack.setCurrentIndex(2) - self.tabViews.setCurrentIndex(0) + self.stack.setCurrentIndex(5) + self.tabViews.setCurrentIndex(3) self.themeStack.setCurrentIndex(1) self.themeEditStack.setCurrentIndex(3) self.lstMenu.currentRowChanged['int'].connect(self.stack.setCurrentIndex) @@ -1873,13 +1854,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 +1898,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 +1932,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 +1949,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,16 +1966,15 @@ 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")) 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 e22e6f3..194161d 100644 --- a/manuskript/ui/settings_ui.ui +++ b/manuskript/ui/settings_ui.ui @@ -7,7 +7,7 @@ 0 0 658 - 530 + 632 @@ -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 @@ -795,13 +768,12 @@ text-align:center; - 0 + 3 - - + .. Tree @@ -1070,7 +1042,7 @@ text-align:center; - Show wordcount + Show &wordcount @@ -1083,7 +1055,7 @@ text-align:center; - Show progress + S&how progress @@ -1096,7 +1068,7 @@ text-align:center; - Show summary + Show summar&y @@ -1140,7 +1112,7 @@ text-align:center; - Show wordcount + &Show wordcount @@ -1153,7 +1125,7 @@ text-align:center; - Show progress + Show p&rogress @@ -1225,8 +1197,7 @@ text-align:center; - - + .. Outline @@ -1573,8 +1544,7 @@ text-align:center; - - + .. Index cards @@ -1693,7 +1663,7 @@ text-align:center; - Old style + Old st&yle @@ -1706,7 +1676,7 @@ text-align:center; - New style + Ne&w style @@ -1976,365 +1946,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 +2294,65 @@ text-align:center; Paragraphs + + + + + 50 + false + + + + Alignment: + + + + + + + + 50 + false + + + + + Left + + + + .. + + + + + Center + + + + .. + + + + + Right + + + + .. + + + + + Justify + + + + .. + + + + @@ -2550,8 +2556,23 @@ text-align:center; + + + + + + + + 75 + true + + + + Cursor + + - + 50 @@ -2559,58 +2580,43 @@ text-align:center; - Alignment: + Use block insertion of - + 50 false - - - Left - - - - - - - - - - Center - - - - - - - - - - Right - - - - - - - - - - Justify - - - - - - - + + px + + + 0 + + + 99 + + + 9 + + + + + + + + 50 + false + + + + Disable blinking + @@ -2646,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 @@ -2787,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 @@ -2863,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 @@ -3597,8 +3576,7 @@ text-align:center; - - + .. @@ -3607,8 +3585,7 @@ text-align:center; - - + .. @@ -3617,8 +3594,7 @@ text-align:center; - - + .. @@ -3627,8 +3603,7 @@ text-align:center; - - + .. diff --git a/manuskript/ui/style.py b/manuskript/ui/style.py index ad5c907..ba1d72f 100644 --- a/manuskript/ui/style.py +++ b/manuskript/ui/style.py @@ -8,14 +8,40 @@ 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 +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 +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 +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 + +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 """ @@ -26,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): @@ -40,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(): @@ -78,104 +125,126 @@ 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 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: {highlightedText}; + }} + QTabBar::tab:!selected:hover{{ + background:{highlight}; + color: {highlightedText}; + }} + """.format( + highlight=highlight, + highlightedText=highlightedText, + text=text, + borderColor=mid, + ) + + # Add scrollbar + SS += simpleScrollBarV(handle=mid, width=10) + + return SS - QScrollBar:vertical {{ - border: none; - background: transparent; - width: 10px; - }} - QScrollBar::handle {{ - background: rgba(180, 180, 180, 40%); - }} - 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( - bgColor=settings.textEditor["background"], - foreground=settings.textEditor["fontColor"] - ) 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 """ @@ -195,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 ) @@ -231,17 +302,17 @@ 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(): return """ QTextEdit{ @@ -249,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; - }""" \ No newline at end of file + }}""".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/tools/splitDialog.py b/manuskript/ui/tools/splitDialog.py new file mode 100644 index 0000000..da950bb --- /dev/null +++ b/manuskript/ui/tools/splitDialog.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- +from PyQt5.QtWidgets import QInputDialog +from manuskript.functions import mainWindow + + +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 escape 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) + + 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() + + mark = self.textValue() + + if r and mark: + + mark = mark.replace("\\n", "\n") + mark = mark.replace("\\t", "\t") + + for idx in indexes: + 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/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/corkDelegate.py b/manuskript/ui/views/corkDelegate.py index ab4e4c5..d3908eb 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): @@ -20,6 +21,8 @@ class corkDelegate(QStyledItemDelegate): self.editing = None self.margin = 5 + self.bgColors = {} + def newStyle(self): return settings.corkStyle == "new" @@ -42,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 @@ -56,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): @@ -71,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 @@ -85,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): @@ -141,14 +149,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 +175,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 +204,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 +220,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) @@ -240,10 +248,15 @@ 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) + + # Cache background color + self.bgColors[index] = backgroundColor.name() + p.setPen(Qt.NoPen) p.drawRect(self.cardRect) if item.isFolder(): @@ -273,10 +286,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,12 +312,23 @@ class corkDelegate(QStyledItemDelegate): # Draw title p.save() text = index.data() - + 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) @@ -357,6 +381,7 @@ class corkDelegate(QStyledItemDelegate): f = QFont(option.font) f.setBold(True) p.setFont(f) + p.setPen(textColor) fm = QFontMetrics(f) elidedText = fm.elidedText(lineSummary, Qt.ElideRight, self.mainLineRect.width()) p.drawText(self.mainLineRect, Qt.AlignLeft | Qt.AlignVCenter, elidedText) @@ -364,8 +389,11 @@ class corkDelegate(QStyledItemDelegate): # Full summary if fullSummary: + p.save() p.setFont(option.font) + p.setPen(textColor) p.drawText(self.mainTextRect, Qt.TextWordWrap, fullSummary) + p.restore() def paint_v1(self, p, option, index): # QStyledItemDelegate.paint(self, p, option, index) diff --git a/manuskript/ui/views/outlineBasics.py b/manuskript/ui/views/outlineBasics.py index 7fc6959..ccd655c 100644 --- a/manuskript/ui/views/outlineBasics.py +++ b/manuskript/ui/views/outlineBasics.py @@ -1,20 +1,22 @@ #!/usr/bin/env python # --!-- coding: utf8 --!-- from PyQt5.QtCore import Qt, QSignalMapper, QSize -from PyQt5.QtGui import QIcon -from PyQt5.QtWidgets import QAbstractItemView, qApp, QMenu, QAction -from PyQt5.QtWidgets import QListWidget, QWidgetAction, QListWidgetItem, QLineEdit +from PyQt5.QtGui import QIcon, QCursor +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 mainWindow, statusMessage from manuskript.functions import toInt, customIcons from manuskript.models.outlineModel import outlineItem +from manuskript.ui.tools.splitDialog import splitDialog class outlineBasics(QAbstractItemView): def __init__(self, parent=None): - pass + self._indexesToOpen = None def getSelection(self): sel = [] @@ -39,14 +41,51 @@ class outlineBasics(QAbstractItemView): menu = QMenu(self) - # Add / remove 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 = qApp.translate("outlineBasics", "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 = qApp.translate("outlineBasics", "Open {} items in new tabs").format(len(sel)) + self._indexesToOpen = sel + else: + actionTitle = qApp.translate("outlineBasics", "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() - # 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) @@ -181,22 +220,38 @@ 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) 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): - 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() + self.edit(index) + elif len(self.getSelection()) > 1: + # FIXME: add smart rename + pass + def addFolder(self): self.addItem("folder") @@ -231,8 +286,132 @@ 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): + self.copy() + self.paste() + + def move(self, delta=1): + """ + Move selected items up or down. + """ + + # we store selected indexesret + 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.setSmsgBoxelectionModel(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 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: + # 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/outlineDelegates.py b/manuskript/ui/views/outlineDelegates.py index 818718d..8995ec9 100644 --- a/manuskript/ui/views/outlineDelegates.py +++ b/manuskript/ui/views/outlineDelegates.py @@ -8,6 +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): @@ -39,7 +40,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 +77,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) @@ -132,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/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! diff --git a/manuskript/ui/views/textEditView.py b/manuskript/ui/views/textEditView.py index d557e4d..ce7cf72 100644 --- a/manuskript/ui/views/textEditView.py +++ b/manuskript/ui/views/textEditView.py @@ -2,19 +2,19 @@ # --!-- 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 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 from manuskript.ui.editors.basicHighlighter import basicHighlighter from manuskript.ui.editors.textFormat import textFormat +from manuskript.ui import style as S try: import enchant @@ -49,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() @@ -59,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")) @@ -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,14 +87,23 @@ 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: - 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 @@ -132,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 = [] @@ -181,6 +205,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}; @@ -191,8 +217,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"], @@ -213,7 +239,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() @@ -281,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: @@ -290,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: @@ -301,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]: @@ -337,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 @@ -384,7 +410,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 @@ -439,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) @@ -526,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") diff --git a/manuskript/ui/views/treeDelegates.py b/manuskript/ui/views/treeDelegates.py index a9ee454..9e8179b 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,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 = 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) @@ -131,8 +142,12 @@ 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) + 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() painter.restore() 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() diff --git a/manuskript/ui/welcome.py b/manuskript/ui/welcome.py index c6d4c68..d3858e8 100644 --- a/manuskript/ui/welcome.py +++ b/manuskript/ui/welcome.py @@ -20,9 +20,12 @@ 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 -locale.setlocale(locale.LC_ALL, '') - +try: + locale.setlocale(locale.LC_ALL, '') +except: + pass class welcome(QWidget, Ui_welcome): def __init__(self, parent=None): @@ -65,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: @@ -139,6 +149,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) @@ -269,6 +281,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) @@ -333,8 +346,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) @@ -372,6 +385,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": diff --git a/package/create_deb.sh b/package/create_deb.sh new file mode 100755 index 0000000..f9773f0 --- /dev/null +++ b/package/create_deb.sh @@ -0,0 +1,60 @@ +#!/bin/bash + +# Can take two parameters: AppVersion PkgNumber +# Default values are: 0.5.0 1 + +# Manuskript Vars +AppName=manuskript +AppVersion=${1:-0.5.0} +PkgNumber=${2:-1} +PkgVersion=$AppVersion-$PkgNumber +#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..a8e3296 --- /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: 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 +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; 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..9529460 --- /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