Merge branch 'develop' into characterscount

This commit is contained in:
Tobias Frisch 2021-02-19 15:05:15 +01:00 committed by GitHub
commit 30b15b94dc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
123 changed files with 90353 additions and 4730 deletions

23
.gitignore vendored
View file

@ -1,15 +1,22 @@
snowflake*
*.pyc
# List of file patterns for git to ignore.
#
# Please try to keep entries in alphabetical order :-)
#
*.lprof
.directory
*.msk
Notes.t2t
*.nja
manuskript/pycallgraph.txt
ExportTest
icons/Numix
*.pyc
.cache
.directory
.idea
.project
.pydevproject
.settings/org.eclipse.core.resources.prefs
ExportTest
Notes.t2t
dist
build
icons/Numix
manuskript/pycallgraph.txt
snowflake*
test-projects
.cache

View file

@ -1,18 +1,18 @@
language: cpp
language: generic
os:
- osx
osx_image: xcode7.3
- linux
osx_image: xcode11
sudo: required
install:
- if [ "$TRAVIS_OS_NAME" = "osx" ]; then bash package/prepare_osx.sh; fi
- if [ "$TRAVIS_OS_NAME" = "osx" ]; then package/prepare_osx.sh; fi
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then package/prepare_linux.sh; fi
script:
- python3 package/dependency_test.py
- if [ "$TRAVIS_OS_NAME" = "osx" ]; then python3 -B -m pytest -vs; fi
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then xvfb-run -s '-screen 0 640x480x24 +extension GLX' pytest -vs; fi
before_deploy:
- export FILENAME=manuskript-$TRAVIS_BRANCH-$TRAVIS_OS_NAME.zip
- pyinstaller manuskript.spec --clean
- cd dist && zip $FILENAME -r manuskript && cd ..
- ls dist
- cp dist/$FILENAME dist/manuskript-osx-develop.zip
- if [ "$TRAVIS_OS_NAME" = "osx" ]; then package/build_osx.sh; fi
deploy:
- provider: releases
api_key:
@ -21,11 +21,13 @@ deploy:
overwrite: true
skip_cleanup: true
on:
condition: $TRAVIS_OS_NAME = osx
tags: true
- provider: script
script: "curl -T dist/manuskript-osx-develop.zip -u hfpn_semaphoreci:$FTP_PASSWORD ftp://www.theologeek.ch/web/manuskript/releases/ -v"
skip_cleanup: true
on:
condition: $TRAVIS_OS_NAME = osx
branch: develop
env:
global:

View file

@ -1,13 +1,286 @@
# Change Log
# Changelog
## [0.11.0](https://github.com/olivierkes/manuskript/tree/0.11.0) (2020-01-18)
[Full Changelog](https://github.com/olivierkes/manuskript/compare/0.10.0...HEAD)
**Fixed bugs:**
- Ctrl+Space destroys and Ctrl+Z can't recover [\#703](https://github.com/olivierkes/manuskript/issues/703)
- References Mysteriously Delete in Summary and Reference/Notes Fields [\#688](https://github.com/olivierkes/manuskript/issues/688)
- After use of ctrl-z or ctrl-v Manuskript goes in inactive State [\#672](https://github.com/olivierkes/manuskript/issues/672)
- Cut and Paste - Comment Field - First shows, then disappiers [\#670](https://github.com/olivierkes/manuskript/issues/670)
- 0.10.0 crash using Windows dark mode [\#659](https://github.com/olivierkes/manuskript/issues/659)
**Closed issues:**
- Can't get the program to open [\#686](https://github.com/olivierkes/manuskript/issues/686)
- Default for separator between folders should be pagebreak [\#680](https://github.com/olivierkes/manuskript/issues/680)
- \[Feature Request\] Automatically calculate folder word count goal [\#664](https://github.com/olivierkes/manuskript/issues/664)
**Merged pull requests:**
- Change wording of import warning for PyQt/Qt versions 5.11 and 5.12 [\#715](https://github.com/olivierkes/manuskript/pull/715) ([gedakc](https://github.com/gedakc))
- Remove support for macOS X Sierra \(10.12\) in Travis CI build [\#713](https://github.com/olivierkes/manuskript/pull/713) ([gedakc](https://github.com/gedakc))
- Fixed bugs caused by parallel access during multithreading [\#706](https://github.com/olivierkes/manuskript/pull/706) ([TheJackiMonster](https://github.com/TheJackiMonster))
- More german translations [\#701](https://github.com/olivierkes/manuskript/pull/701) ([argail1980](https://github.com/argail1980))
- Fixed translation mistake. Trilogy translates to Trilogie in German [\#700](https://github.com/olivierkes/manuskript/pull/700) ([argail1980](https://github.com/argail1980))
- Fix for Windows 10 Dark Theme on older Qt versions [\#660](https://github.com/olivierkes/manuskript/pull/660) ([worstje](https://github.com/worstje))
## [0.10.0](https://github.com/olivierkes/manuskript/tree/0.10.0) (2019-09-30)
[Full Changelog](https://github.com/olivierkes/manuskript/compare/0.9.0...0.10.0)
**Implemented enhancements:**
- Add non-enchant spellcheck support [\#505](https://github.com/olivierkes/manuskript/issues/505)
- Basic dark theme support \(Windows 10\) [\#630](https://github.com/olivierkes/manuskript/pull/630) ([worstje](https://github.com/worstje))
- Refactor spellchecker code and add other spellcheck library support [\#507](https://github.com/olivierkes/manuskript/pull/507) ([kakaroto](https://github.com/kakaroto))
**Fixed bugs:**
- Impossible to change UI language to english if your system locale isn't set to an anglophonic country [\#619](https://github.com/olivierkes/manuskript/issues/619)
- All Imports are crashing [\#611](https://github.com/olivierkes/manuskript/issues/611)
- When compiling, it overwrite files without asking [\#608](https://github.com/olivierkes/manuskript/issues/608)
- Crash when exporting with pandoc as custom path only [\#563](https://github.com/olivierkes/manuskript/issues/563)
- Crash on insertion of new page character [\#562](https://github.com/olivierkes/manuskript/issues/562)
- Crash on adding word goal in outline [\#561](https://github.com/olivierkes/manuskript/issues/561)
- Crash in Windows 10 when drag and drop [\#559](https://github.com/olivierkes/manuskript/issues/559)
- Image crash: When using tooltip on an incomplete image filename [\#549](https://github.com/olivierkes/manuskript/issues/549)
- pandoc export crashes if project title is empty [\#535](https://github.com/olivierkes/manuskript/issues/535)
- editor/cork options wrong after deleting a text [\#516](https://github.com/olivierkes/manuskript/issues/516)
- crash on import directory [\#500](https://github.com/olivierkes/manuskript/issues/500)
- Inconsistent and/or undesirable window placements [\#481](https://github.com/olivierkes/manuskript/issues/481)
- Crash when deleting folder with files in tree view [\#479](https://github.com/olivierkes/manuskript/issues/479)
- New level 'unit' is reset [\#468](https://github.com/olivierkes/manuskript/issues/468)
- Lowering number of saved revisions below 1 crashes program [\#381](https://github.com/olivierkes/manuskript/issues/381)
- ValueError: All strings must be XML compatible: Unicode or ASCII, no NULL bytes or control characters [\#31](https://github.com/olivierkes/manuskript/issues/31)
- OS X: cannot leave fullscreen mode [\#24](https://github.com/olivierkes/manuskript/issues/24)
**Closed issues:**
- Fullscreen mode causes spike in CPU [\#643](https://github.com/olivierkes/manuskript/issues/643)
- Italian dictionary [\#638](https://github.com/olivierkes/manuskript/issues/638)
- Manuskript 9.0 crashes when creating new project or opening existing project [\#631](https://github.com/olivierkes/manuskript/issues/631)
- Spell Check Not working [\#625](https://github.com/olivierkes/manuskript/issues/625)
- Manuskript me fastidio un documento de word [\#616](https://github.com/olivierkes/manuskript/issues/616)
- Feature request: Option to vertically center text input line on screen in fullscreen mode [\#602](https://github.com/olivierkes/manuskript/issues/602)
- File Randomly won't open [\#597](https://github.com/olivierkes/manuskript/issues/597)
- utf-8' codec can't decode byte 0xff in position 0 [\#591](https://github.com/olivierkes/manuskript/issues/591)
- Issue with saving as directory [\#589](https://github.com/olivierkes/manuskript/issues/589)
- Headings h4 not translated from Markdown to ODF [\#580](https://github.com/olivierkes/manuskript/issues/580)
- \[BUG\] Shim error [\#579](https://github.com/olivierkes/manuskript/issues/579)
- Crash when edit text [\#555](https://github.com/olivierkes/manuskript/issues/555)
- Unusual environment failure [\#547](https://github.com/olivierkes/manuskript/issues/547)
- Won't run \(Arch Linux\) [\#546](https://github.com/olivierkes/manuskript/issues/546)
- Rendre extensible les modèles d'intrigue [\#329](https://github.com/olivierkes/manuskript/issues/329)
- Word count goal progress bar broken in develop. [\#652](https://github.com/olivierkes/manuskript/issues/652)
- story line feature crashing [\#620](https://github.com/olivierkes/manuskript/issues/620)
- Italian translation not applied, application still english. [\#599](https://github.com/olivierkes/manuskript/issues/599)
- Adding Persian\(Farsi\) in Weblate [\#596](https://github.com/olivierkes/manuskript/issues/596)
- Importing images into Manuskript [\#593](https://github.com/olivierkes/manuskript/issues/593)
- British English translation [\#592](https://github.com/olivierkes/manuskript/issues/592)
- Crashes in outliner [\#582](https://github.com/olivierkes/manuskript/issues/582)
**Merged pull requests:**
- Changes to Revisions UI [\#655](https://github.com/olivierkes/manuskript/pull/655) ([worstje](https://github.com/worstje))
- Restore progress bar functionality [\#654](https://github.com/olivierkes/manuskript/pull/654) ([worstje](https://github.com/worstje))
- Default keep revisions to disabled, and remove tests for revisions [\#653](https://github.com/olivierkes/manuskript/pull/653) ([gedakc](https://github.com/gedakc))
- Fix word recognition for spell checker, ignore active partial words [\#651](https://github.com/olivierkes/manuskript/pull/651) ([gedakc](https://github.com/gedakc))
- Fix typo missed in previous commit [\#648](https://github.com/olivierkes/manuskript/pull/648) ([luzpaz](https://github.com/luzpaz))
- Fix source typo [\#645](https://github.com/olivierkes/manuskript/pull/645) ([luzpaz](https://github.com/luzpaz))
- Move Qt 5.11 / 5.12 version warning to Import invocation [\#642](https://github.com/olivierkes/manuskript/pull/642) ([gedakc](https://github.com/gedakc))
- Add DISABLE\_WAYLAND to snapcraft.yaml [\#637](https://github.com/olivierkes/manuskript/pull/637) ([gedakc](https://github.com/gedakc))
- Further refinement of image tooltips \(issue \#593\) [\#629](https://github.com/olivierkes/manuskript/pull/629) ([worstje](https://github.com/worstje))
- Reworking of the translation loading process \(issue \#619\) [\#627](https://github.com/olivierkes/manuskript/pull/627) ([worstje](https://github.com/worstje))
- change markdown to "Markdown" in.... [\#624](https://github.com/olivierkes/manuskript/pull/624) ([leela52452](https://github.com/leela52452))
- Fix tab key order, and default window tab for character & plot panes [\#623](https://github.com/olivierkes/manuskript/pull/623) ([gedakc](https://github.com/gedakc))
- Add British English Translation updates [\#621](https://github.com/olivierkes/manuskript/pull/621) ([gedakc](https://github.com/gedakc))
- Do not prompt "Save project?" when \_Save on quit\_ setting enabled [\#615](https://github.com/olivierkes/manuskript/pull/615) ([gedakc](https://github.com/gedakc))
- Fix exports silently overwriting files \(fixes \#608\) & small fix to dialog logic [\#613](https://github.com/olivierkes/manuskript/pull/613) ([worstje](https://github.com/worstje))
- Working Pandoc import \(fixes \#611\) & small dialog UI update. [\#612](https://github.com/olivierkes/manuskript/pull/612) ([worstje](https://github.com/worstje))
- Fix Linux Travis CI build error - pyenv: version `3.6.3' not installed [\#610](https://github.com/olivierkes/manuskript/pull/610) ([gedakc](https://github.com/gedakc))
- Fix crash when setting word Goal on new Text \(scene\) in Outline pane [\#609](https://github.com/olivierkes/manuskript/pull/609) ([gedakc](https://github.com/gedakc))
- Spelling: Manuscript, could not, process, … No content [\#588](https://github.com/olivierkes/manuskript/pull/588) ([comradekingu](https://github.com/comradekingu))
- fix issue \#468 'unit' is reset [\#587](https://github.com/olivierkes/manuskript/pull/587) ([NocturnalFred](https://github.com/NocturnalFred))
- Fix pandoc export crashes is project title is empty [\#585](https://github.com/olivierkes/manuskript/pull/585) ([gedakc](https://github.com/gedakc))
- Track dirty state and have the UI behave accordingly [\#583](https://github.com/olivierkes/manuskript/pull/583) ([worstje](https://github.com/worstje))
- Fix crash if invalid character is inserted into the text. [\#578](https://github.com/olivierkes/manuskript/pull/578) ([kakaroto](https://github.com/kakaroto))
- Fix crash if using a custom pandoc installation [\#577](https://github.com/olivierkes/manuskript/pull/577) ([kakaroto](https://github.com/kakaroto))
- Fix dialog windows being created outside the desktop area [\#576](https://github.com/olivierkes/manuskript/pull/576) ([kakaroto](https://github.com/kakaroto))
- Fix occasional crashes when \(re\)moving items [\#571](https://github.com/olivierkes/manuskript/pull/571) ([worstje](https://github.com/worstje))
- trying to resolve full screen exit issues on macOS [\#569](https://github.com/olivierkes/manuskript/pull/569) ([dschanoeh](https://github.com/dschanoeh))
- Fix typos in translation format placeholders that lead to crash [\#566](https://github.com/olivierkes/manuskript/pull/566) ([RaphaelWimmer](https://github.com/RaphaelWimmer))
- Fixed \#549 and refactored the image tooltip code [\#558](https://github.com/olivierkes/manuskript/pull/558) ([worstje](https://github.com/worstje))
- Fix typo [\#548](https://github.com/olivierkes/manuskript/pull/548) ([Acid147](https://github.com/Acid147))
- Fix misc. typos [\#489](https://github.com/olivierkes/manuskript/pull/489) ([luzpaz](https://github.com/luzpaz))
## [0.9.0](https://github.com/olivierkes/manuskript/tree/0.9.0) (2019-04-04)
[Full Changelog](https://github.com/olivierkes/manuskript/compare/0.8.0...0.9.0)
**Implemented enhancements:**
- Fullscreen editor suggestions [\#527](https://github.com/olivierkes/manuskript/issues/527)
- \[Feature Request\] Keyboard shortcuts in Full-Screen mode [\#444](https://github.com/olivierkes/manuskript/issues/444)
- \[Feature Request\] Add Ability to Add Image When Creating Fullscreen Theme [\#399](https://github.com/olivierkes/manuskript/issues/399)
- Making Fullscreen Mode Great Again [\#234](https://github.com/olivierkes/manuskript/issues/234)
**Fixed bugs:**
- Crash when previewing malformed regular expression when compiling [\#488](https://github.com/olivierkes/manuskript/issues/488)
- Spellcheck On/Off setting ignored / Manuskript unresponsive [\#474](https://github.com/olivierkes/manuskript/issues/474)
- Wrong codepage for import causes crash [\#470](https://github.com/olivierkes/manuskript/issues/470)
- Full-screen mode right-click menu black text on black background [\#440](https://github.com/olivierkes/manuskript/issues/440)
- Application language still the same after changing it in the settings. [\#411](https://github.com/olivierkes/manuskript/issues/411)
**Closed issues:**
- Python issues? lxml [\#541](https://github.com/olivierkes/manuskript/issues/541)
- Cannot open a project. [\#529](https://github.com/olivierkes/manuskript/issues/529)
- Corrupted Project File Crashes When Opening. [\#522](https://github.com/olivierkes/manuskript/issues/522)
- Specific document suddenly won't open [\#502](https://github.com/olivierkes/manuskript/issues/502)
- trying to get pandoc to work manuskript 0.8.0 Win10 64 [\#475](https://github.com/olivierkes/manuskript/issues/475)
- Editor does not show text [\#472](https://github.com/olivierkes/manuskript/issues/472)
- Application crashes when trying to save "…" [\#461](https://github.com/olivierkes/manuskript/issues/461)
- Feature Request: script writing interface for manuskript [\#435](https://github.com/olivierkes/manuskript/issues/435)
- suggestion: Use sudo for your Fedora install instructions, not su -c [\#573](https://github.com/olivierkes/manuskript/issues/573)
- Chinese translation filename suffix [\#428](https://github.com/olivierkes/manuskript/issues/428)
**Merged pull requests:**
- Fix color scheme of fullscreen editor [\#539](https://github.com/olivierkes/manuskript/pull/539) ([kakaroto](https://github.com/kakaroto))
- Directory entries in ZIP break loading code [\#531](https://github.com/olivierkes/manuskript/pull/531) ([worstje](https://github.com/worstje))
- Providing a suitable icon for consumption by Windows operating systems [\#530](https://github.com/olivierkes/manuskript/pull/530) ([worstje](https://github.com/worstje))
- Ensure text file open methods use utf-8 encoding [\#515](https://github.com/olivierkes/manuskript/pull/515) ([gedakc](https://github.com/gedakc))
- Fix crash when right-clicking twice on fullscreen panel in Windows 10 [\#514](https://github.com/olivierkes/manuskript/pull/514) ([kakaroto](https://github.com/kakaroto))
- Add support for IPython Jupyter QT Console as a debugging aid [\#513](https://github.com/olivierkes/manuskript/pull/513) ([kakaroto](https://github.com/kakaroto))
- Fix background of popup menus that were transparent \(black\) [\#512](https://github.com/olivierkes/manuskript/pull/512) ([kakaroto](https://github.com/kakaroto))
- Add snap build and package [\#511](https://github.com/olivierkes/manuskript/pull/511) ([tomwardill](https://github.com/tomwardill))
- Add ability to add new background images through UI. [\#510](https://github.com/olivierkes/manuskript/pull/510) ([kakaroto](https://github.com/kakaroto))
- Fullscreen panels improvements [\#509](https://github.com/olivierkes/manuskript/pull/509) ([kakaroto](https://github.com/kakaroto))
- Fix corkView background image on Windows [\#508](https://github.com/olivierkes/manuskript/pull/508) ([kakaroto](https://github.com/kakaroto))
- Do not default spellcheck to True for new editor views [\#506](https://github.com/olivierkes/manuskript/pull/506) ([kakaroto](https://github.com/kakaroto))
- Set editor theme stylesheet to QTextEdit only. [\#504](https://github.com/olivierkes/manuskript/pull/504) ([kakaroto](https://github.com/kakaroto))
- Fix fullscreen editor's myScrollBar delayed destruction causing a crash [\#503](https://github.com/olivierkes/manuskript/pull/503) ([kakaroto](https://github.com/kakaroto))
- 2nd try to fix macOS X blank screen when leaving fullscreen editor mode [\#495](https://github.com/olivierkes/manuskript/pull/495) ([gedakc](https://github.com/gedakc))
- Fix crash when right clicking a word in editor and enchant is not installed [\#492](https://github.com/olivierkes/manuskript/pull/492) ([kakaroto](https://github.com/kakaroto))
- Don't crash if a typo is made in the exporter's regular expression. [\#486](https://github.com/olivierkes/manuskript/pull/486) ([kakaroto](https://github.com/kakaroto))
- Fix crash when previewing pandoc HTML with QTextEdit as web renderer… [\#485](https://github.com/olivierkes/manuskript/pull/485) ([kakaroto](https://github.com/kakaroto))
- Fix crash when 7 pound signs are written alone on a line. [\#484](https://github.com/olivierkes/manuskript/pull/484) ([kakaroto](https://github.com/kakaroto))
- Try to fix macOS X blank screen when leaving editor fullscreen mode [\#482](https://github.com/olivierkes/manuskript/pull/482) ([gedakc](https://github.com/gedakc))
- Fix wrong codepage crash on import with Windows 10 [\#478](https://github.com/olivierkes/manuskript/pull/478) ([gedakc](https://github.com/gedakc))
- Spelling: Manuscript, may have to be restarted [\#454](https://github.com/olivierkes/manuskript/pull/454) ([comradekingu](https://github.com/comradekingu))
- Chinese translation [\#434](https://github.com/olivierkes/manuskript/pull/434) ([lingsamuel](https://github.com/lingsamuel))
- fix translator [\#433](https://github.com/olivierkes/manuskript/pull/433) ([lingsamuel](https://github.com/lingsamuel))
- Remember last accessed directory [\#431](https://github.com/olivierkes/manuskript/pull/431) ([lingsamuel](https://github.com/lingsamuel))
- translation suffix, change translation load order [\#430](https://github.com/olivierkes/manuskript/pull/430) ([lingsamuel](https://github.com/lingsamuel))
## [0.8.0](https://github.com/olivierkes/manuskript/tree/0.8.0) (2018-12-05)
[Full Changelog](https://github.com/olivierkes/manuskript/compare/0.7.0...0.8.0)
**Fixed bugs:**
- Snowflake Method option is greyed out. [\#419](https://github.com/olivierkes/manuskript/issues/419)
- Plots bounce around main, secondary, and minor -- unsatisfactory solution? [\#404](https://github.com/olivierkes/manuskript/issues/404)
- Segmentation fault on import [\#402](https://github.com/olivierkes/manuskript/issues/402)
- "Corrupted" settings and impossibility to start [\#377](https://github.com/olivierkes/manuskript/issues/377)
- Resolution step deleting itself on pressing Ctrl + Backspace [\#375](https://github.com/olivierkes/manuskript/issues/375)
- Develop Branch Crashes in Outline View [\#355](https://github.com/olivierkes/manuskript/issues/355)
- Export crashes, because of encoding to 1250 [\#331](https://github.com/olivierkes/manuskript/issues/331)
- pandoc v2 has deprecated some options and extensions so manuskript is giving error. [\#304](https://github.com/olivierkes/manuskript/issues/304)
- Compile Issue for Pandoc Formats - pandoc.exe incorrect [\#186](https://github.com/olivierkes/manuskript/issues/186)
**Closed issues:**
- Problems with running from 0.7.0 pyinstaller package on mac os x 10.13 [\#386](https://github.com/olivierkes/manuskript/issues/386)
- Old bugs in current version 0.6.0 \(with crosslinks and details\) [\#371](https://github.com/olivierkes/manuskript/issues/371)
- pt\_PT translation and Weblate [\#408](https://github.com/olivierkes/manuskript/issues/408)
- Italian translation [\#395](https://github.com/olivierkes/manuskript/issues/395)
- Snowflake view mode always disabled [\#45](https://github.com/olivierkes/manuskript/issues/45)
**Merged pull requests:**
- Remove unimplemented snowflake view mode menu entry [\#424](https://github.com/olivierkes/manuskript/pull/424) ([gedakc](https://github.com/gedakc))
- Increase Travis CI macOS X build minimum to Sierra \(10.12\) [\#423](https://github.com/olivierkes/manuskript/pull/423) ([gedakc](https://github.com/gedakc))
- Remove plot resolution step key bindings Ctrl+Enter and Ctrl+Backspace [\#420](https://github.com/olivierkes/manuskript/pull/420) ([gedakc](https://github.com/gedakc))
- Add support for pandoc version 2 [\#418](https://github.com/olivierkes/manuskript/pull/418) ([gedakc](https://github.com/gedakc))
- Prevent build and deploy steps for linux on Travis CI [\#414](https://github.com/olivierkes/manuskript/pull/414) ([gedakc](https://github.com/gedakc))
- Limit pyinstaller package build and deploy to osx on Travis CI [\#413](https://github.com/olivierkes/manuskript/pull/413) ([gedakc](https://github.com/gedakc))
- Fix segmentation fault on import [\#412](https://github.com/olivierkes/manuskript/pull/412) ([gedakc](https://github.com/gedakc))
- Fix pytest warnings [\#407](https://github.com/olivierkes/manuskript/pull/407) ([gedakc](https://github.com/gedakc))
- Fix plot importance changes if delete earlier plot and click other plots [\#406](https://github.com/olivierkes/manuskript/pull/406) ([gedakc](https://github.com/gedakc))
- Enable testing in TravisCI [\#403](https://github.com/olivierkes/manuskript/pull/403) ([katafrakt](https://github.com/katafrakt))
- Fix Travis CI build for Mac OSX - pip3: command not found [\#400](https://github.com/olivierkes/manuskript/pull/400) ([gedakc](https://github.com/gedakc))
- Moved incorrectly placed parameter to correct place. Closes \#377. [\#389](https://github.com/olivierkes/manuskript/pull/389) ([RiderExMachina](https://github.com/RiderExMachina))
## [0.7.0](https://github.com/olivierkes/manuskript/tree/0.7.0) (2018-08-15)
[Full Changelog](https://github.com/olivierkes/manuskript/compare/0.6.0...0.7.0)
**Implemented enhancements:**
- Display images as tooltip [\#270](https://github.com/olivierkes/manuskript/issues/270)
- Focus mode [\#259](https://github.com/olivierkes/manuskript/issues/259)
- Add markdown support of other tabs [\#232](https://github.com/olivierkes/manuskript/issues/232)
- Translation automation [\#228](https://github.com/olivierkes/manuskript/issues/228)
- Add: command line parameter to open project [\#223](https://github.com/olivierkes/manuskript/issues/223)
- Moving World Items [\#219](https://github.com/olivierkes/manuskript/issues/219)
- Make http links clickable in markdown editor [\#215](https://github.com/olivierkes/manuskript/issues/215)
- Feature suggestion: Typewriter scrolling. [\#175](https://github.com/olivierkes/manuskript/issues/175)
- Request for Bullets and Numbering option [\#123](https://github.com/olivierkes/manuskript/issues/123)
- Markdown syntax highlighting [\#13](https://github.com/olivierkes/manuskript/issues/13)
- Add moving World Items [\#298](https://github.com/olivierkes/manuskript/pull/298) ([JackXVII](https://github.com/JackXVII))
**Fixed bugs:**
- Install on MacOsX failed [\#282](https://github.com/olivierkes/manuskript/issues/282)
- Crash if Cheatsheet filter term not found and Enter key pressed [\#354](https://github.com/olivierkes/manuskript/issues/354)
- Overlay status bar prevents access to add/delete world item icons when displaying a message [\#307](https://github.com/olivierkes/manuskript/issues/307)
- Deleting multiple World items leaves/creates two empty items [\#306](https://github.com/olivierkes/manuskript/issues/306)
- Underline causes false spelling error [\#283](https://github.com/olivierkes/manuskript/issues/283)
- .DS\_Store files let crash Manuskript when opening project [\#281](https://github.com/olivierkes/manuskript/issues/281)
- Programm killed by Hovereffekt? [\#275](https://github.com/olivierkes/manuskript/issues/275)
- Spell check is crashing the program [\#273](https://github.com/olivierkes/manuskript/issues/273)
- Highlight Contrast Problem [\#272](https://github.com/olivierkes/manuskript/issues/272)
- Segfault when pasting text with focus mode enabled [\#271](https://github.com/olivierkes/manuskript/issues/271)
- Compile Check Box not working in Outline view [\#263](https://github.com/olivierkes/manuskript/issues/263)
- Manuskript response slow with recent addition of focus mode [\#261](https://github.com/olivierkes/manuskript/issues/261)
- Organize Menu is not disabled on startup [\#260](https://github.com/olivierkes/manuskript/issues/260)
- Ctrl+tab gets trapped in Debug tab [\#249](https://github.com/olivierkes/manuskript/issues/249)
- Index card status can spillover [\#246](https://github.com/olivierkes/manuskript/issues/246)
- Cannot write a summary on a plot resolution step [\#240](https://github.com/olivierkes/manuskript/issues/240)
- Format buttons in text editor window not working [\#59](https://github.com/olivierkes/manuskript/issues/59)
- stop crash when click btnGoUp and current editor is None [\#318](https://github.com/olivierkes/manuskript/pull/318) ([Windspar](https://github.com/Windspar))
- Avoid crash on spellcheck by ensuring enchant dictionary exists [\#303](https://github.com/olivierkes/manuskript/pull/303) ([gedakc](https://github.com/gedakc))
- Skip loading directory and file names that begin with a period [\#302](https://github.com/olivierkes/manuskript/pull/302) ([gedakc](https://github.com/gedakc))
**Closed issues:**
- \[Feature request\] Russian translation [\#358](https://github.com/olivierkes/manuskript/issues/358)
- Manuskript crashes during save process and "corrupts" the msk-file [\#352](https://github.com/olivierkes/manuskript/issues/352)
- Add polish translation [\#289](https://github.com/olivierkes/manuskript/issues/289)
- \[Feature request\] Accept first command line argument as project file name to open [\#278](https://github.com/olivierkes/manuskript/issues/278)
- Status bar distracting when saving with current develop branch [\#262](https://github.com/olivierkes/manuskript/issues/262)
- Editor Consistency [\#257](https://github.com/olivierkes/manuskript/issues/257)
- French Tab in English Mode [\#253](https://github.com/olivierkes/manuskript/issues/253)
- I want to translate it to portuguese [\#230](https://github.com/olivierkes/manuskript/issues/230)
**Merged pull requests:**
- Fix Travix CI build error on OSX installing python3 [\#338](https://github.com/olivierkes/manuskript/pull/338) ([gedakc](https://github.com/gedakc))
- Use QPersistentModelIndex in textEditView [\#308](https://github.com/olivierkes/manuskript/pull/308) ([JackXVII](https://github.com/JackXVII))
- Add automated script to create RPM package [\#368](https://github.com/olivierkes/manuskript/pull/368) ([gedakc](https://github.com/gedakc))
- Build MacOS release with XCode 7.3 image [\#287](https://github.com/olivierkes/manuskript/pull/287) ([katafrakt](https://github.com/katafrakt))
## [0.6.0](https://github.com/olivierkes/manuskript/tree/0.6.0) (2017-11-29)
[Full Changelog](https://github.com/olivierkes/manuskript/compare/0.5.0...0.6.0)
**Implemented enhancements:**
- Add markdown support of other tabs [\#232](https://github.com/olivierkes/manuskript/issues/232)
- Translation automation [\#228](https://github.com/olivierkes/manuskript/issues/228)
- Feature suggestion: Typewriter scrolling. [\#175](https://github.com/olivierkes/manuskript/issues/175)
- Adds: document menu \(copy, paste, delete, duplicate, split, merge, etc.\) [\#229](https://github.com/olivierkes/manuskript/issues/229)
- Add transparent text editor [\#216](https://github.com/olivierkes/manuskript/issues/216)
- Add Mind Map Import [\#208](https://github.com/olivierkes/manuskript/issues/208)
@ -48,6 +321,7 @@
- Adds: Import OPML [\#192](https://github.com/olivierkes/manuskript/pull/192) ([camstevenson](https://github.com/camstevenson))
## [0.5.0](https://github.com/olivierkes/manuskript/tree/0.5.0) (2017-10-31)
[Full Changelog](https://github.com/olivierkes/manuskript/compare/0.4.0...0.5.0)
**Implemented enhancements:**
@ -92,6 +366,7 @@
- In revision mode text, selecting group doesn't load text-preferences right. [\#51](https://github.com/olivierkes/manuskript/issues/51)
- Undo/redo works in some text areas but not others [\#34](https://github.com/olivierkes/manuskript/issues/34)
- Some bugs in Windows XP and Ubuntu 15.1 [\#25](https://github.com/olivierkes/manuskript/issues/25)
- Stylesheet error on windows [\#18](https://github.com/olivierkes/manuskript/issues/18)
- Manuskript fails to load last state of panels [\#14](https://github.com/olivierkes/manuskript/issues/14)
- Multiple selections of items sometimes gets Notes/references field to be ereased [\#10](https://github.com/olivierkes/manuskript/issues/10)
@ -125,6 +400,7 @@
- Fixes: incorrect reference to 32px icon [\#97](https://github.com/olivierkes/manuskript/pull/97) ([gedakc](https://github.com/gedakc))
## [0.4.0](https://github.com/olivierkes/manuskript/tree/0.4.0) (2017-05-25)
[Full Changelog](https://github.com/olivierkes/manuskript/compare/0.3.0...0.4.0)
**Implemented enhancements:**
@ -132,7 +408,6 @@
- Export into text? \[feature suggestion\] [\#80](https://github.com/olivierkes/manuskript/issues/80)
- Default background for fullscreen mode is unusable \[minor\] [\#79](https://github.com/olivierkes/manuskript/issues/79)
- Documention Needed [\#69](https://github.com/olivierkes/manuskript/issues/69)
- Snowflage view mode always disable [\#45](https://github.com/olivierkes/manuskript/issues/45)
- Compile dialog issues: cancel doesn't seem to do anything, default ouput directory wrong [\#77](https://github.com/olivierkes/manuskript/issues/77)
- OS X app with Platypus [\#28](https://github.com/olivierkes/manuskript/issues/28)
@ -198,6 +473,7 @@
- Added spanish translation \(and changed "chuleta" for "guía rápida"\). [\#66](https://github.com/olivierkes/manuskript/pull/66) ([jmgaguilera](https://github.com/jmgaguilera))
## [0.3.0](https://github.com/olivierkes/manuskript/tree/0.3.0) (2016-03-31)
[Full Changelog](https://github.com/olivierkes/manuskript/compare/0.2.0...0.3.0)
**Fixed bugs:**
@ -212,6 +488,7 @@
- Windows installation issue [\#16](https://github.com/olivierkes/manuskript/issues/16)
## [0.2.0](https://github.com/olivierkes/manuskript/tree/0.2.0) (2016-02-28)
[Full Changelog](https://github.com/olivierkes/manuskript/compare/0.1.1...0.2.0)
**Fixed bugs:**
@ -220,6 +497,7 @@
- Save file doesn't automatically add .msk [\#2](https://github.com/olivierkes/manuskript/issues/2)
## [0.1.1](https://github.com/olivierkes/manuskript/tree/0.1.1) (2016-02-08)
[Full Changelog](https://github.com/olivierkes/manuskript/compare/0.1.0...0.1.1)
**Fixed bugs:**
@ -228,5 +506,8 @@
## [0.1.0](https://github.com/olivierkes/manuskript/tree/0.1.0) (2016-02-06)
[Full Changelog](https://github.com/olivierkes/manuskript/compare/5df82d5e2de7cadd75b013c48ce4575688dd804a...0.1.0)
\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*
\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*

View file

@ -29,7 +29,7 @@ With Manuskript you can:
* View [Story line](http://www.theologeek.ch/manuskript/2016/02/28/story-line/)
* Compose with
[fiction or non-fiction templates and writing modes](http://www.theologeek.ch/manuskript/2016/03/31/writing-modes-simple-fiction/)
* Export to HTML, ePub, OpenDocument, DocX, PDF, and
* Import and export document formats such as HTML, ePub, OpenDocument, DocX, and
[more](https://github.com/olivierkes/manuskript/wiki/Import-and-Export-capabilities)

View file

@ -89,8 +89,26 @@ SOURCES += ../manuskript/ui/views/outlineBasics.py
SOURCES += ../manuskript/ui/views/outlineDelegates.py
SOURCES += ../manuskript/ui/collapsibleDockWidgets.py
TRANSLATIONS += manuskript_fr.ts
TRANSLATIONS += manuskript_es.ts
TRANSLATIONS += manuskript_ar_SA.ts
TRANSLATIONS += manuskript_de.ts
TRANSLATIONS += manuskript_sv.ts
TRANSLATIONS += manuskript_en_GB.ts
TRANSLATIONS += manuskript_es.ts
TRANSLATIONS += manuskript_fa.ts
TRANSLATIONS += manuskript_fr.ts
TRANSLATIONS += manuskript_hu.ts
TRANSLATIONS += manuskript_id.ts
TRANSLATIONS += manuskript_it.ts
TRANSLATIONS += manuskript_ja.ts
TRANSLATIONS += manuskript_ko.ts
TRANSLATIONS += manuskript_nb_NO.ts
TRANSLATIONS += manuskript_nl.ts
TRANSLATIONS += manuskript_pl.ts
TRANSLATIONS += manuskript_pt_BR.ts
TRANSLATIONS += manuskript_pt_PT.ts
TRANSLATIONS += manuskript_ro.ts
TRANSLATIONS += manuskript_ru.ts
TRANSLATIONS += manuskript_sv.ts
TRANSLATIONS += manuskript_tr.ts
TRANSLATIONS += manuskript_uk.ts
TRANSLATIONS += manuskript_zh_CN.ts
TRANSLATIONS += manuskript_zh_HANT.ts

BIN
i18n/manuskript_ar_SA.qm Normal file

Binary file not shown.

4547
i18n/manuskript_ar_SA.ts Normal file

File diff suppressed because it is too large Load diff

Binary file not shown.

File diff suppressed because it is too large Load diff

BIN
i18n/manuskript_en_GB.qm Normal file

Binary file not shown.

4541
i18n/manuskript_en_GB.ts Normal file

File diff suppressed because it is too large Load diff

Binary file not shown.

File diff suppressed because it is too large Load diff

1
i18n/manuskript_fa.qm Normal file
View file

@ -0,0 +1 @@
<クd<>箆!ソ`。スン

4536
i18n/manuskript_fa.ts Normal file

File diff suppressed because it is too large Load diff

Binary file not shown.

File diff suppressed because it is too large Load diff

BIN
i18n/manuskript_hu.qm Normal file

Binary file not shown.

4578
i18n/manuskript_hu.ts Normal file

File diff suppressed because it is too large Load diff

BIN
i18n/manuskript_id.qm Normal file

Binary file not shown.

4536
i18n/manuskript_id.ts Normal file

File diff suppressed because it is too large Load diff

BIN
i18n/manuskript_it.qm Normal file

Binary file not shown.

4581
i18n/manuskript_it.ts Normal file

File diff suppressed because it is too large Load diff

BIN
i18n/manuskript_ja.qm Normal file

Binary file not shown.

4543
i18n/manuskript_ja.ts Normal file

File diff suppressed because it is too large Load diff

BIN
i18n/manuskript_ko.qm Normal file

Binary file not shown.

4562
i18n/manuskript_ko.ts Normal file

File diff suppressed because it is too large Load diff

BIN
i18n/manuskript_nb_NO.qm Normal file

Binary file not shown.

4556
i18n/manuskript_nb_NO.ts Normal file

File diff suppressed because it is too large Load diff

BIN
i18n/manuskript_nl.qm Normal file

Binary file not shown.

4547
i18n/manuskript_nl.ts Normal file

File diff suppressed because it is too large Load diff

BIN
i18n/manuskript_pl.qm Normal file

Binary file not shown.

File diff suppressed because it is too large Load diff

BIN
i18n/manuskript_pt_BR.qm Normal file

Binary file not shown.

4578
i18n/manuskript_pt_BR.ts Normal file

File diff suppressed because it is too large Load diff

BIN
i18n/manuskript_pt_PT.qm Normal file

Binary file not shown.

4586
i18n/manuskript_pt_PT.ts Normal file

File diff suppressed because it is too large Load diff

BIN
i18n/manuskript_ro.qm Normal file

Binary file not shown.

4536
i18n/manuskript_ro.ts Normal file

File diff suppressed because it is too large Load diff

BIN
i18n/manuskript_ru.qm Normal file

Binary file not shown.

4581
i18n/manuskript_ru.ts Normal file

File diff suppressed because it is too large Load diff

Binary file not shown.

File diff suppressed because it is too large Load diff

BIN
i18n/manuskript_tr.qm Normal file

Binary file not shown.

4536
i18n/manuskript_tr.ts Normal file

File diff suppressed because it is too large Load diff

BIN
i18n/manuskript_uk.qm Normal file

Binary file not shown.

4558
i18n/manuskript_uk.ts Normal file

File diff suppressed because it is too large Load diff

BIN
i18n/manuskript_zh_CN.qm Normal file

Binary file not shown.

4542
i18n/manuskript_zh_CN.ts Normal file

File diff suppressed because it is too large Load diff

BIN
i18n/manuskript_zh_HANT.qm Normal file

Binary file not shown.

4536
i18n/manuskript_zh_HANT.ts Normal file

File diff suppressed because it is too large Load diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

View file

@ -20,32 +20,35 @@ profile:
compile:
cd manuskript && python3 setup.py build_ext --inplace
callgraph:
cd manuskript; pycallgraph myoutput -- main.py
translation:
pylupdate5 -noobsolete i18n/manuskript.pro
linguist:
linguist i18n/manuskript_fr.ts
lrelease i18n/manuskript_fr.ts
i18n: $(QMs)
pyinstaller:
python3 /usr/local/bin/pyinstaller manuskript.spec
snappkg:
snapcraft snap
stats:
python3 libs/gh-release-stats.py olivierkes manuskript -d
%_rc.py : %.qrc
pyrcc5 "$<" -o "$@"
pyrcc5 "$<" -o "$@"
%.py : %.ui
# pyuic4 "$<" > "$@"
pyuic5 "$<" > "$@"
# pyuic4 "$<" > "$@"
pyuic5 "$<" > "$@"
%.qm: %.ts
lrelease "$<"

View file

@ -29,7 +29,8 @@ exe = EXE(pyz,
debug=False,
strip=False,
upx=True,
console=True )
console=True,
icon=os.path.join(SPECPATH, 'icons/Manuskript/manuskript.ico') )
coll = COLLECT(exe,
a.binaries,
a.zipfiles,

View file

@ -59,7 +59,7 @@ class basicExporter:
run = self.customPath
else:
print("Error: no command for", self.name)
return
return None
r = subprocess.check_output([run] + args) # timeout=.2
return r.decode("utf-8")

View file

@ -21,6 +21,7 @@ class HTML(markdown):
exportVarName = "lastManuskriptHTML"
exportFilter = "HTML files (*.html);; Any files (*)"
exportDefaultSuffix = ".html"
def isValid(self):
return MD is not None

View file

@ -16,6 +16,7 @@ class markdown(plainText):
exportVarName = "lastManuskriptMarkdown"
exportFilter = "Markdown files (*.md);; Any files (*)"
exportDefaultSuffix = ".md"
icon = "text-x-markdown"
def settingsWidget(self):

View file

@ -2,18 +2,18 @@
# --!-- coding: utf8 --!--
import re
from PyQt5.QtGui import QFont, QTextCharFormat
from PyQt5.QtWidgets import QPlainTextEdit, qApp, QFrame, QFileDialog
from PyQt5.QtWidgets import QPlainTextEdit, qApp, QFrame, QFileDialog, QMessageBox
from manuskript.exporter.basic import basicFormat
from manuskript.functions import mainWindow
from manuskript.functions import mainWindow, getSaveFileNameWithSuffix
from manuskript.models import outlineItem
from manuskript.ui.exporters.manuskript.plainTextSettings import exporterSettings
import codecs
class plainText(basicFormat):
name = qApp.translate("Export", "Plain text")
description = qApp.translate("Export", """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>.""")
by Manuskript, for example <a href='www.fountain.io'>Fountain</a>.""")
implemented = True
requires = {
"Settings": True,
@ -24,6 +24,7 @@ class plainText(basicFormat):
# Default settings used in self.getExportFilename. For easy subclassing when exporting plaintext.
exportVarName = "lastPlainText"
exportFilter = "Text files (*.txt);; Any files (*)"
exportDefaultSuffix = ".txt" # qt ignores the period, but it is clearer in our code to have it
def __init__(self):
pass
@ -41,7 +42,12 @@ class plainText(basicFormat):
def output(self, settingsWidget):
settings = settingsWidget.getSettings()
return self.concatenate(mainWindow().mdlOutline.rootItem, settings)
try:
return self.concatenate(mainWindow().mdlOutline.rootItem, settings)
except re.error as e:
QMessageBox.warning(mainWindow().dialog, qApp.translate("Export", "Error"),
qApp.translate("Export", "Could not process regular expression: \n{}").format(str(e)))
return ""
def getExportFilename(self, settingsWidget, varName=None, filter=None):
@ -59,30 +65,18 @@ class plainText(basicFormat):
else:
filename = ""
filename, filter = QFileDialog.getSaveFileName(settingsWidget.parent(),
caption=qApp.translate("Export", "Chose output file..."),
filter=filter,
directory=filename)
filename, filter = getSaveFileNameWithSuffix(settingsWidget.parent(),
caption=qApp.translate("Export", "Choose output file…"),
filter=filter,
directory=filename,
defaultSuffix=self.exportDefaultSuffix)
if filename:
s[varName] = filename
settingsWidget.settings["Output"] = s
# Auto adds extension if necessary
try:
# Extract the extension from "Some name (*.ext)"
ext = filter.split("(")[1].split(")")[0]
ext = ext.split(".")[1]
if " " in ext: # In case there are multiple extensions: "Images (*.png *.jpg)"
ext = ext.split(" ")[0]
except:
ext = ""
if ext and filename[-len(ext)-1:] != ".{}".format(ext):
filename += "." + ext
# Save settings
settingsWidget.writeSettings()
# Save settings
settingsWidget.writeSettings()
return filename
@ -90,15 +84,16 @@ class plainText(basicFormat):
settings = settingsWidget.getSettings()
filename = self.getExportFilename(settingsWidget)
settingsWidget.writeSettings()
content = self.output(settingsWidget)
if not content:
print("Error: content is empty. Nothing saved.")
return
if filename:
with open(filename, "w") as f:
settingsWidget.writeSettings()
content = self.output(settingsWidget)
if not content:
print("Error: No content. Nothing saved.")
return
with open(filename, "w", encoding='utf8') as f:
f.write(content)
def preview(self, settingsWidget, previewWidget):

View file

@ -1,6 +1,6 @@
#!/usr/bin/env python
# --!-- coding: utf8 --!--
from PyQt5.QtWidgets import qApp
from PyQt5.QtWidgets import qApp, QTextEdit
from PyQt5.QtCore import QUrl
from manuskript.exporter.manuskript import HTML as MskHTML
@ -16,6 +16,7 @@ class HTML(abstractPlainText):
exportVarName = "lastPandocHTML"
toFormat = "html"
exportFilter = "HTML files (*.html);; Any files (*)"
exportDefaultSuffix = ".html"
requires = {
"Settings": True,
"Preview": True,
@ -40,4 +41,8 @@ class HTML(abstractPlainText):
previewWidget.widget(0).setPlainText(src)
self.preparesTextEditView(previewWidget.widget(1), settings["Preview"]["PreviewFont"])
previewWidget.widget(1).setPlainText(html)
previewWidget.widget(2).setHtml(html, QUrl.fromLocalFile(path))
w2 = previewWidget.widget(2)
if isinstance(w2, QTextEdit):
w2.setHtml(html)
else:
w2.setHtml(html, QUrl.fromLocalFile(path))

View file

@ -23,6 +23,7 @@ class PDF(abstractOutput):
exportVarName = "lastPandocPDF"
toFormat = "pdf"
exportFilter = "PDF files (*.pdf);; Any files (*)"
exportDefaultSuffix = ".pdf"
requires = {
"Settings": True,
"Preview": True,

View file

@ -17,7 +17,7 @@ from manuskript.functions import mainWindow
class pandocExporter(basicExporter):
name = "Pandoc"
description = qApp.translate("Export", """<p>A universal document converter. Can be used to convert markdown to a wide range of other
description = qApp.translate("Export", """<p>A universal document converter. Can be used to convert Markdown to a wide range of other
formats.</p>
<p>Website: <a href="http://www.pandoc.org">http://pandoc.org/</a></p>
""")
@ -48,7 +48,14 @@ class pandocExporter(basicExporter):
return ""
def convert(self, src, args, outputfile=None):
args = [self.cmd] + args
if self.isValid() == 2:
run = self.cmd
elif self.isValid() == 1:
run = self.customPath
else:
print("Error: no command for pandoc")
return None
args = [run] + args
if outputfile:
args.append("--output={}".format(outputfile))
@ -67,6 +74,12 @@ class pandocExporter(basicExporter):
if var and item and item.text().strip():
args.append("--variable={}:{}".format(var, item.text().strip()))
# Add title metadata required for pandoc >= 2.x
title = "Untitled"
if mainWindow().mdlFlatData.item(0, 0):
title = mainWindow().mdlFlatData.item(0, 0).text().strip()
args.append("--metadata=title:{}".format(title))
qApp.setOverrideCursor(QCursor(Qt.WaitCursor))
p = subprocess.Popen(
@ -83,8 +96,11 @@ class pandocExporter(basicExporter):
qApp.restoreOverrideCursor()
if stderr:
err = stderr.decode("utf-8")
if stderr or p.returncode != 0:
err = "ERROR on export" + "\n" \
+ "Return code" + ": %d\n" % (p.returncode) \
+ "Command and parameters" + ":\n%s\n" % (p.args) \
+ "Stderr content" + ":\n" + stderr.decode("utf-8")
print(err)
QMessageBox.critical(mainWindow().dialog, qApp.translate("Export", "Error"), err)
return None

View file

@ -10,6 +10,7 @@ class abstractOutput(abstractPlainText):
toFormat = "SUBCLASSME"
icon = "SUBCLASSME"
exportFilter = "SUBCLASSME"
exportDefaultSuffix = ".SUBCLASSME"
requires = {
"Settings": True,
"Preview": False,

View file

@ -1,5 +1,7 @@
#!/usr/bin/env python
# --!-- coding: utf8 --!--
import re
from PyQt5.QtGui import QTextCharFormat, QFont
from PyQt5.QtWidgets import qApp, QVBoxLayout, QCheckBox, QWidget, QHBoxLayout, QLabel, QSpinBox, QComboBox
@ -14,12 +16,20 @@ class abstractPlainText(markdown):
toFormat = "SUBCLASSME"
icon = "SUBCLASSME"
exportFilter = "SUBCLASSME"
exportDefaultSuffix = ".SUBCLASSME"
def __init__(self, exporter):
self.exporter = exporter
def settingsWidget(self):
w = pandocSettings(self, toFormat=self.toFormat)
# Get pandoc major version to determine valid command line options
p = re.compile(r'pandoc (\d+)\..*')
m = p.match(self.exporter.version())
if m:
majorVersion = m.group(1)
else:
majorVersion = ""
w = pandocSettings(self, majorVersion, toFormat=self.toFormat)
w.loadSettings()
return w
@ -92,8 +102,10 @@ class pandocSettings(markdownSettings):
"TOC-depth": pandocSetting("--toc-depth=", "number", "",
qApp.translate("Export", "Number of sections level to include in TOC: "),
default=3, min=1, max=6),
# pandoc v1 only
"smart": pandocSetting("--smart", "checkbox", "",
qApp.translate("Export", "Typographically correct output")),
# pandoc v1 only
"normalize": pandocSetting("--normalize", "checkbox", "",
qApp.translate("Export", "Normalize the document (cleaner)")),
"base-header": pandocSetting("--base-header-level=", "number", "",
@ -111,9 +123,14 @@ class pandocSettings(markdownSettings):
qApp.translate("Export", "Self-contained HTML files, with no dependencies")),
"q-tags": pandocSetting("--html-q-tags", "checkbox", "html",
qApp.translate("Export", "Use <q> tags for quotes in HTML")),
# pandoc v1 only
"latex-engine": pandocSetting("--latex-engine=", "combo", "pdf",
qApp.translate("Export", "LaTeX engine used to produce the PDF."),
vals="pdflatex|lualatex|xelatex"),
# pandoc v2
"pdf-engine": pandocSetting("--pdf-engine=", "combo", "pdf",
qApp.translate("Export", "LaTeX engine used to produce the PDF."),
vals="pdflatex|lualatex|xelatex"),
"epub3": pandocSetting("EXTepub3", "checkbox", "epub",
qApp.translate("Export", "Convert to ePUB3")),
}
@ -138,17 +155,24 @@ class pandocSettings(markdownSettings):
}
def __init__(self, _format, toFormat=None, parent=None):
def __init__(self, _format, majorVersion="", toFormat=None, parent=None):
markdownSettings.__init__(self, _format, parent)
self.format = toFormat
self.majorVersion = majorVersion
w = QWidget(self)
w.setLayout(QVBoxLayout())
self.grpPandocGeneral = self.collapsibleGroupBox(self.tr("General"), w)
self.addSettingsWidget("smart", self.grpPandocGeneral)
self.addSettingsWidget("normalize", self.grpPandocGeneral)
if majorVersion == "1":
# pandoc v1 only
self.addSettingsWidget("smart", self.grpPandocGeneral)
self.addSettingsWidget("normalize", self.grpPandocGeneral)
else:
# pandoc v2
self.settingsList.pop("smart", None)
self.settingsList.pop("normalize", None)
self.addSettingsWidget("base-header", self.grpPandocGeneral)
self.addSettingsWidget("standalone", self.grpPandocGeneral)
self.addSettingsWidget("disable-YAML", self.grpPandocGeneral)
@ -164,7 +188,14 @@ class pandocSettings(markdownSettings):
self.addSettingsWidget("atx", self.grpPandocSpecific)
self.addSettingsWidget("self-contained", self.grpPandocSpecific)
self.addSettingsWidget("q-tags", self.grpPandocSpecific)
self.addSettingsWidget("latex-engine", self.grpPandocSpecific)
if majorVersion == "1":
# pandoc v1 only
self.addSettingsWidget("latex-engine", self.grpPandocSpecific)
self.settingsList.pop("pdf-engine", None)
else:
# pandoc v2
self.settingsList.pop("latex-engine", None)
self.addSettingsWidget("pdf-engine", self.grpPandocSpecific)
self.addSettingsWidget("epub3", self.grpPandocSpecific)
# PDF settings

View file

@ -13,6 +13,7 @@ class ePub(abstractOutput):
exportVarName = "lastPandocePub"
toFormat = "epub"
exportFilter = "ePub files (*.epub);; Any files (*)"
exportDefaultSuffix = ".epub"
class OpenDocument(abstractOutput):
@ -23,6 +24,7 @@ class OpenDocument(abstractOutput):
toFormat = "odt"
icon = "application-vnd.oasis.opendocument.text"
exportFilter = "OpenDocument files (*.odt);; Any files (*)"
exportDefaultSuffix = ".odt"
class DocX(abstractOutput):
@ -33,4 +35,5 @@ class DocX(abstractOutput):
toFormat = "docx"
icon = "application-vnd.openxmlformats-officedocument.wordprocessingml.document"
exportFilter = "DocX files (*.docx);; Any files (*)"
exportDefaultSuffix = ".docx"

View file

@ -14,6 +14,7 @@ class markdown(abstractPlainText):
exportVarName = "lastPandocMarkdown"
toFormat = "markdown"
exportFilter = "Markdown files (*.md);; Any files (*)"
exportDefaultSuffix = ".md"
class reST(abstractPlainText):
@ -24,6 +25,7 @@ class reST(abstractPlainText):
toFormat = "rst"
icon = "text-plain"
exportFilter = "reST files (*.rst);; Any files (*)"
exportDefaultSuffix = ".rst"
class latex(abstractPlainText):
@ -35,6 +37,7 @@ class latex(abstractPlainText):
toFormat = "latex"
icon = "text-x-tex"
exportFilter = "Tex files (*.tex);; Any files (*)"
exportDefaultSuffix = ".tex"
class OPML(abstractPlainText):
@ -47,5 +50,5 @@ class OPML(abstractPlainText):
toFormat = "opml"
icon = "text-x-opml+xml"
exportFilter = "OPML files (*.opml);; Any files (*)"
exportDefaultSuffix = ".opml"

View file

@ -9,7 +9,7 @@ from PyQt5.QtCore import Qt, QRect, QStandardPaths, QObject, QRegExp, QDir
from PyQt5.QtCore import QUrl, QTimer
from PyQt5.QtGui import QBrush, QIcon, QPainter, QColor, QImage, QPixmap
from PyQt5.QtGui import QDesktopServices
from PyQt5.QtWidgets import qApp, QTextEdit
from PyQt5.QtWidgets import qApp, QFileDialog, QTextEdit
from manuskript.enums import Outline
@ -23,6 +23,27 @@ def wordCount(text):
t = [l for l in t if l]
return len(t)
validate_ok = lambda *args, **kwargs: True
def uiParse(input, default, converter, validator=validate_ok):
"""
uiParse is a utility function that intends to make it easy to convert
user input to data that falls in the range of expected values the
program is expecting to handle.
It swallows all exceptions that happen during conversion.
The validator should return True to permit the converted value.
"""
result = default
try:
result = converter(input)
except:
pass # failed to convert
# Whitelist default value in case default type differs from converter output.
if (result != default) and not validator(result):
result = default
return result
def toInt(text):
if text:
@ -50,6 +71,7 @@ def toString(text):
def drawProgress(painter, rect, progress, radius=0):
from manuskript.ui import style as S
progress = toFloat(progress) # handle invalid input (issue #561)
painter.setPen(Qt.NoPen)
painter.setBrush(QColor(S.base)) # "#dddddd"
painter.drawRoundedRect(rect, radius, radius)
@ -385,6 +407,28 @@ def openURL(url):
"""
QDesktopServices.openUrl(QUrl(url))
def getSaveFileNameWithSuffix(parent, caption, directory, filter, options=None, selectedFilter=None, defaultSuffix=None):
"""
A reimplemented version of QFileDialog.getSaveFileName() because we would like to make use
of the QFileDialog.defaultSuffix property that getSaveFileName() does not let us adjust.
Note: knowing the selected filter is not an invitation to change the chosen filename later.
"""
dialog = QFileDialog(parent=parent, caption=caption, directory=directory, filter=filter)
if options:
dialog.setOptions(options)
if defaultSuffix:
dialog.setDefaultSuffix(defaultSuffix)
dialog.setFileMode(QFileDialog.AnyFile)
if hasattr(dialog, 'setSupportedSchemes'): # Pre-Qt5.6 lacks this.
dialog.setSupportedSchemes(("file",))
dialog.setAcceptMode(QFileDialog.AcceptSave)
if selectedFilter:
dialog.selectNameFilter(selectedFilter)
if (dialog.exec() == QFileDialog.Accepted):
return dialog.selectedFiles()[0], dialog.selectedNameFilter()
return None, None
def inspect():
"""
Debugging tool. Call it to see a stack of calls up to that point.
@ -397,3 +441,6 @@ def inspect():
s.lineno,
s.function))
print(" " + "".join(s.code_context))
# Spellchecker loads writablePath from this file, so we need to load it after they get defined
from manuskript.functions.spellchecker import Spellchecker

View file

@ -0,0 +1,429 @@
#!/usr/bin/env python
# --!-- coding: utf8 --!--
import os, gzip, json, glob
from PyQt5.QtCore import QLocale
from collections import OrderedDict
from manuskript.functions import writablePath
try:
import enchant
except ImportError:
enchant = None
try:
import spellchecker as pyspellchecker
except ImportError:
pyspellchecker = None
SYMSPELLPY_MIN_VERSION = "6.3.8"
try:
import symspellpy
import distutils.version
if distutils.version.LooseVersion(symspellpy.__version__) < SYMSPELLPY_MIN_VERSION:
symspellpy = None
except ImportError:
symspellpy = None
class Spellchecker:
dictionaries = {}
# In order of priority
implementations = []
def __init__(self):
pass
@staticmethod
def registerImplementation(impl):
Spellchecker.implementations.append(impl)
@staticmethod
def isInstalled():
for impl in Spellchecker.implementations:
if impl.isInstalled():
return True
return False
@staticmethod
def supportedLibraries():
libs = OrderedDict()
for impl in Spellchecker.implementations:
libs[impl.getLibraryName()] = impl.getLibraryRequirement()
return libs
@staticmethod
def availableLibraries():
ret = []
for impl in Spellchecker.implementations:
if impl.isInstalled():
ret.append(impl.getLibraryName())
return ret
@staticmethod
def availableDictionaries():
dictionaries = OrderedDict()
for impl in Spellchecker.implementations:
if impl.isInstalled():
dictionaries[impl.getLibraryName()] = impl.availableDictionaries()
return dictionaries
@staticmethod
def normalizeDictName(lib, dictionary):
return "{}:{}".format(lib, dictionary)
@staticmethod
def getDefaultDictionary():
for impl in Spellchecker.implementations:
default = impl.getDefaultDictionary()
if default:
return Spellchecker.normalizeDictName(impl.getLibraryName(), default)
return None
@staticmethod
def getLibraryURL(lib=None):
urls = {}
for impl in Spellchecker.implementations:
urls[impl.getLibraryName()] = impl.getLibraryURL()
if lib:
return urls.get(lib, None)
return urls
@staticmethod
def getDictionary(dictionary):
if not dictionary:
dictionary = Spellchecker.getDefaultDictionary()
if not dictionary:
return None
values = dictionary.split(":", 1)
if len(values) == 1:
(lib, name) = (Spellchecker.implementations[0].getLibraryName(), dictionary)
dictionary = Spellchecker.normalizeDictName(lib, name)
else:
(lib, name) = values
try:
d = Spellchecker.dictionaries.get(dictionary, None)
if d is None:
for impl in Spellchecker.implementations:
if impl.isInstalled() and lib == impl.getLibraryName():
d = impl(name)
Spellchecker.dictionaries[dictionary] = d
break
return d
except Exception as e:
pass
return None
class BasicDictionary:
def __init__(self, name):
self._lang = name
if not self._lang:
self._lang = self.getDefaultDictionary()
self._customDict = set()
customPath = self.getCustomDictionaryPath()
try:
with gzip.open(customPath, "rt", encoding='utf-8') as f:
self._customDict = set(json.loads(f.read()))
for word in self._customDict:
self._dict.create_dictionary_entry(word, self.CUSTOM_COUNT)
except:
# If error loading the file, overwrite with empty dictionary
self._saveCustomDict()
@property
def name(self):
return self._lang
@staticmethod
def getLibraryName():
raise NotImplemented
@staticmethod
def getLibraryRequirement():
return None
@staticmethod
def getLibraryURL():
raise NotImplemented
@staticmethod
def isInstalled():
raise NotImplemented
@staticmethod
def getDefaultDictionary():
raise NotImplemented
@staticmethod
def availableDictionaries():
raise NotImplemented
def isMisspelled(self, word):
raise NotImplemented
def getSuggestions(self, word):
raise NotImplemented
def isCustomWord(self, word):
return word.lower() in self._customDict
def addWord(self, word):
word = word.lower()
if not word in self._customDict:
self._customDict.add(word)
self._saveCustomDict()
def removeWord(self, word):
word = word.lower()
if word in self._customDict:
self._customDict.remove(word)
self._saveCustomDict()
@classmethod
def getResourcesPath(cls):
path = os.path.join(writablePath(), "resources", "dictionaries", cls.getLibraryName())
if not os.path.exists(path):
os.makedirs(path)
return path
def getCustomDictionaryPath(self):
return os.path.join(self.getResourcesPath(), "{}.json.gz".format(self._lang))
def _saveCustomDict(self):
customPath = self.getCustomDictionaryPath()
with gzip.open(customPath, "wt") as f:
f.write(json.dumps(list(self._customDict)))
class EnchantDictionary(BasicDictionary):
def __init__(self, name):
self._lang = name
if not (self._lang and enchant.dict_exists(self._lang)):
self._lang = self.getDefaultDictionary()
self._dict = enchant.DictWithPWL(self._lang, self.getCustomDictionaryPath())
@staticmethod
def getLibraryName():
return "PyEnchant"
@staticmethod
def getLibraryURL():
return "https://pypi.org/project/pyenchant/"
@staticmethod
def isInstalled():
return enchant is not None
@staticmethod
def availableDictionaries():
if EnchantDictionary.isInstalled():
return list(map(lambda i: str(i[0]), enchant.list_dicts()))
return []
@staticmethod
def getDefaultDictionary():
if not EnchantDictionary.isInstalled():
return None
default_locale = enchant.get_default_language()
if default_locale and not enchant.dict_exists(default_locale):
default_locale = None
if default_locale is None:
default_locale = QLocale.system().name()
if default_locale is None:
default_locale = self.availableDictionaries()[0]
return default_locale
def isMisspelled(self, word):
return not self._dict.check(word)
def getSuggestions(self, word):
return self._dict.suggest(word)
def isCustomWord(self, word):
return self._dict.is_added(word)
def addWord(self, word):
self._dict.add(word)
def removeWord(self, word):
self._dict.remove(word)
def getCustomDictionaryPath(self):
return os.path.join(self.getResourcesPath(), "{}.txt".format(self.name))
class PySpellcheckerDictionary(BasicDictionary):
def __init__(self, name):
BasicDictionary.__init__(self, name)
self._dict = pyspellchecker.SpellChecker(self.name)
self._dict.word_frequency.load_words(self._customDict)
@staticmethod
def getLibraryName():
return "pyspellchecker"
@staticmethod
def getLibraryURL():
return "https://pyspellchecker.readthedocs.io/en/latest/"
@staticmethod
def isInstalled():
return pyspellchecker is not None
@staticmethod
def availableDictionaries():
if PySpellcheckerDictionary.isInstalled():
dictionaries = []
files = glob.glob(os.path.join(pyspellchecker.__path__[0], "resources", "*.json.gz"))
for file in files:
dictionaries.append(os.path.basename(file)[:-8])
return dictionaries
return []
@staticmethod
def getDefaultDictionary():
if not PySpellcheckerDictionary.isInstalled():
return None
default_locale = QLocale.system().name()
if default_locale:
default_locale = default_locale[0:2]
if default_locale is None:
default_locale = "en"
return default_locale
def isMisspelled(self, word):
return len(self._dict.unknown([word])) > 0
def getSuggestions(self, word):
candidates = self._dict.candidates(word)
if word in candidates:
candidates.remove(word)
return candidates
def addWord(self, word):
BasicDictionary.addWord(self, word)
self._dict.word_frequency.add(word.lower())
def removeWord(self, word):
BasicDictionary.removeWord(self, word)
self._dict.word_frequency.remove(word.lower())
class SymSpellDictionary(BasicDictionary):
CUSTOM_COUNT = 1
DISTANCE = 2
def __init__(self, name):
BasicDictionary.__init__(self, name)
self._dict = symspellpy.SymSpell(self.DISTANCE)
cachePath = self.getCachedDictionaryPath()
try:
if not self._dict.load_pickle(cachePath, False):
raise Exception("Can't load cached dictionary. " +
"File might be corrupted or incompatible with installed symspellpy version")
except:
if pyspellchecker:
path = os.path.join(pyspellchecker.__path__[0], "resources", "{}.json.gz".format(self.name))
if os.path.exists(path):
with gzip.open(path, "rt", encoding='utf-8') as f:
data = json.loads(f.read())
for key in data:
self._dict.create_dictionary_entry(key, data[key])
self._dict.save_pickle(cachePath, False)
for word in self._customDict:
self._dict.create_dictionary_entry(word, self.CUSTOM_COUNT)
def getCachedDictionaryPath(self):
return os.path.join(self.getResourcesPath(), "{}.sym".format(self.name))
@staticmethod
def getLibraryName():
return "symspellpy"
@staticmethod
def getLibraryRequirement():
return ">= " + SYMSPELLPY_MIN_VERSION
@staticmethod
def getLibraryURL():
return "https://github.com/mammothb/symspellpy"
@staticmethod
def isInstalled():
return symspellpy is not None
@classmethod
def availableDictionaries(cls):
if SymSpellDictionary.isInstalled():
files = glob.glob(os.path.join(cls.getResourcesPath(), "*.sym"))
dictionaries = []
for file in files:
dictionaries.append(os.path.basename(file)[:-4])
for sp_dict in PySpellcheckerDictionary.availableDictionaries():
if not sp_dict in dictionaries:
dictionaries.append(sp_dict)
return dictionaries
return []
@staticmethod
def getDefaultDictionary():
if not SymSpellDictionary.isInstalled():
return None
return PySpellcheckerDictionary.getDefaultDictionary()
def isMisspelled(self, word):
suggestions = self._dict.lookup(word.lower(), symspellpy.Verbosity.TOP)
if len(suggestions) > 0 and suggestions[0].distance == 0:
return False
# Try the word as is, since a dictionary might have uppercase letter as part
# of it's spelling ("I'm" or "January" for example)
suggestions = self._dict.lookup(word, symspellpy.Verbosity.TOP)
if len(suggestions) > 0 and suggestions[0].distance == 0:
return False
return True
def getSuggestions(self, word):
upper = word.isupper()
upper1 = word[0].isupper()
suggestions = self._dict.lookup_compound(word, 2)
suggestions.extend(self._dict.lookup(word, symspellpy.Verbosity.CLOSEST))
candidates = []
for sug in suggestions:
if upper:
term = sug.term.upper()
elif upper1:
term = sug.term[0].upper() + sug.term[1:]
else:
term = sug.term
if sug.distance > 0 and not term in candidates:
candidates.append(term)
return candidates
def addWord(self, word):
BasicDictionary.addWord(self, word)
self._dict.create_dictionary_entry(word.lower(), self.CUSTOM_COUNT)
def removeWord(self, word):
BasicDictionary.removeWord(self, word)
# Since 6.3.8
self._dict.delete_dictionary_entry(word)
# Register the implementations in order of priority
Spellchecker.implementations.append(EnchantDictionary)
Spellchecker.registerImplementation(SymSpellDictionary)
Spellchecker.registerImplementation(PySpellcheckerDictionary)

View file

@ -44,7 +44,7 @@ class folderImporter(abstractImporter):
fName, fExt = os.path.splitext(f)
if fExt.lower() in ext:
try:
with open(os.path.join(dirpath, f), "r") as fr:
with open(os.path.join(dirpath, f), "r", encoding="utf-8") as fr:
content = fr.read()
child = outlineItem(title=fName, _type="md", parent=item)
child._data[Outline.text] = content

View file

@ -63,7 +63,7 @@ class markdownImporter(abstractImporter):
if not fromString:
# Read file
with open(filePath, "r") as f:
with open(filePath, "r", encoding="utf-8") as f:
txt = f.read()
else:
txt = fromString

View file

@ -38,6 +38,9 @@ class pandocImporter(abstractImporter):
r = pandocExporter().run(args)
if r is None:
return None
if formatTo == "opml":
return self.opmlImporter.startImport("", parentItem,
settingsWidget, fromString=r)

View file

@ -54,7 +54,7 @@ def loadProject(project):
# Not a zip
else:
with open(project, "r") as f:
with open(project, "r", encoding="utf-8") as f:
version = int(f.read())
print("Loading:", project)

View file

@ -178,7 +178,10 @@ def loadFilesFromZip(zipname):
zf = zipfile.ZipFile(zipname)
files = {}
for f in zf.namelist():
files[os.path.normpath(f)] = zf.read(f)
# Some archiving programs (e.g. 7-Zip) also store entries for the directories when
# creating an archive. We have no use for these entries; skip them entirely.
if f[-1:] != '/':
files[os.path.normpath(f)] = zf.read(f)
return files

View file

@ -118,7 +118,14 @@ def saveProject(zip=None):
# List of files to be moved
moves = []
# MainWindow interaction things.
mw = mainWindow()
project = mw.currentProject
# Sanity check (see PR-583): make sure we actually have a current project.
if project is None:
print("Error: cannot save project because there is no current project in the UI.")
return False
# File format version
files.append(("MANUSKRIPT", "1"))
@ -295,10 +302,8 @@ def saveProject(zip=None):
files.append(("settings.txt", settings.save(protocol=0)))
project = mw.currentProject
# We check if the file exist and we have write access. If the file does
# not exists, we check the parent folder, because it might be a new project.
# not exist, we check the parent folder, because it might be a new project.
if os.path.exists(project) and not os.access(project, os.W_OK) or \
not os.path.exists(project) and not os.access(os.path.dirname(project), os.W_OK):
print("Error: you don't have write access to save this project there.")

View file

@ -2,12 +2,13 @@
import faulthandler
import os
import platform
import sys
import manuskript.ui.views.webView
from PyQt5.QtCore import QLocale, QTranslator, QSettings
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QApplication, qApp
from PyQt5.QtCore import QLocale, QTranslator, QSettings, Qt
from PyQt5.QtGui import QIcon, QColor, QPalette
from PyQt5.QtWidgets import QApplication, qApp, QStyleFactory
from manuskript.functions import appPath, writablePath
from manuskript.version import getVersion
@ -36,23 +37,109 @@ def prepare(tests=False):
app.setStyle(style)
# Translation process
locale = QLocale.system().name()
appTranslator = QTranslator()
appTranslator = QTranslator(app)
# By default: locale
translation = appPath(os.path.join("i18n", "manuskript_{}.qm".format(locale)))
# Load translation from settings
def tryLoadTranslation(translation, source):
"""Tries to load and activate a given translation for use."""
if appTranslator.load(translation, appPath("i18n")):
app.installTranslator(appTranslator)
print("Loaded translation: {}".format(translation))
# Note: QTranslator.load() does some fancy heuristics where it simplifies
# the given locale until it is 'close enough' if the given filename does
# not work out. For example, if given 'i18n/manuskript_en_US.qm', it tries:
# * i18n/manuskript_en_US.qm.qm
# * i18n/manuskript_en_US.qm
# * i18n/manuskript_en_US
# * i18n/manuskript_en.qm
# * i18n/manuskript_en
# * i18n/manuskript.qm
# * i18n/manuskript
# We have no way to determining what it eventually went with, so mind your
# filenames when you observe strange behaviour with the loaded translations.
return True
else:
print("No translation found or loaded. ({})".format(translation))
return False
def activateTranslation(translation, source):
"""Loads the most suitable translation based on the available information."""
using_builtin_translation = True
if (translation != ""): # empty string == 'no translation, use builtin'
if isinstance(translation, str):
if tryLoadTranslation(translation, source):
using_builtin_translation = False
else: # A list of language codes to try. Once something works, we're done.
# This logic is loosely based on the working of QTranslator.load(QLocale, ...);
# it allows us to more accurately detect the language used for the user interface.
for language_code in translation:
lc = language_code.replace('-', '_')
if lc.lower() == 'en_US'.lower():
break
if tryLoadTranslation("manuskript_{}.qm".format(lc), source):
using_builtin_translation = False
break
if using_builtin_translation:
print("Using the builtin translation.")
# Load application translation
translation = ""
source = "default"
if settings.contains("applicationTranslation"):
translation = appPath(os.path.join("i18n", settings.value("applicationTranslation")))
print("Found translation in settings:", translation)
if appTranslator.load(translation):
app.installTranslator(appTranslator)
print(app.tr("Loaded translation: {}.").format(translation))
# Use the language configured by the user.
translation = settings.value("applicationTranslation")
source = "user setting"
else:
print(app.tr("Note: No translator found or loaded for locale {}.").format(locale))
# Auto-detect based on system locale.
translation = QLocale().uiLanguages()
source = "available ui languages"
print("Preferred translation: {} (based on {})".format(("builtin" if translation == "" else translation), source))
activateTranslation(translation, source)
def respectSystemDarkThemeSetting():
"""Adjusts the Qt theme to match the OS 'dark theme' setting configured by the user."""
if platform.system() is not 'Windows':
return
# Basic Windows 10 Dark Theme support.
# Source: https://forum.qt.io/topic/101391/windows-10-dark-theme/4
themeSettings = QSettings("HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", QSettings.NativeFormat)
if themeSettings.value("AppsUseLightTheme") == 0:
darkPalette = QPalette()
darkColor = QColor(45,45,45)
disabledColor = QColor(127,127,127)
darkPalette.setColor(QPalette.Window, darkColor)
darkPalette.setColor(QPalette.WindowText, Qt.white)
darkPalette.setColor(QPalette.Base, QColor(18,18,18))
darkPalette.setColor(QPalette.AlternateBase, darkColor)
darkPalette.setColor(QPalette.ToolTipBase, Qt.white)
darkPalette.setColor(QPalette.ToolTipText, Qt.white)
darkPalette.setColor(QPalette.Text, Qt.white)
darkPalette.setColor(QPalette.Disabled, QPalette.Text, disabledColor)
darkPalette.setColor(QPalette.Button, darkColor)
darkPalette.setColor(QPalette.ButtonText, Qt.white)
darkPalette.setColor(QPalette.Disabled, QPalette.ButtonText, disabledColor)
darkPalette.setColor(QPalette.BrightText, Qt.red)
darkPalette.setColor(QPalette.Link, QColor(42, 130, 218))
darkPalette.setColor(QPalette.Highlight, QColor(42, 130, 218))
darkPalette.setColor(QPalette.HighlightedText, Qt.black)
darkPalette.setColor(QPalette.Disabled, QPalette.HighlightedText, disabledColor)
# Fixes ugly (not to mention hard to read) disabled menu items.
# Source: https://bugreports.qt.io/browse/QTBUG-10322?focusedCommentId=371060#comment-371060
darkPalette.setColor(QPalette.Disabled, QPalette.Light, Qt.transparent)
app.setPalette(darkPalette)
# This broke the Settings Dialog at one point... and then it stopped breaking it.
# TODO: Why'd it break? Check if tooltips look OK... and if not, make them look OK.
#app.setStyleSheet("QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }")
respectSystemDarkThemeSetting()
QIcon.setThemeSearchPaths(QIcon.themeSearchPaths() + [appPath("icons")])
QIcon.setThemeName("NumixMsk")
@ -79,14 +166,59 @@ def prepare(tests=False):
return app, MW
def launch(MW = None):
def launch(app, MW = None):
if MW is None:
from manuskript.functions import mainWindow
MW = mainWindow()
MW.show()
qApp.exec_()
# Support for IPython Jupyter QT Console as a debugging aid.
# Last argument must be --console to enable it
# Code reference :
# https://github.com/ipython/ipykernel/blob/master/examples/embedding/ipkernel_qtapp.py
# https://github.com/ipython/ipykernel/blob/master/examples/embedding/internal_ipkernel.py
if len(sys.argv) > 1 and sys.argv[-1] == "--console":
try:
from IPython.lib.kernel import connect_qtconsole
from ipykernel.kernelapp import IPKernelApp
# Only to ensure matplotlib QT mainloop integration is available
import matplotlib
# Create IPython kernel within our application
kernel = IPKernelApp.instance()
# Initialize it and use matplotlib for main event loop integration with QT
kernel.initialize(['python', '--matplotlib=qt'])
# Create the console in a new process and connect
console = connect_qtconsole(kernel.abs_connection_file, profile=kernel.profile)
# Export MW and app variable to the console's namespace
kernel.shell.user_ns['MW'] = MW
kernel.shell.user_ns['app'] = app
kernel.shell.user_ns['kernel'] = kernel
kernel.shell.user_ns['console'] = console
# When we close manuskript, make sure we close the console process and stop the
# IPython kernel's mainloop, otherwise the app will never finish.
def console_cleanup():
app.quit()
console.kill()
kernel.io_loop.stop()
app.lastWindowClosed.connect(console_cleanup)
# Very important, IPython-specific step: this gets GUI event loop
# integration going, and it replaces calling app.exec_()
kernel.start()
except Exception as e:
print("Console mode requested but error initializing IPython : %s" % str(e))
print("To make use of the Interactive IPython QT Console, make sure you install : ")
print("$ pip3 install ipython qtconsole matplotlib")
qApp.exec_()
else:
qApp.exec_()
qApp.deleteLater()
def run():
@ -99,7 +231,7 @@ def run():
app, MW = prepare()
# Separating launch to avoid segfault, so it seem.
# Cf. http://stackoverflow.com/questions/12433491/is-this-pyqt-4-python-bug-or-wrongly-behaving-code
launch(MW)
launch(app, MW)
if __name__ == "__main__":
run()

View file

@ -2,12 +2,14 @@
# --!-- coding: utf8 --!--
import imp
import os
import re
from PyQt5.QtCore import (pyqtSignal, QSignalMapper, QTimer, QSettings, Qt,
from PyQt5.Qt import qVersion, PYQT_VERSION_STR
from PyQt5.QtCore import (pyqtSignal, QSignalMapper, QTimer, QSettings, Qt, QPoint,
QRegExp, QUrl, QSize, QModelIndex)
from PyQt5.QtGui import QStandardItemModel, QIcon, QColor
from PyQt5.QtWidgets import QMainWindow, QHeaderView, qApp, QMenu, QActionGroup, QAction, QStyle, QListWidgetItem, \
QLabel, QDockWidget, QWidget
QLabel, QDockWidget, QWidget, QMessageBox
from manuskript import settings
from manuskript.enums import Character, PlotStep, Plot, World, Outline
@ -34,15 +36,10 @@ from manuskript.ui.statusLabel import statusLabel
# Spellcheck support
from manuskript.ui.views.textEditView import textEditView
try:
import enchant
except ImportError:
enchant = None
from manuskript.functions import Spellchecker
class MainWindow(QMainWindow, Ui_MainWindow):
dictChanged = pyqtSignal(str)
# dictChanged = pyqtSignal(str)
# Tab indexes
TabInfos = 0
@ -62,9 +59,10 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# Var
self.currentProject = None
self.projectDirty = None # has the user made any unsaved changes ?
self._lastFocus = None
self._lastMDEditView = None
self._defaultCursorFlashTime = 1000 # Overriden at startup with system
self._defaultCursorFlashTime = 1000 # Overridden at startup with system
# value. In manuskript.main.
self._autoLoadProject = None # Used to load a command line project
@ -172,10 +170,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.actModeGroup = QActionGroup(self)
self.actModeSimple.setActionGroup(self.actModeGroup)
self.actModeFiction.setActionGroup(self.actModeGroup)
self.actModeSnowflake.setActionGroup(self.actModeGroup)
self.actModeSimple.triggered.connect(self.setViewModeSimple)
self.actModeFiction.triggered.connect(self.setViewModeFiction)
self.actModeSnowflake.setEnabled(False)
# Main Menu:: Tool
self.actToolFrequency.triggered.connect(self.frequencyAnalyzer)
@ -280,6 +276,15 @@ class MainWindow(QMainWindow, Ui_MainWindow):
break
new = new.parent()
def projectName(self):
"""
Returns a user-friendly name for the loaded project.
"""
pName = os.path.split(self.currentProject)[1]
if pName.endswith('.msk'):
pName=pName[:-4]
return pName
###############################################################################
# SUMMARY
###############################################################################
@ -417,8 +422,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
PlotStep.meta, QHeaderView.ResizeToContents)
self.lstSubPlots.verticalHeader().hide()
def updatePlotImportance(self, ID):
imp = self.mdlPlots.getPlotImportanceByID(ID)
def updatePlotImportance(self, row):
imp = self.mdlPlots.getPlotImportanceByRow(row)
self.sldPlotImportance.setValue(int(imp))
def changeCurrentSubPlot(self, index):
@ -552,9 +557,9 @@ class MainWindow(QMainWindow, Ui_MainWindow):
If ``loadFromFile`` is False, then it does not load datas from file.
It assumes that the datas have been populated in a different way."""
if loadFromFile and not os.path.exists(project):
print(self.tr("The file {} does not exist. Try again.").format(project))
print(self.tr("The file {} does not exist. Has it been moved or deleted?").format(project))
F.statusMessage(
self.tr("The file {} does not exist. Try again.", importance=3).format(project))
self.tr("The file {} does not exist. Has it been moved or deleted?").format(project), importance=3)
return
if loadFromFile:
@ -625,38 +630,76 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# We force to emit even if it opens on the current tab
self.tabMain.currentChanged.emit(settings.lastTab)
# Make sure we can update the window title later.
self.currentProject = project
self.projectDirty = False
QSettings().setValue("lastProject", project)
# Add project name to Window's name
pName = os.path.split(project)[1]
if pName.endswith('.msk'):
pName=pName[:-4]
self.setWindowTitle(pName + " - " + self.tr("Manuskript"))
self.setWindowTitle(self.projectName() + " - " + self.tr("Manuskript"))
# Stuff
# self.checkPersosID() # Shouldn't be necessary any longer
self.currentProject = project
QSettings().setValue("lastProject", project)
# Show main Window
self.switchToProject()
def handleUnsavedChanges(self):
"""
There may be some currently unsaved changes, but the action the user triggered
will result in the project or application being closed. To save, or not to save?
Or just bail out entirely?
Sometimes it is best to just ask.
"""
if not self.projectDirty:
return True # no unsaved changes, all is good
msg = QMessageBox(QMessageBox.Question,
self.tr("Save project?"),
"<p><b>" +
self.tr("Save changes to project \"{}\" before closing?").format(self.projectName()) +
"</b></p>" +
"<p>" +
self.tr("Your changes will be lost if you don't save them.") +
"</p>",
QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel)
ret = msg.exec()
if ret == QMessageBox.Cancel:
return False # the situation has not been handled, cancel action
if ret == QMessageBox.Save:
self.saveDatas()
return True # the situation has been handled
def closeProject(self):
if not self.currentProject:
return
# Make sure data is saved.
if (self.projectDirty and settings.saveOnQuit == True):
self.saveDatas()
elif not self.handleUnsavedChanges():
return # user cancelled action
# Close open tabs in editor
self.mainEditor.closeAllTabs()
# Save datas
self.saveDatas()
self.currentProject = None
self.projectDirty = None
QSettings().setValue("lastProject", "")
# Clear datas
self.loadEmptyDatas()
self.saveTimer.stop()
self.saveTimerNoChanges.stop()
loadSave.clearSaveCache()
self.breakConnections()
@ -718,23 +761,6 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self._toolbarState = ""
def closeEvent(self, event):
# Save State and geometry and other things
sttgns = QSettings(qApp.organizationName(), qApp.applicationName())
sttgns.setValue("geometry", self.saveGeometry())
sttgns.setValue("windowState", self.saveState())
sttgns.setValue("metadataState", self.redacMetadata.saveState())
sttgns.setValue("revisionsState", self.redacMetadata.revisions.saveState())
sttgns.setValue("splitterRedacH", self.splitterRedacH.saveState())
sttgns.setValue("splitterRedacV", self.splitterRedacV.saveState())
sttgns.setValue("toolbar", self.toolbar.saveState())
# If we are not in the welcome window, we update the visibility
# of the docks widgets
if self.stack.currentIndex() == 1:
self.updateDockVisibility()
# Storing the visibility of docks to restore it on restart
sttgns.setValue("docks", self._dckVisibility)
# Specific settings to save before quitting
settings.lastTab = self.tabMain.currentIndex()
@ -742,14 +768,42 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# Remembering the current items (stores outlineItem's ID)
settings.openIndexes = self.mainEditor.tabSplitter.openIndexes()
# Save data from models
if self.currentProject and settings.saveOnQuit:
self.saveDatas()
# Save data from models
if settings.saveOnQuit:
self.saveDatas()
elif not self.handleUnsavedChanges():
event.ignore() # user opted to cancel the close action
# closeEvent
# QMainWindow.closeEvent(self, event) # Causing segfaults?
# User may have canceled close event, so make sure we indeed want to close.
# This is necessary because self.updateDockVisibility() hides UI elements.
if event.isAccepted():
# Save State and geometry and other things
appSettings = QSettings(qApp.organizationName(), qApp.applicationName())
appSettings.setValue("geometry", self.saveGeometry())
appSettings.setValue("windowState", self.saveState())
appSettings.setValue("metadataState", self.redacMetadata.saveState())
appSettings.setValue("revisionsState", self.redacMetadata.revisions.saveState())
appSettings.setValue("splitterRedacH", self.splitterRedacH.saveState())
appSettings.setValue("splitterRedacV", self.splitterRedacV.saveState())
appSettings.setValue("toolbar", self.toolbar.saveState())
# If we are not in the welcome window, we update the visibility
# of the docks widgets
if self.stack.currentIndex() == 1:
self.updateDockVisibility()
# Storing the visibility of docks to restore it on restart
appSettings.setValue("docks", self._dckVisibility)
def startTimerNoChanges(self):
"""
Something changed in the project that requires auto-saving.
"""
self.projectDirty = True
if settings.autoSaveNoChanges:
self.saveTimerNoChanges.start()
@ -764,11 +818,24 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.currentProject = projectName
QSettings().setValue("lastProject", projectName)
r = loadSave.saveProject() # version=0
# Stop the timer before saving: if auto-saving fails (bugs out?) we don't want it
# to keep trying and continuously hitting the failure condition. Nor do we want to
# risk a scenario where the timer somehow triggers a new save while saving.
self.saveTimerNoChanges.stop()
if self.currentProject is None:
# No UI feedback here as this code path indicates a race condition that happens
# after the user has already closed the project through some way. But in that
# scenario, this code should not be reachable to begin with.
print("Bug: there is no current project to save.")
return
r = loadSave.saveProject() # version=0
projectName = os.path.basename(self.currentProject)
if r:
self.projectDirty = False # successful save, clear dirty flag
feedback = self.tr("Project {} saved.").format(projectName)
F.statusMessage(feedback, importance=0)
else:
@ -1075,14 +1142,17 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# HELP
###############################################################################
def centerChildWindow(self, win):
r = win.geometry()
r2 = self.geometry()
win.move(r2.center() - QPoint(r.width()/2, r.height()/2))
def about(self):
self.dialog = aboutDialog(mw=self)
self.dialog.setFixedSize(self.dialog.size())
self.dialog.show()
# Center about dialog
r = self.dialog.geometry()
r2 = self.geometry()
self.dialog.move(r2.center() - r.center())
self.centerChildWindow(self.dialog)
###############################################################################
# GENERAL AKA UNSORTED
@ -1248,24 +1318,29 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.actShowHelp.setChecked(False)
# Spellcheck
if enchant:
if Spellchecker.isInstalled():
self.menuDict = QMenu(self.tr("Dictionary"))
self.menuDictGroup = QActionGroup(self)
self.updateMenuDict()
self.menuTools.addMenu(self.menuDict)
self.actSpellcheck.toggled.connect(self.toggleSpellcheck, F.AUC)
self.dictChanged.connect(self.mainEditor.setDict, F.AUC)
self.dictChanged.connect(self.redacMetadata.setDict, F.AUC)
self.dictChanged.connect(self.outlineItemEditor.setDict, F.AUC)
# self.dictChanged.connect(self.mainEditor.setDict, F.AUC)
# self.dictChanged.connect(self.redacMetadata.setDict, F.AUC)
# self.dictChanged.connect(self.outlineItemEditor.setDict, F.AUC)
else:
# No Spell check support
self.actSpellcheck.setVisible(False)
a = QAction(self.tr("Install PyEnchant to use spellcheck"), self)
a.setIcon(self.style().standardIcon(QStyle.SP_MessageBoxWarning))
a.triggered.connect(self.openPyEnchantWebPage, F.AUC)
self.menuTools.addAction(a)
for lib, requirement in Spellchecker.supportedLibraries().items():
a = QAction(self.tr("Install {}{} to use spellcheck").format(lib, requirement or ""), self)
a.setIcon(self.style().standardIcon(QStyle.SP_MessageBoxWarning))
# Need to bound the lib argument otherwise the lambda uses the same lib value across all calls
def gen_slot_cb(l):
return lambda: self.openSpellcheckWebPage(l)
a.triggered.connect(gen_slot_cb(lib), F.AUC)
self.menuTools.addAction(a)
###############################################################################
# SPELLCHECK
@ -1273,37 +1348,75 @@ class MainWindow(QMainWindow, Ui_MainWindow):
def updateMenuDict(self):
if not enchant:
if not Spellchecker.isInstalled():
return
self.menuDict.clear()
for i in enchant.list_dicts():
a = QAction(str(i[0]), self)
a.setCheckable(True)
if settings.dict is None:
settings.dict = enchant.get_default_language()
if str(i[0]) == settings.dict:
a.setChecked(True)
a.triggered.connect(self.setDictionary, F.AUC)
self.menuDictGroup.addAction(a)
dictionaries = Spellchecker.availableDictionaries()
# Set first run dictionary
if settings.dict is None:
settings.dict = Spellchecker.getDefaultDictionary()
# Check if project dict is unavailable on this machine
dict_available = False
for lib, dicts in dictionaries.items():
if dict_available:
break
for i in dicts:
if Spellchecker.normalizeDictName(lib, i) == settings.dict:
dict_available = True
break
# Reset dict to default one if it's unavailable
if not dict_available:
settings.dict = Spellchecker.getDefaultDictionary()
for lib, dicts in dictionaries.items():
if len(dicts) > 0:
a = QAction(lib, self)
else:
a = QAction(self.tr("{} has no installed dictionaries").format(lib), self)
a.setEnabled(False)
self.menuDict.addAction(a)
for i in dicts:
a = QAction(i, self)
a.data = lib
a.setCheckable(True)
if Spellchecker.normalizeDictName(lib, i) == settings.dict:
a.setChecked(True)
a.triggered.connect(self.setDictionary, F.AUC)
self.menuDictGroup.addAction(a)
self.menuDict.addAction(a)
self.menuDict.addSeparator()
# If a new dictionary was chosen, apply the change and re-enable spellcheck if it was enabled.
if not dict_available:
self.setDictionary()
self.toggleSpellcheck(settings.spellcheck)
for lib, requirement in Spellchecker.supportedLibraries().items():
if lib not in dictionaries:
a = QAction(self.tr("{}{} is not installed").format(lib, requirement or ""), self)
a.setEnabled(False)
self.menuDict.addAction(a)
self.menuDict.addSeparator()
def setDictionary(self):
if not enchant:
if not Spellchecker.isInstalled():
return
for i in self.menuDictGroup.actions():
if i.isChecked():
# self.dictChanged.emit(i.text().replace("&", ""))
settings.dict = i.text().replace("&", "")
settings.dict = Spellchecker.normalizeDictName(i.data, i.text().replace("&", ""))
# Find all textEditView from self, and toggle spellcheck
for w in self.findChildren(textEditView, QRegExp(".*"),
Qt.FindChildrenRecursively):
w.setDict(settings.dict)
def openPyEnchantWebPage(self):
F.openURL("http://pythonhosted.org/pyenchant/")
def openSpellcheckWebPage(self, lib):
F.openURL(Spellchecker.getLibraryURL(lib))
def toggleSpellcheck(self, val):
settings.spellcheck = val
@ -1328,9 +1441,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.sw.hide()
self.sw.setWindowModality(Qt.ApplicationModal)
self.sw.setWindowFlags(Qt.Dialog)
r = self.sw.geometry()
r2 = self.geometry()
self.sw.move(r2.center() - r.center())
self.centerChildWindow(self.sw)
if tab:
self.sw.setTab(tab)
self.sw.show()
@ -1342,6 +1453,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
def frequencyAnalyzer(self):
self.fw = frequencyAnalyzer(self)
self.fw.show()
self.centerChildWindow(self.fw)
###############################################################################
# VIEW MENU
@ -1470,17 +1582,41 @@ class MainWindow(QMainWindow, Ui_MainWindow):
###############################################################################
def doImport(self):
# Warn about buggy Qt versions and import crash
#
# (Py)Qt 5.11 and 5.12 have a bug that can cause crashes when simply
# setting up various UI elements.
# This has been reported and verified to happen with File -> Import.
# See PR #611.
if re.match("^5\\.1[12](\\.?|$)", qVersion()):
warning1 = self.tr("PyQt / Qt versions 5.11 and 5.12 are known to cause a crash which might result in a loss of data.")
warning2 = self.tr("PyQt {} and Qt {} are in use.").format(qVersion(), PYQT_VERSION_STR)
# Don't translate for debug log.
print("WARNING:", warning1, warning2)
msg = QMessageBox(QMessageBox.Warning,
self.tr("Proceed with import at your own risk"),
"<p><b>" +
warning1 +
"</b></p>" +
"<p>" +
warning2 +
"</p>",
QMessageBox.Abort | QMessageBox.Ignore)
msg.setDefaultButton(QMessageBox.Abort)
# Return because user heeds warning
if msg.exec() == QMessageBox.Abort:
return
# Proceed with Import
self.dialog = importerDialog(mw=self)
self.dialog.show()
self.centerChildWindow(self.dialog)
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()
r = self.dialog.geometry()
r2 = self.geometry()
self.dialog.move(r2.center() - r.center())
self.centerChildWindow(self.dialog)

View file

@ -9,18 +9,22 @@ from PyQt5.QtCore import Qt
from PyQt5.QtGui import QIcon, QFont
from PyQt5.QtWidgets import QTextEdit, qApp
from lxml import etree as ET
import re
from manuskript import enums
class abstractItem():
# Enum kept on the class for easier acces
# Enum kept on the class for easier access
enum = enums.Abstract
# Used for XML export
name = "abstractItem"
# Regexp from https://stackoverflow.com/questions/8733233/filtering-out-certain-bytes-in-python
valid_xml_re = re.compile(u'[^\u0020-\uD7FF\u0009\u000A\u000D\uE000-\uFFFD\U00010000-\U0010FFFF]+')
def __init__(self, model=None, title="", _type="abstract", xml=None, parent=None, ID=None):
self._data = {}
@ -101,7 +105,7 @@ class abstractItem():
return self._data[self.enum.type]
#######################################################################
# Parent / Children managment
# Parent / Children management
#######################################################################
def child(self, row):
@ -122,6 +126,7 @@ class abstractItem():
def row(self):
if self.parent():
return self.parent().childItems.index(self)
return None
def appendChild(self, child):
self.insertChild(self.childCount(), child)
@ -140,6 +145,9 @@ class abstractItem():
@return: the removed abstractItem
"""
r = self.childItems.pop(row)
# Disassociate the child from its parent and the model.
r._parent = None
r.setModel(None)
return r
def parent(self):
@ -177,6 +185,11 @@ class abstractItem():
item.setData(self.enum.ID, None)
return item
def siblings(self):
if self.parent():
return self.parent().children()
return []
###############################################################################
# IDS
###############################################################################
@ -249,6 +262,9 @@ class abstractItem():
# We want to force some data even if they're empty
XMLForce = []
def cleanTextForXML(self, text):
return self.valid_xml_re.sub('', text)
def toXML(self):
"""
Returns a string containing the item (and children) in XML.
@ -263,7 +279,7 @@ class abstractItem():
continue
val = self.data(attrib)
if val or attrib in self.XMLForce:
item.set(attrib.name, str(val))
item.set(attrib.name, self.cleanTextForXML(str(val)))
# Saving lastPath
item.set("lastPath", self._lastPath)

View file

@ -92,9 +92,15 @@ class abstractModel(QAbstractItemModel):
"""
return self.rootItem.findItemsContaining(text, columns, mainWindow(), caseSensitive)
def getItemByID(self, ID):
def getItemByID(self, ID, ignore=None):
"""Returns the item whose ID is `ID`, unless this item matches `ignore`."""
def search(item):
if item.ID() == ID:
if item == ignore:
# The item we really want won't be found in the children of this
# particular item anymore; stop searching this branch entirely.
return None
return item
for c in item.children():
r = search(c)
@ -104,9 +110,12 @@ class abstractModel(QAbstractItemModel):
item = search(self.rootItem)
return item
def getIndexByID(self, ID, column=0):
"Returns the index of item whose ID is `ID`. If none, returns QModelIndex()."
item = self.getItemByID(ID)
def getIndexByID(self, ID, column=0, ignore=None):
"""Returns the index of item whose ID is `ID`. If none, returns QModelIndex().
If `ignore` is set, it will not return that item if found as valid match for the ID"""
item = self.getItemByID(ID, ignore=ignore)
if not item:
return QModelIndex()
else:
@ -119,7 +128,10 @@ class abstractModel(QAbstractItemModel):
childItem = index.internalPointer()
parentItem = childItem.parent()
if parentItem == self.rootItem:
# Check whether the parent is the root, or is otherwise invalid.
# That is to say: no parent or the parent lacks a parent.
if (parentItem == self.rootItem) or \
(parentItem is None) or (parentItem.parent() is None):
return QModelIndex()
return self.createIndex(parentItem.row(), 0, parentItem)

View file

@ -83,6 +83,15 @@ class outlineItem(abstractItem):
def charCount(self):
return self._data.get(self.enum.charCount, 0)
def __str__(self):
return "{id}: {folder}{title}{children}".format(
id=self.ID(),
folder="*" if self.isFolder() else "",
title=self.data(self.enum.title),
children="" if self.isText() else "({})".format(self.childCount())
)
__repr__ = __str__
#######################################################################
# Data
@ -173,13 +182,11 @@ class outlineItem(abstractItem):
def removeChild(self, row):
r = abstractItem.removeChild(self, row)
# Might be causing segfault when updateWordCount emits dataChanged
self.updateWordCount(emit=False)
self.updateWordCount()
return r
def updateWordCount(self, emit=True):
"""Update word count for item and parents.
If emit is False, no signal is emitted (sometimes cause segfault)"""
def updateWordCount(self):
"""Update word count for item and parents."""
if not self.isFolder():
setGoal = F.toInt(self.data(self.enum.setGoal))
goal = F.toInt(self.data(self.enum.goal))
@ -217,12 +224,11 @@ class outlineItem(abstractItem):
else:
self.setData(self.enum.goalPercentage, "")
if emit:
self.emitDataChanged([self.enum.goal, self.enum.setGoal,
self.enum.wordCount, self.enum.goalPercentage])
self.emitDataChanged([self.enum.goal, self.enum.setGoal,
self.enum.wordCount, self.enum.goalPercentage])
if self.parent():
self.parent().updateWordCount(emit)
self.parent().updateWordCount()
def stats(self):
wc = self.data(enums.Outline.wordCount)
@ -232,12 +238,12 @@ class outlineItem(abstractItem):
wc = 0
if goal:
return qApp.translate("outlineItem", "{} words / {} ({})").format(
locale.format("%d", wc, grouping=True),
locale.format("%d", goal, grouping=True),
locale.format_string("%d", wc, grouping=True),
locale.format_string("%d", goal, grouping=True),
"{}%".format(str(int(progress * 100))))
else:
return qApp.translate("outlineItem", "{} words").format(
locale.format("%d", wc, grouping=True))
locale.format_string("%d", wc, grouping=True))
#######################################################################
# Tools: split and merge
@ -482,7 +488,7 @@ class outlineItem(abstractItem):
for r in rev:
revItem = ET.Element("revision")
revItem.set("timestamp", str(r[0]))
revItem.set("text", r[1])
revItem.set("text", self.cleanTextForXML(r[1]))
item.append(revItem)
return item

View file

@ -68,10 +68,9 @@ class plotModel(QStandardItemModel):
return name
return None
def getPlotImportanceByID(self, ID):
def getPlotImportanceByRow(self, row):
for i in range(self.rowCount()):
_ID = self.item(i, Plot.ID).text()
if _ID == ID or toInt(_ID) == ID:
if i == row:
importance = self.item(i, Plot.importance).text()
return importance
return "0" # Default to "Minor"

View file

@ -34,6 +34,12 @@ viewSettings = {
},
}
fullscreenSettings = {
"autohide-top": True,
"autohide-bottom": True,
"autohide-left": True,
}
# Application
spellcheck = False
dict = None
@ -79,7 +85,7 @@ textEditor = {
}
revisions = {
"keep": True,
"keep": False,
"smartremove": True,
"rules": collections.OrderedDict({
10 * 60: 60, # One per minute for the last 10mn
@ -119,10 +125,11 @@ 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, dontShowDeleteWarning
saveToZip, dontShowDeleteWarning, fullscreenSettings
allSettings = {
"viewSettings": viewSettings,
"fullscreenSettings": fullscreenSettings,
"dict": dict,
"spellcheck": spellcheck,
"corkSizeFactor": corkSizeFactor,
@ -131,6 +138,7 @@ def save(filename=None, protocol=None):
"openIndexes": openIndexes,
"autoSave":autoSave,
"autoSaveDelay":autoSaveDelay,
# TODO: Settings Cleanup Task -- Rename saveOnQuit to saveOnProjectClose -- see PR #615
"saveOnQuit":saveOnQuit,
"autoSaveNoChanges":autoSaveNoChanges,
"autoSaveNoChangesDelay":autoSaveNoChangesDelay,
@ -199,6 +207,10 @@ def load(string, fromString=False, protocol=None):
if not name in viewSettings[cat]:
viewSettings[cat][name] = default
if "fullscreenSettings" in allSettings:
global fullscreenSettings
fullscreenSettings = allSettings["fullscreenSettings"]
if "dict" in allSettings:
global dict
dict = allSettings["dict"]

View file

@ -1,6 +1,7 @@
#!/usr/bin/env python
# --!-- coding: utf8 --!--
import os
import shutil
from collections import OrderedDict
from PyQt5.QtCore import QSize, QSettings, QRegExp, QTranslator, QObject
@ -8,7 +9,7 @@ from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtGui import QIntValidator, QIcon, QFont, QColor, QPixmap, QStandardItem, QPainter
from PyQt5.QtGui import QStyleHints
from PyQt5.QtWidgets import QStyleFactory, QWidget, QStyle, QColorDialog, QListWidgetItem, QMessageBox
from PyQt5.QtWidgets import qApp
from PyQt5.QtWidgets import qApp, QFileDialog
# Spell checker support
from manuskript import settings
@ -25,11 +26,6 @@ from manuskript.ui.views.textEditView import textEditView
from manuskript.ui.welcome import welcome
from manuskript.ui import style as S
try:
import enchant
except ImportError:
enchant = None
class settingsWindow(QWidget, Ui_Settings):
def __init__(self, mainWindow):
@ -73,10 +69,29 @@ class settingsWindow(QWidget, Ui_Settings):
self.cmbTranslation.clear()
tr = OrderedDict()
tr["English"] = ""
tr["Français"] = "manuskript_fr.qm"
tr["Español"] = "manuskript_es.qm"
tr["Deutsch"] = "manuskript_de.qm"
tr["Arabic (Saudi Arabia)"] = "manuskript_ar_SA.qm"
tr["German"] = "manuskript_de.qm"
tr["English (Great Britain)"] = "manuskript_en_GB.qm"
tr["Spanish"] = "manuskript_es.qm"
tr["Persian"] = "manuskript_fa.qm"
tr["French"] = "manuskript_fr.qm"
tr["Hungarian"] = "manuskript_hu.qm"
tr["Indonesian"] = "manuskript_id.qm"
tr["Italian"] = "manuskript_it.qm"
tr["Japanese"] = "manuskript_ja.qm"
tr["Korean"] = "manuskript_ko.qm"
tr["Norwegian Bokmål"] = "manuskript_nb_NO.qm"
tr["Dutch"] = "manuskript_nl.qm"
tr["Polish"] = "manuskript_pl.qm"
tr["Portuguese (Brazil)"] = "manuskript_pt_BR.qm"
tr["Portuguese (Portugal)"] = "manuskript_pt_PT.qm"
tr["Romanian"] = "manuskript_ro.qm"
tr["Russian"] = "manuskript_ru.qm"
tr["Svenska"] = "manuskript_sv.qm"
tr["Turkish"] = "manuskript_tr.qm"
tr["Ukrainian"] = "manuskript_uk.qm"
tr["Chinese (Simplified)"] = "manuskript_zh_CN.qm"
tr["Chinese (Traditional)"] = "manuskript_zh_HANT.qm"
self.translations = tr
for name in tr:
@ -448,12 +463,24 @@ class settingsWindow(QWidget, Ui_Settings):
self.btnCorkColor.setStyleSheet("background:{};".format(settings.corkBackground["color"]))
def setCorkBackground(self, i):
# Check if combobox was reset
if i == -1:
return
img = self.cmbCorkImage.itemData(i)
img = os.path.basename(img)
if img:
settings.corkBackground["image"] = img
else:
settings.corkBackground["image"] = ""
txt = self.cmbCorkImage.itemText(i)
if txt == "":
settings.corkBackground["image"] = ""
else:
img = self.addBackgroundImage()
if img:
self.populatesCmbBackgrounds(self.cmbCorkImage)
settings.corkBackground["image"] = img
self.setCorkImageDefault()
# Update Cork view
self.mw.mainEditor.updateCorkBackground()
@ -472,8 +499,34 @@ class settingsWindow(QWidget, Ui_Settings):
px = QPixmap(os.path.join(p, l)).scaled(128, 64, Qt.KeepAspectRatio)
cmb.addItem(QIcon(px), "", os.path.join(p, l))
cmb.addItem(QIcon.fromTheme("list-add"), " ", "")
cmb.setIconSize(QSize(128, 64))
def addBackgroundImage(self):
lastDirectory = self.mw.welcome.getLastAccessedDirectory()
"""File dialog that request an existing file. For opening an image."""
filename = QFileDialog.getOpenFileName(self,
self.tr("Open Image"),
lastDirectory,
self.tr("Image files (*.jpg; *.jpeg; *.png)"))[0]
if filename:
try:
px = QPixmap()
valid = px.load(filename)
del px
if valid:
shutil.copy(filename, writablePath("resources/backgrounds"))
return os.path.basename(filename)
else:
QMessageBox.warning(self, self.tr("Error"),
self.tr("Unable to load selected file"))
except Exception as e:
QMessageBox.warning(self, self.tr("Error"),
self.tr("Unable to add selected image:\n{}").format(str(e)))
return None
def setCorkImageDefault(self):
if settings.corkBackground["image"] != "":
i = self.cmbCorkImage.findData(findBackground(settings.corkBackground["image"]))
@ -858,12 +911,26 @@ class settingsWindow(QWidget, Ui_Settings):
self.timerUpdateFSPreview.start()
def updateThemeBackground(self, i):
img = self.cmbCorkImage.itemData(i)
# Check if combobox was reset
if i == -1:
return
img = self.cmbThemeBackgroundImage.itemData(i)
if img:
self._themeData["Background/ImageFile"] = os.path.split(img)[1]
else:
self._themeData["Background/ImageFile"] = ""
txt = self.cmbThemeBackgroundImage.itemText(i)
if txt == "":
self._themeData["Background/ImageFile"] = ""
else:
img = self.addBackgroundImage()
if img:
self.populatesCmbBackgrounds(self.cmbThemeBackgroundImage)
self._themeData["Background/ImageFile"] = img
i = self.cmbThemeBackgroundImage.findData(self._themeData["Background/ImageFile"], flags=Qt.MatchContains)
if i != -1:
self.cmbThemeBackgroundImage.setCurrentIndex(i)
self.updatePreview()
def getThemeColor(self, key):

View file

@ -20,8 +20,8 @@ app, MW = main.prepare(tests=True)
# self.btnAddSubPlot.clicked.connect(self.updateSubPlotView, F.AUC)
# Yet the disconnectAll() function has been called.
# Workaround: we remove the necessity for connection to be unique. This
# works for now, but could create issues later one when we want to tests
# those specific functionnality. Maybe it will be called several times.
# works for now, but could create issues later on when we want to test
# this specific functionality. Maybe it will be called several times?
# At that moment, we will need to catch the exception in the MainWindow,
# or better: understand why it happens at all, and only on some signals.
from manuskript import functions as F

View file

@ -66,7 +66,6 @@ def MWSampleProject(MW):
import shutil
shutil.copyfile(src, tf.name)
shutil.copytree(src[:-4], tf.name[:-4])
MW.closeProject()
MW.loadProject(tf.name)
assert MW.currentProject is not None

View file

@ -59,9 +59,6 @@ def test_outlineItemsProperties(outlineItemFolder, outlineItemText):
text.setData(text.enum.goal, 4)
assert text.data(text.enum.goalPercentage) == .5
# revisions
assert text.data(text.enum.revisions) == []
def test_modelStuff(outlineModelBasic):
"""
Tests with children items.
@ -126,16 +123,6 @@ def test_modelStuff(outlineModelBasic):
assert folder.findItemsContaining("VALUE", cols, MW, True) == []
assert folder.findItemsContaining("VALUE", cols, MW, False) == [text2.ID()]
# Revisions
text2.clearAllRevisions()
assert text2.revisions() == []
text2.setData(text2.enum.text, "Some value.")
assert len(text2.revisions()) == 1
text2.setData(text2.enum.text, "Some new value.")
assert len(text2.revisions()) == 1 # Auto clean
text2.deleteRevision(text2.revisions()[0][0])
assert len(text2.revisions()) == 0
# Model, count and copy
k = folder._model
folder.setModel(14)

View file

@ -30,7 +30,7 @@ class aboutDialog(QWidget, Ui_about):
+ "&nbsp;"*5 + """<a href="http://www.theologeek.ch/manuskript/">
http://www.theologeek.ch/manuskript/
</a><br>"""
+ "&nbsp;"*5 + "Copyright © 2015-2017 Olivier Keshavjee<br>"
+ "&nbsp;"*5 + "Copyright © 2015-2020 Olivier Keshavjee<br>"
+ "&nbsp;"*5 + """<a href="https://www.gnu.org/licenses/gpl-3.0.en.html">
GNU General Public License Version 3
</a><br>"""

View file

@ -71,6 +71,8 @@ class cheatSheet(QWidget, Ui_cheatSheet):
def textChanged(self, text):
if not text:
self.hideList()
self.list.clear()
self.view.setText("")
else:
self.list.show()
@ -146,10 +148,11 @@ class cheatSheet(QWidget, Ui_cheatSheet):
def showInfos(self):
self.hideList()
i = self.list.currentItem()
ref = i.data(Qt.UserRole)
if ref:
self.view.setText(Ref.infos(ref))
if self.list and len(self.txtFilter.text()) != 0:
i = self.list.currentItem()
ref = i.data(Qt.UserRole)
if ref:
self.view.setText(Ref.infos(ref))
def openLink(self, link):
Ref.open(link)

View file

@ -54,7 +54,7 @@ class editorWidget(QWidget, Ui_editorWidget_ui):
self.dictChanged.connect(self.txtRedacText.setDict, AUC)
self.txtRedacText.setHighlighting(True)
self.currentDict = ""
self.spellcheck = True
self.spellcheck = settings.spellcheck
self.folderView = "cork"
self.mw = mainWindow()
self._tabWidget = None # set by mainEditor on creation
@ -73,7 +73,7 @@ class editorWidget(QWidget, Ui_editorWidget_ui):
def resizeEvent(self, event):
"""
textEdit's scrollBar has been reparented to self. So we need to
update it's geomtry when self is resized, and put it where we want it
update it's geometry when self is resized, and put it where we want it
to be.
"""
# Update scrollbar geometry
@ -293,16 +293,14 @@ class editorWidget(QWidget, Ui_editorWidget_ui):
try:
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)
self._model.rowsAboutToBeRemoved.connect(self.rowsAboutToBeRemoved, AUC)
except TypeError:
pass
self.updateStatusBar()
def setCurrentModelIndex(self, index=None):
if index.isValid():
if index and index.isValid():
self.currentIndex = index
self._model = index.model()
self.currentID = self._model.ID(index)
@ -313,17 +311,26 @@ class editorWidget(QWidget, Ui_editorWidget_ui):
if self._model:
self.setView()
def updateIndexFromID(self):
def updateIndexFromID(self, fallback=None, ignore=None):
"""
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._model.getIndexByID(self.currentID)
# If we have an ID but the ID does not exist, it has been deleted
It will ignore the passed model item to avoid ambiguity during times
of inconsistent state.
"""
idx = self._model.getIndexByID(self.currentID, ignore=ignore)
# If we have an ID but the ID does not exist, it has been deleted.
if self.currentID and idx == QModelIndex():
# Item has been deleted, we open the parent instead
self.setCurrentModelIndex(self.currentIndex.parent())
# If we are given a fallback item to display, do so.
if fallback:
self.setCurrentModelIndex(fallback)
else:
# After tab closing is implemented, any calls to `updateIndexFromID`
# should be re-evaluated to match the desired behaviour.
raise NotImplementedError("implement tab closing")
# FIXME: selection in self.mw.treeRedacOutline is not updated
# but we cannot simply setCurrentIndex through treeRedacOutline
# because this might be a tab in the background / out of focus
@ -337,19 +344,39 @@ class editorWidget(QWidget, Ui_editorWidget_ui):
self.setView()
def modelDataChanged(self, topLeft, bottomRight):
# if self.currentID:
# self.updateIndexFromID()
if not self.currentIndex:
return
if not self.currentIndex.isValid():
return # Just to be safe.
# We are only concerned with minor changes to the current index,
# so there is no need to call updateIndexFromID() nor setView().
if topLeft.row() <= self.currentIndex.row() <= bottomRight.row():
self.updateTabTitle()
self.updateStatusBar()
#def rowsAboutToBeRemoved(self, parent, first, last):
#if self.currentIndex:
#if self.currentIndex.parent() == parent and \
#first <= self.currentIndex.row() <= last:
## Item deleted, close tab
#self.mw.mainEditor.tab.removeTab(self.mw.mainEditor.tab.indexOf(self))
def rowsAboutToBeRemoved(self, parent, first, last):
if not self.currentIndex.isValid():
return # Just to be safe.
# Look for a common ancestor to verify whether the deleted rows include our index in their hierarchy.
childItem = self.currentIndex
ancestorCandidate = childItem.parent() # start at folder above current item
while (ancestorCandidate != parent):
childItem = ancestorCandidate
ancestorCandidate = childItem.parent()
if not ancestorCandidate.isValid():
return # we ran out of ancestors without finding the matching QModelIndex
# My sanity advocates a healthy dose of paranoia. (Just to be safe.)
if ancestorCandidate != parent:
return # we did not find our shared ancestor
# Verify our origins come from the relevant first..last range.
if first <= childItem.row() <= last:
# If the row in question was actually moved, there is a duplicate item
# already inserted elsewhere in the tree. Try to update this tab view,
# but make sure we exclude ourselves from the search for a replacement.
self.updateIndexFromID(fallback=parent, ignore=self.currentIndex.internalPointer())
def updateStatusBar(self):
# Update progress
@ -359,7 +386,7 @@ class editorWidget(QWidget, Ui_editorWidget_ui):
if not mw:
return
mw.mainEditor.updateStats()
mw.mainEditor.tabChanged()
def toggleSpellcheck(self, v):
self.spellcheck = v

View file

@ -2,8 +2,8 @@
# --!-- coding: utf8 --!--
import os
from PyQt5.QtCore import Qt, QSize, QPoint, QRect, QEvent, QTimer
from PyQt5.QtGui import QFontMetrics, QColor, QBrush, QPalette, QPainter, QPixmap
from PyQt5.QtCore import Qt, QSize, QPoint, QRect, QEvent, QTime, QTimer
from PyQt5.QtGui import QFontMetrics, QColor, QBrush, QPalette, QPainter, QPixmap, QCursor
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QFrame, QWidget, QPushButton, qApp, QStyle, QComboBox, QLabel, QScrollBar, \
QStyleOptionSlider, QHBoxLayout, QVBoxLayout, QMenu, QAction
@ -11,21 +11,19 @@ from PyQt5.QtWidgets import QFrame, QWidget, QPushButton, qApp, QStyle, QComboBo
# Spell checker support
from manuskript import settings
from manuskript.enums import Outline
from manuskript.models import outlineItem
from manuskript.functions import allPaths, drawProgress
from manuskript.ui.editors.locker import locker
from manuskript.ui.editors.themes import findThemePath, generateTheme, setThemeEditorDatas
from manuskript.ui.editors.themes import loadThemeDatas
from manuskript.ui.views.MDEditView import MDEditView
try:
import enchant
except ImportError:
enchant = None
from manuskript.functions import Spellchecker
class fullScreenEditor(QWidget):
def __init__(self, index, parent=None):
QWidget.__init__(self, parent)
self.setAttribute(Qt.WA_DeleteOnClose, True)
self._background = None
self._index = index
self._theme = findThemePath(settings.fullScreenTheme)
@ -53,23 +51,53 @@ class fullScreenEditor(QWidget):
# self.topPanel.layout().addStretch(1)
# Spell checking
if enchant:
if Spellchecker.isInstalled():
self.btnSpellCheck = QPushButton(self)
self.btnSpellCheck.setFlat(True)
self.btnSpellCheck.setIcon(QIcon.fromTheme("tools-check-spelling"))
self.btnSpellCheck.setCheckable(True)
self.btnSpellCheck.setChecked(self.editor.spellcheck)
self.btnSpellCheck.toggled.connect(self.editor.toggleSpellcheck)
self.topPanel.layout().addWidget(self.btnSpellCheck)
else:
self.btnSpellCheck = None
self.topPanel.layout().addStretch(1)
# Navigation Buttons
self.btnPrevious = QPushButton(self)
self.btnPrevious.setFlat(True)
self.btnPrevious.setIcon(QIcon.fromTheme("arrow-left"))
self.btnPrevious.clicked.connect(self.switchPreviousItem)
self.btnNext = QPushButton(self)
self.btnNext.setFlat(True)
self.btnNext.setIcon(QIcon.fromTheme("arrow-right"))
self.btnNext.clicked.connect(self.switchNextItem)
self.btnNew = QPushButton(self)
self.btnNew.setFlat(True)
self.btnNew.setIcon(QIcon.fromTheme("document-new"))
self.btnNew.clicked.connect(self.createNewText)
# Path and New Text Buttons
self.wPath = myPath(self)
# Close
self.btnClose = QPushButton(self)
self.btnClose.setIcon(qApp.style().standardIcon(QStyle.SP_DialogCloseButton))
self.btnClose.clicked.connect(self.close)
self.btnClose.clicked.connect(self.leaveFullscreen)
self.btnClose.setFlat(True)
# Top panel Layout
if self.btnSpellCheck:
self.topPanel.layout().addWidget(self.btnSpellCheck)
self.topPanel.layout().addSpacing(15)
self.topPanel.layout().addWidget(self.btnPrevious)
self.topPanel.layout().addWidget(self.btnNext)
self.topPanel.layout().addWidget(self.btnNew)
self.topPanel.layout().addStretch(1)
self.topPanel.layout().addWidget(self.wPath)
self.topPanel.layout().addStretch(1)
self.topPanel.layout().addWidget(self.btnClose)
self.updateTopBar()
# Left Panel
self._locked = False
@ -98,7 +126,8 @@ class fullScreenEditor(QWidget):
# self.lstThemes.setCurrentText(settings.fullScreenTheme)
self.lstThemes.currentTextChanged.connect(self.setTheme)
self.lstThemes.setMaximumSize(QSize(300, QFontMetrics(qApp.font()).height()))
self.bottomPanel.layout().addWidget(QLabel(self.tr("Theme:"), self))
themeLabel = QLabel(self.tr("Theme:"), self)
self.bottomPanel.layout().addWidget(themeLabel)
self.bottomPanel.layout().addWidget(self.lstThemes)
self.bottomPanel.layout().addStretch(1)
@ -106,12 +135,33 @@ class fullScreenEditor(QWidget):
self.lblProgress.setMaximumSize(QSize(200, 14))
self.lblProgress.setMinimumSize(QSize(100, 14))
self.lblWC = QLabel(self)
self.lblClock = myClockLabel(self)
self.bottomPanel.layout().addWidget(self.lblWC)
self.bottomPanel.layout().addWidget(self.lblProgress)
self.bottomPanel.layout().addSpacing(15)
self.bottomPanel.layout().addWidget(self.lblClock)
self.updateStatusBar()
self.bottomPanel.layout().addSpacing(24)
# Add Widget Settings
if self.btnSpellCheck:
self.topPanel.addWidgetSetting(self.tr("Spellcheck"), 'top-spellcheck', (self.btnSpellCheck, ))
self.topPanel.addWidgetSetting(self.tr("Navigation"), 'top-navigation', (self.btnPrevious, self.btnNext))
self.topPanel.addWidgetSetting(self.tr("New Text"), 'top-new-doc', (self.btnNew, ))
self.topPanel.addWidgetSetting(self.tr("Title"), 'top-title', (self.wPath, ))
self.topPanel.addSetting(self.tr("Title: Show Full Path"), 'title-show-full-path', True)
self.topPanel.setSettingCallback('title-show-full-path', lambda var, val: self.updateTopBar())
self.bottomPanel.addWidgetSetting(self.tr("Theme selector"), 'bottom-theme', (self.lstThemes, themeLabel))
self.bottomPanel.addWidgetSetting(self.tr("Word count"), 'bottom-wc', (self.lblWC, ))
self.bottomPanel.addWidgetSetting(self.tr("Progress"), 'bottom-progress', (self.lblProgress, ))
self.bottomPanel.addSetting(self.tr("Progress: Auto Show/Hide"), 'progress-auto-show', True)
self.bottomPanel.addWidgetSetting(self.tr("Clock"), 'bottom-clock', (self.lblClock, ))
self.bottomPanel.addSetting(self.tr("Clock: Show Seconds"), 'clock-show-seconds', True)
self.bottomPanel.setAutoHideVariable('autohide-bottom')
self.topPanel.setAutoHideVariable('autohide-top')
self.leftPanel.setAutoHideVariable('autohide-left')
# Connection
self._index.model().dataChanged.connect(self.dataChanged)
@ -120,6 +170,15 @@ class fullScreenEditor(QWidget):
# self.showMaximized()
# self.show()
def __del__(self):
# print("Leaving fullScreenEditor via Destructor event", flush=True)
self.showNormal()
self.close()
def leaveFullscreen(self):
self.showNormal()
self.close()
def setLocked(self, val):
self._locked = val
self.btnClose.setVisible(not val)
@ -142,8 +201,8 @@ class fullScreenEditor(QWidget):
# Colors
if self._themeDatas["Foreground/Color"] == self._themeDatas["Background/Color"] or \
self._themeDatas["Foreground/Opacity"] < 5:
self._bgcolor = QColor(self._themeDatas["Text/Color"])
self._fgcolor = QColor(self._themeDatas["Background/Color"])
self._fgcolor = QColor(self._themeDatas["Text/Color"])
self._bgcolor = QColor(self._themeDatas["Background/Color"])
else:
self._bgcolor = QColor(self._themeDatas["Foreground/Color"])
self._bgcolor.setAlpha(self._themeDatas["Foreground/Opacity"] * 255 / 100)
@ -197,7 +256,7 @@ class fullScreenEditor(QWidget):
p.setBrush(QPalette.ButtonText, self._fgcolor)
p.setBrush(QPalette.WindowText, self._fgcolor)
for panel in (self.bottomPanel, self.topPanel):
for panel in (self.bottomPanel, self.topPanel, self.leftPanel):
for i in range(panel.layout().count()):
item = panel.layout().itemAt(i)
if item.widget():
@ -221,7 +280,17 @@ class fullScreenEditor(QWidget):
def keyPressEvent(self, event):
if event.key() in [Qt.Key_Escape, Qt.Key_F11] and \
not self._locked:
# print("Leaving fullScreenEditor via keyPressEvent", flush=True)
self.showNormal()
self.close()
elif (event.modifiers() & Qt.AltModifier) and \
event.key() in [Qt.Key_PageUp, Qt.Key_PageDown, Qt.Key_Left, Qt.Key_Right]:
if event.key() in [Qt.Key_PageUp, Qt.Key_Left]:
success = self.switchPreviousItem()
if event.key() in [Qt.Key_PageDown, Qt.Key_Right]:
success = self.switchNextItem()
if not success:
QWidget.keyPressEvent(self, event)
else:
QWidget.keyPressEvent(self, event)
@ -265,6 +334,14 @@ class fullScreenEditor(QWidget):
if topLeft.row() <= self._index.row() <= bottomRight.row():
self.updateStatusBar()
def updateTopBar(self):
item = self._index.internalPointer()
previousItem = self.previousTextItem(item)
nextItem = self.nextTextItem(item)
self.btnPrevious.setEnabled(previousItem is not None)
self.btnNext.setEnabled(nextItem is not None)
self.wPath.setItem(item)
def updateStatusBar(self):
if self._index:
item = self._index.internalPointer()
@ -274,18 +351,22 @@ class fullScreenEditor(QWidget):
pg = item.data(Outline.goalPercentage)
if goal:
rect = self.lblProgress.geometry()
rect = QRect(QPoint(0, 0), rect.size())
self.px = QPixmap(rect.size())
self.px.fill(Qt.transparent)
p = QPainter(self.px)
drawProgress(p, rect, pg, 2)
p.end()
self.lblProgress.setPixmap(self.px)
if settings.fullscreenSettings.get("progress-auto-show", True):
self.lblProgress.show()
self.lblWC.setText(self.tr("{} words / {}").format(wc, goal))
else:
self.lblProgress.hide()
if settings.fullscreenSettings.get("progress-auto-show", True):
self.lblProgress.hide()
self.lblWC.setText(self.tr("{} words").format(wc))
pg = 0
rect = self.lblProgress.geometry()
rect = QRect(QPoint(0, 0), rect.size())
self.px = QPixmap(rect.size())
self.px.fill(Qt.transparent)
p = QPainter(self.px)
drawProgress(p, rect, pg, 2)
p.end()
self.lblProgress.setPixmap(self.px)
self.locker.setWordCount(wc)
# If there's a goal, then we update the locker target's number of word accordingly
@ -296,6 +377,102 @@ class fullScreenEditor(QWidget):
elif not wc:
self.locker.spnWordTarget.setValue(goal)
def setCurrentModelIndex(self, index):
self._index = index
self.editor.setCurrentModelIndex(index)
self.updateTopBar()
self.updateStatusBar()
def switchPreviousItem(self):
item = self._index.internalPointer()
previousItem = self.previousTextItem(item)
if previousItem:
self.setCurrentModelIndex(previousItem.index())
return True
return False
def switchNextItem(self):
item = self._index.internalPointer()
nextItem = self.nextTextItem(item)
if nextItem:
self.setCurrentModelIndex(nextItem.index())
return True
return False
def switchToItem(self, item):
item = self.firstTextItem(item)
if item:
self.setCurrentModelIndex(item.index())
def createNewText(self):
item = self._index.internalPointer()
newItem = outlineItem(title=qApp.translate("outlineBasics", "New"), _type=settings.defaultTextType)
self._index.model().insertItem(newItem, item.row() + 1, item.parent().index())
self.setCurrentModelIndex(newItem.index())
def previousModelItem(self, item):
parent = item.parent()
if not parent:
# Root has no sibling
return None
row = parent.childItems.index(item)
if row > 0:
return parent.child(row - 1)
return self.previousModelItem(parent)
def nextModelItem(self, item):
parent = item.parent()
if not parent:
# Root has no sibling
return None
row = parent.childItems.index(item)
if row + 1 < parent.childCount():
return parent.child(row + 1)
return self.nextModelItem(parent)
def previousTextItem(self, item):
previous = self.previousModelItem(item)
while previous:
last = self.lastTextItem(previous)
if last:
return last
previous = self.previousModelItem(previous)
return None
def nextTextItem(self, item):
if item.isFolder() and item.childCount() > 0:
next = item.child(0)
else:
next = self.nextModelItem(item)
while next:
first = self.firstTextItem(next)
if first:
return first
next = self.nextModelItem(next)
return None
def firstTextItem(self, item):
if item.isText():
return item
for child in item.children():
first = self.firstTextItem(child)
if first:
return first
return None
def lastTextItem(self, item):
if item.isText():
return item
for child in reversed(item.children()):
last = self.lastTextItem(child)
if last:
return last
return None
class myScrollBar(QScrollBar):
def __init__(self, color=Qt.white, parent=None):
@ -305,11 +482,14 @@ class myScrollBar(QScrollBar):
self.timer = QTimer()
self.timer.setInterval(500)
self.timer.setSingleShot(True)
self.timer.timeout.connect(lambda: self.parent().hideWidget(self))
self.timer.timeout.connect(self.hide)
self.valueChanged.connect(lambda v: self.timer.start())
self.valueChanged.connect(lambda: self.parent().showWidget(self))
self.rangeChanged.connect(self.rangeHasChanged)
def hide(self):
self.parent().hideWidget(self)
def setColor(self, color):
self._color = color
@ -349,6 +529,10 @@ class myPanel(QWidget):
self.show()
self.setAttribute(Qt.WA_TranslucentBackground)
self._autoHide = True
self._m = None
self._autoHideVar = None
self._settings = []
self._callbacks = {}
if not vertical:
self.setLayout(QHBoxLayout())
@ -364,16 +548,144 @@ class myPanel(QWidget):
painter = QPainter(self)
painter.fillRect(r, self._color)
def _setConfig(self, config_name, value):
settings.fullscreenSettings[config_name] = value
if config_name in self._callbacks:
self._callbacks[config_name](config_name, value)
def _setSettingValue(self, setting, value):
if setting[2]:
for w in setting[2]:
w.show() if value else w.hide()
self._setConfig(setting[1], value)
def setAutoHide(self, value):
self._autoHide = value
if self._autoHideVar:
self._setConfig(self._autoHideVar, value)
def setAutoHideVariable(self, name):
if name:
self.setAutoHide(settings.fullscreenSettings[name])
self._autoHideVar = name
def addWidgetSetting(self, label, config_name, widgets):
setting = (label, config_name, widgets)
self._settings.append(setting)
if settings.fullscreenSettings.get(config_name, None) is not None:
self._setSettingValue(setting, settings.fullscreenSettings[config_name])
def addSetting(self, label, config_name, default=True):
if settings.fullscreenSettings.get(config_name, None) is None:
self._setConfig(config_name, default)
self.addWidgetSetting(label, config_name, None)
def setSettingCallback(self, config_name, callback):
self._callbacks[config_name] = callback
def mouseReleaseEvent(self, event):
if event.button() == Qt.RightButton:
if self._m:
self._m.deleteLater()
m = QMenu()
a = QAction(self.tr("Auto-hide"), m)
a.setCheckable(True)
a.setChecked(self._autoHide)
a.toggled.connect(self.setAutoHide)
m.addAction(a)
for item in self._settings:
a = QAction(item[0], m)
a.setCheckable(True)
if item[2]:
a.setChecked(item[2][0].isVisible())
else:
a.setChecked(settings.fullscreenSettings[item[1]])
def gen_cb(setting):
return lambda v: self._setSettingValue(setting, v)
a.toggled.connect(gen_cb(item))
m.addAction(a)
m.popup(self.mapToGlobal(event.pos()))
self._m = m
class myPath(QWidget):
def __init__(self, parent=None):
QWidget.__init__(self, parent)
self.editor = parent
self.setAttribute(Qt.WA_TranslucentBackground)
self.setLayout(QHBoxLayout())
self.layout().setContentsMargins(0, 0, 0, 0)
def setItem(self, item):
self._item = item
path = self.getItemPath(item)
layout = self.layout()
while layout.count() > 0:
li = layout.takeAt(0)
w = li.widget()
w.deleteLater()
def gen_cb(i):
return lambda: self.popupPath(i)
# Skip Root
for i in path[1:]:
if not settings.fullscreenSettings.get("title-show-full-path", True) and \
i.isFolder():
continue
btn = QPushButton(i.title(), self)
btn.setFlat(True)
btn.clicked.connect(gen_cb(i))
self.layout().addWidget(btn)
if i.isFolder():
lblSeparator = QLabel(" > ", self)
#lblSeparator = QLabel(self)
#lblSeparator.setPixmap(QIcon.fromTheme("view-list-tree").pixmap(24,24))
self.layout().addWidget(lblSeparator)
def popupPath(self, item):
m = QMenu()
def gen_cb(i):
return lambda: self.editor.switchToItem(i)
for i in item.siblings():
a = QAction(i.title(), m)
if i == item:
a.setIcon(QIcon.fromTheme("stock_yes"))
a.setEnabled(False)
elif self.editor.firstTextItem(i) is None:
a.setEnabled(False)
else:
a.triggered.connect(gen_cb(i))
m.addAction(a)
m.popup(QCursor.pos())
self._m = m
def getItemPath(self, item):
path = [item]
parent = item.parent()
while parent:
path.insert(0, parent)
parent = parent.parent()
return path
class myClockLabel(QLabel):
def __init__(self, parent=None):
QLabel.__init__(self, parent)
self.updateClock()
self.timer = QTimer()
self.timer.setInterval(1000)
self.timer.timeout.connect(self.updateClock)
self.timer.start()
def updateClock(self):
time = QTime.currentTime()
if settings.fullscreenSettings.get("clock-show-seconds", True):
timeStr = time.toString("hh:mm:ss")
else:
timeStr = time.toString("hh:mm")
self.setText(timeStr)

View file

@ -9,7 +9,7 @@ from PyQt5.QtWidgets import QWidget, qApp
from manuskript import settings
from manuskript.enums import Outline
from manuskript.functions import AUC, mainWindow, drawProgress, appPath
from manuskript.functions import AUC, mainWindow, drawProgress, appPath, uiParse
from manuskript.ui import style
from manuskript.ui.editors.editorWidget import editorWidget
from manuskript.ui.editors.fullScreenEditor import fullScreenEditor
@ -304,7 +304,9 @@ class mainEditor(QWidget, Ui_mainEditor):
goal = item.data(Outline.goal)
chars = item.data(Outline.charCount) # len(item.data(Outline.text))
progress = item.data(Outline.goalPercentage)
# mw = qApp.activeWindow()
goal = uiParse(goal, None, int, lambda x: x>=0)
progress = uiParse(progress, 0.0, float)
if not chars:
chars = 0

View file

@ -137,7 +137,7 @@ def createThemePreview(theme, screenRect, size=QSize(200, 120)):
def findThemePath(themeName):
p = findFirstFile(re.escape("{}.theme".format(themeName)), "resources/themes")
if not p:
return findFirstFile(".*\.theme", "resources/themes")
return findFirstFile(r".*\.theme", "resources/themes")
else:
return p
@ -249,12 +249,14 @@ def setThemeEditorDatas(editor, themeDatas, pixmap, screenRect):
# editor.setFont(f)
editor.setStyleSheet("""
background: transparent;
color: {foreground};
font-family: {ff};
font-size: {fs};
selection-color: {sc};
selection-background-color: {sbc};
QTextEdit {{
background: transparent;
color: {foreground};
font-family: {ff};
font-size: {fs};
selection-color: {sc};
selection-background-color: {sbc};
}}
""".format(
foreground=themeDatas["Text/Color"],
ff=f.family(),

View file

@ -3,7 +3,7 @@
import json
import os
from PyQt5.QtCore import Qt
from PyQt5.QtCore import Qt, QPoint
from PyQt5.QtGui import QBrush, QColor, QIcon
from PyQt5.QtWidgets import QWidget, QStyle
@ -138,7 +138,7 @@ class exporterDialog(QWidget, Ui_exporter):
r = self.dialog.geometry()
r2 = self.geometry()
self.dialog.move(r2.center() - r.center())
self.dialog.move(r2.center() - QPoint(r.width()/2, r.height()/2))
self.dialog.exportersMightHaveChanged.connect(self.populateExportList)
@ -153,4 +153,4 @@ class exporterDialog(QWidget, Ui_exporter):
item.widget().deleteLater()
l.addWidget(widget)
widget.setParent(group)
widget.setParent(group)

View file

@ -128,7 +128,7 @@ class exporterSettings(QWidget, Ui_exporterSettings):
def loadSettings(self):
filename = self.getSettingsPath()
if os.path.exists(filename):
with open(filename) as f:
with open(filename, "r", encoding="utf-8") as f:
self.settings = json.load(f)
self.updateFromSettings()
@ -138,7 +138,7 @@ class exporterSettings(QWidget, Ui_exporterSettings):
def writeSettings(self):
self.getSettings()
with open(self.getSettingsPath(), 'w') as f:
with open(self.getSettingsPath(), 'w', encoding="utf-8") as f:
# json.dumps(json.loads(json.dumps(allSettings)), indent=4, sort_keys=True)
json.dump(self.settings, f, indent=4, sort_keys=True)

View file

@ -11,19 +11,19 @@ from manuskript.ui.highlighters import BasicHighlighter
class MMDHighlighter(BasicHighlighter):
MARKDOWN_REGEX = {
'Bold': '(\*\*)(.+?)(\*\*)',
'Bold': r'(\*\*)(.+?)(\*\*)',
'Bold2': '(__)(.+?)(__)',
'Italic': '(\*)([^\*].+?[^\*])(\*)',
'Italic': r'(\*)([^\*].+?[^\*])(\*)',
'Italic2': '(_)([^_].+?[^_])(_)',
'Title': '^(#+)(\s*)(.*)(#*)',
'Title': r'^(#+)(\s*)(.*)(#*)',
'HTML': '<.+?>',
'Blockquotes': '^(> )+.*$',
'OrderedList': '^\d+\.\s+',
'UnorderedList': '^[\*\+-]\s+',
'Code': '^\s{4,}.*$',
'Links-inline': '(\[)(.*?)(\])(\()(.*?)(\))',
'Links-ref': '(\[)(.*?)(\])\s?(\[)(.*?)(\])',
'Links-ref2': '^\s{,3}(\[)(.*?)(\]:)\s+([^\s]*)\s*(.*?)*$',
'OrderedList': r'^\d+\.\s+',
'UnorderedList': r'^[\*\+-]\s+',
'Code': r'^\s{4,}.*$',
'Links-inline': r'(\[)(.*?)(\])(\()(.*?)(\))',
'Links-ref': r'(\[)(.*?)(\])\s?(\[)(.*?)(\])',
'Links-ref2': r'^\s{,3}(\[)(.*?)(\]:)\s+([^\s]*)\s*(.*?)*$',
}
def __init__(self, editor, style="Default"):

View file

@ -1,5 +1,5 @@
#!/usr/bin/python
# -*- coding: utf8 -*-
# -*- coding: utf-8 -*-
import re
@ -86,7 +86,7 @@ class BasicHighlighter(QSyntaxHighlighter):
def doHighlightBlock(self, text):
"""
Virtual funtion to subclass.
Virtual function to subclass.
"""
pass
@ -146,13 +146,16 @@ class BasicHighlighter(QSyntaxHighlighter):
textedText = text + " "
# Based on http://john.nachtimwald.com/2009/08/22/qplaintextedit-with-in-line-spell-check/
WORDS = r'(?iu)(((?!_)[\w\'])+)'
WORDS = r'(?iu)((?:[^_\W]|\')+)[^A-Za-z0-9\']'
# (?iu) means case insensitive and Unicode
# (?!_) means perform negative lookahead to exclude "_" from pattern match. See issue #283
# ((?:[^_\W]|\')+) means words exclude underscores but include apostrophes
# [^A-Za-z0-9\'] used with above hack to prevent spellcheck while typing word
#
# See also https://stackoverflow.com/questions/2062169/regex-w-in-utf-8
if hasattr(self.editor, "spellcheck") and self.editor.spellcheck:
for word_object in re.finditer(WORDS, textedText):
if (self.editor._dict
and not self.editor._dict.check(word_object.group(1))):
and self.editor._dict.isMisspelled(word_object.group(1))):
format = self.format(word_object.start(1))
format.setUnderlineColor(self._misspelledColor)
# SpellCheckUnderline fails with some fonts

View file

@ -85,7 +85,7 @@ class MarkdownHighlighter(BasicHighlighter):
def unfocusConditions(self):
"""
Returns:
- True if the text is suposed to be unfocused
- True if the text is supposed to be unfocused
- (start, end) if block is supposed to be unfocused except for that part.
"""
@ -283,7 +283,7 @@ class MarkdownHighlighter(BasicHighlighter):
theme = {
"markup": markup}
#Exemple:
#Example:
#"color": Qt.red,
#"deltaSize": 10,
#"background": Qt.yellow,
@ -477,7 +477,7 @@ class MarkdownHighlighter(BasicHighlighter):
self.transparentFormat(fmt)
self.transparentFormat(markupFormat)
# Format openning Markup
# Format opening Markup
self.setFormat(token.position, token.openingMarkupLength,
markupFormat)
@ -676,7 +676,7 @@ class MarkdownHighlighter(BasicHighlighter):
spellingErrorFormat = self.format(startIndex)
spellingErrorFormat.setUnderlineColor(self.spellingErrorColor)
spellingErrorFormat.setUnderlineStyle(
qApp.stlye().styleHint(QStyle.SH_SpellCheckUnderlineStyle))
qApp.style().styleHint(QStyle.SH_SpellCheckUnderlineStyle))
self.setFormat(startIndex, length, spellingErrorFormat)

View file

@ -91,7 +91,7 @@ class MarkdownTokenizer(HighlightTokenizer):
strongRegex.setMinimal(True)
strikethroughRegex = QRegExp("~~[^\\s]+.*[^\\s]+~~")
strikethroughRegex.setMinimal(True)
superScriptRegex = QRegExp("\^([^\\s]|(\\\\\\s))+\^") # Spaces must be escaped "\ "
superScriptRegex = QRegExp(r"\^([^\s]|(\\\\\s))+\^") # Spaces must be escaped "\ "
superScriptRegex.setMinimal(True)
subScriptRegex = QRegExp("~([^\\s]|(\\\\\\s))+~") # Spaces must be escaped "\ "
subScriptRegex.setMinimal(True)
@ -279,7 +279,9 @@ class MarkdownTokenizer(HighlightTokenizer):
if level > 0 and level < len(text):
# Count how many pound signs are at the end of the text.
while escapedText[-trailingPoundCount -1] == "#":
# Ignore starting pound signs when calculating trailing signs
while level + trailingPoundCount < len(text) and \
escapedText[-trailingPoundCount -1] == "#":
trailingPoundCount += 1
token = Token()
@ -363,7 +365,7 @@ class MarkdownTokenizer(HighlightTokenizer):
spaceCount += 1
# If this list item is the first in the list, ensure the
# number of spaces preceeding the bullet point does not
# number of spaces preceding the bullet point does not
# exceed three, as that would indicate a code block rather
# than a bullet point list.
@ -830,15 +832,15 @@ class MarkdownTokenizer(HighlightTokenizer):
markupStartCount=0, markupEndCount=0,
replaceMarkupChars=False, replaceAllChars=False):
"""
Tokenizes a block of text, searching for all occurrances of regex.
Occurrances are set to the given token type and added to the list of
Tokenizes a block of text, searching for all occurrences of regex.
Occurrences are set to the given token type and added to the list of
tokens. The markupStartCount and markupEndCount values are used to
indicate how many markup special characters preceed and follow the
indicate how many markup special characters precede and follow the
main text, respectively.
For example, if the matched string is "**bold**", and
markupStartCount = 2 and markupEndCount = 2, then the asterisks
preceeding and following the word "bold" will be set as opening and
preceding and following the word "bold" will be set as opening and
closing markup in the token.
If replaceMarkupChars is true, then the markupStartCount and
@ -887,7 +889,7 @@ class MarkdownTokenizer(HighlightTokenizer):
with the escaped characters replaced with a dummy character.
"""
return re.sub("\\\\.", "\$", text)
return re.sub("\\\\.", r"\$", text)
#escape = False
#escapedText = text

View file

@ -36,6 +36,7 @@ class importerDialog(QWidget, Ui_importer):
# Var
self.mw = mw
self.settingsWidget = None
self.fileName = ""
self.setStyleSheet(style.mainWindowSS())
self.tree.setStyleSheet("QTreeView{background:transparent;}")
@ -121,9 +122,8 @@ class importerDialog(QWidget, Ui_importer):
F = self._format
options = QFileDialog.Options()
options |= QFileDialog.DontUseNativeDialog
if F.fileFormat == "<<folder>>":
options = QFileDialog.DontUseNativeDialog | QFileDialog.ShowDirsOnly
options = QFileDialog.ShowDirsOnly
fileName = QFileDialog.getExistingDirectory(self, "Select import folder",
"", options=options)
else:
@ -195,7 +195,7 @@ class importerDialog(QWidget, Ui_importer):
self.grpPreview.setEnabled(True)
self.settingsWidget = generalSettings()
#TODO: custom format widget
#TODO: custom format widget to match exporter visuals?
self.settingsWidget = F.settingsWidget(self.settingsWidget)
# Set the settings widget in place

View file

@ -97,7 +97,7 @@ class Ui_importer(object):
_translate = QtCore.QCoreApplication.translate
importer.setWindowTitle(_translate("importer", "Import"))
self.label.setText(_translate("importer", "Format:"))
self.btnChoseFile.setText(_translate("importer", "Chose file"))
self.btnChoseFile.setText(_translate("importer", "Choose file"))
self.btnClearFileName.setToolTip(_translate("importer", "Clear file"))
self.btnPreview.setText(_translate("importer", "Preview"))
self.btnImport.setText(_translate("importer", "Import"))

View file

@ -42,7 +42,7 @@
<item>
<widget class="QPushButton" name="btnChoseFile">
<property name="text">
<string>Chose file</string>
<string>Choose file</string>
</property>
<property name="icon">
<iconset theme="document-import"/>

View file

@ -1150,9 +1150,6 @@ class Ui_MainWindow(object):
self.actModeFiction = QtWidgets.QAction(MainWindow)
self.actModeFiction.setCheckable(True)
self.actModeFiction.setObjectName("actModeFiction")
self.actModeSnowflake = QtWidgets.QAction(MainWindow)
self.actModeSnowflake.setCheckable(True)
self.actModeSnowflake.setObjectName("actModeSnowflake")
self.actViewCork = QtWidgets.QAction(MainWindow)
self.actViewCork.setObjectName("actViewCork")
self.actViewOutline = QtWidgets.QAction(MainWindow)
@ -1325,7 +1322,6 @@ class Ui_MainWindow(object):
self.menuEdit.addAction(self.actSettings)
self.menuMode.addAction(self.actModeSimple)
self.menuMode.addAction(self.actModeFiction)
self.menuMode.addAction(self.actModeSnowflake)
self.menuView.addAction(self.menuMode.menuAction())
self.menuView.addSeparator()
self.menuOrganize.addAction(self.actMoveUp)
@ -1343,20 +1339,116 @@ class Ui_MainWindow(object):
self.retranslateUi(MainWindow)
self.stack.setCurrentIndex(1)
self.tabMain.setCurrentIndex(3)
self.tabMain.setCurrentIndex(0)
self.tabSummary.setCurrentIndex(0)
self.tabPersos.setCurrentIndex(2)
self.tabPlot.setCurrentIndex(1)
self.tabPersos.setCurrentIndex(0)
self.tabPlot.setCurrentIndex(0)
self.comboBox_2.setCurrentIndex(0)
self.stkPlotSummary.setCurrentIndex(0)
self.tabWorld.setCurrentIndex(0)
self.tabWidget.setCurrentIndex(2)
self.tabWidget.setCurrentIndex(0)
self.comboBox_2.currentIndexChanged['int'].connect(self.stkPlotSummary.setCurrentIndex)
self.btnPlanShowDetails.toggled['bool'].connect(self.frame.setVisible)
self.cmbSummary.currentIndexChanged['int'].connect(self.tabSummary.setCurrentIndex)
self.tabSummary.currentChanged['int'].connect(self.cmbSummary.setCurrentIndex)
self.btnShowSubPlotSummary.toggled['bool'].connect(self.grpSubPlotSummary.setVisible)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
MainWindow.setTabOrder(self.tabMain, self.txtGeneralTitle)
MainWindow.setTabOrder(self.txtGeneralTitle, self.txtGeneralSubtitle)
MainWindow.setTabOrder(self.txtGeneralSubtitle, self.txtGeneralSerie)
MainWindow.setTabOrder(self.txtGeneralSerie, self.txtGeneralVolume)
MainWindow.setTabOrder(self.txtGeneralVolume, self.txtGeneralGenre)
MainWindow.setTabOrder(self.txtGeneralGenre, self.txtGeneralLicense)
MainWindow.setTabOrder(self.txtGeneralLicense, self.txtGeneralAuthor)
MainWindow.setTabOrder(self.txtGeneralAuthor, self.txtGeneralEmail)
MainWindow.setTabOrder(self.txtGeneralEmail, self.cmbSummary)
MainWindow.setTabOrder(self.cmbSummary, self.txtSummarySentence)
MainWindow.setTabOrder(self.txtSummarySentence, self.txtSummarySentence_2)
MainWindow.setTabOrder(self.txtSummarySentence_2, self.txtSummaryPara)
MainWindow.setTabOrder(self.txtSummaryPara, self.txtSummaryPara_2)
MainWindow.setTabOrder(self.txtSummaryPara_2, self.txtSummaryPage)
MainWindow.setTabOrder(self.txtSummaryPage, self.txtSummaryPage_2)
MainWindow.setTabOrder(self.txtSummaryPage_2, self.txtSummaryFull)
MainWindow.setTabOrder(self.txtSummaryFull, self.btnStepThree)
MainWindow.setTabOrder(self.btnStepThree, self.btnStepTwo)
MainWindow.setTabOrder(self.btnStepTwo, self.btnStepFive)
MainWindow.setTabOrder(self.btnStepFive, self.btnStepSeven)
MainWindow.setTabOrder(self.btnStepSeven, self.txtSummarySituation)
MainWindow.setTabOrder(self.txtSummarySituation, self.lstCharacters)
MainWindow.setTabOrder(self.lstCharacters, self.btnAddPerso)
MainWindow.setTabOrder(self.btnAddPerso, self.btnRmPerso)
MainWindow.setTabOrder(self.btnRmPerso, self.txtPersosFilter)
MainWindow.setTabOrder(self.txtPersosFilter, self.tabPersos)
MainWindow.setTabOrder(self.tabPersos, self.scrollAreaPersoInfos)
MainWindow.setTabOrder(self.scrollAreaPersoInfos, self.txtPersoName)
MainWindow.setTabOrder(self.txtPersoName, self.btnPersoColor)
MainWindow.setTabOrder(self.btnPersoColor, self.txtPersoMotivation)
MainWindow.setTabOrder(self.txtPersoMotivation, self.txtPersoGoal)
MainWindow.setTabOrder(self.txtPersoGoal, self.txtPersoConflict)
MainWindow.setTabOrder(self.txtPersoConflict, self.txtPersoEpiphany)
MainWindow.setTabOrder(self.txtPersoEpiphany, self.txtPersoSummarySentence)
MainWindow.setTabOrder(self.txtPersoSummarySentence, self.txtPersoSummaryPara)
MainWindow.setTabOrder(self.txtPersoSummaryPara, self.btnStepFour)
MainWindow.setTabOrder(self.btnStepFour, self.txtPersoSummaryFull)
MainWindow.setTabOrder(self.txtPersoSummaryFull, self.btnStepSix)
MainWindow.setTabOrder(self.btnStepSix, self.txtPersoNotes)
MainWindow.setTabOrder(self.txtPersoNotes, self.tblPersoInfos)
MainWindow.setTabOrder(self.tblPersoInfos, self.btnPersoAddInfo)
MainWindow.setTabOrder(self.btnPersoAddInfo, self.btnPersoRmInfo)
MainWindow.setTabOrder(self.btnPersoRmInfo, self.lineEdit)
MainWindow.setTabOrder(self.lineEdit, self.btnStepEight)
MainWindow.setTabOrder(self.btnStepEight, self.lstPlots)
MainWindow.setTabOrder(self.lstPlots, self.btnAddPlot)
MainWindow.setTabOrder(self.btnAddPlot, self.btnRmPlot)
MainWindow.setTabOrder(self.btnRmPlot, self.txtPlotFilter)
MainWindow.setTabOrder(self.txtPlotFilter, self.tabPlot)
MainWindow.setTabOrder(self.tabPlot, self.txtPlotName)
MainWindow.setTabOrder(self.txtPlotName, self.lstPlotPerso)
MainWindow.setTabOrder(self.lstPlotPerso, self.btnAddPlotPerso)
MainWindow.setTabOrder(self.btnAddPlotPerso, self.btnRmPlotPerso)
MainWindow.setTabOrder(self.btnRmPlotPerso, self.txtPlotDescription)
MainWindow.setTabOrder(self.txtPlotDescription, self.txtPlotResult)
MainWindow.setTabOrder(self.txtPlotResult, self.lstSubPlots)
MainWindow.setTabOrder(self.lstSubPlots, self.txtSubPlotSummary)
MainWindow.setTabOrder(self.txtSubPlotSummary, self.btnAddSubPlot)
MainWindow.setTabOrder(self.btnAddSubPlot, self.btnRmSubPlot)
MainWindow.setTabOrder(self.btnRmSubPlot, self.btnShowSubPlotSummary)
MainWindow.setTabOrder(self.btnShowSubPlotSummary, self.comboBox_2)
MainWindow.setTabOrder(self.comboBox_2, self.txtPlotSummaryPara)
MainWindow.setTabOrder(self.txtPlotSummaryPara, self.txtPlotSummaryPage)
MainWindow.setTabOrder(self.txtPlotSummaryPage, self.txtPlotSummaryFull)
MainWindow.setTabOrder(self.txtPlotSummaryFull, self.treeWorld)
MainWindow.setTabOrder(self.treeWorld, self.btnAddWorld)
MainWindow.setTabOrder(self.btnAddWorld, self.btnRmWorld)
MainWindow.setTabOrder(self.btnRmWorld, self.txtWorldFilter)
MainWindow.setTabOrder(self.txtWorldFilter, self.btnWorldEmptyData)
MainWindow.setTabOrder(self.btnWorldEmptyData, self.tabWorld)
MainWindow.setTabOrder(self.tabWorld, self.txtWorldName)
MainWindow.setTabOrder(self.txtWorldName, self.txtWorldDescription)
MainWindow.setTabOrder(self.txtWorldDescription, self.txtWorldPassion)
MainWindow.setTabOrder(self.txtWorldPassion, self.txtWorldConflict)
MainWindow.setTabOrder(self.txtWorldConflict, self.lstOutlinePlots)
MainWindow.setTabOrder(self.lstOutlinePlots, self.treeOutlineOutline)
MainWindow.setTabOrder(self.treeOutlineOutline, self.btnOutlineAddFolder)
MainWindow.setTabOrder(self.btnOutlineAddFolder, self.btnOutlineAddText)
MainWindow.setTabOrder(self.btnOutlineAddText, self.btnOutlineRemoveItem)
MainWindow.setTabOrder(self.btnOutlineRemoveItem, self.btnPlanShowDetails)
MainWindow.setTabOrder(self.btnPlanShowDetails, self.treeRedacOutline)
MainWindow.setTabOrder(self.treeRedacOutline, self.btnRedacAddFolder)
MainWindow.setTabOrder(self.btnRedacAddFolder, self.btnRedacAddText)
MainWindow.setTabOrder(self.btnRedacAddText, self.btnRedacRemoveItem)
MainWindow.setTabOrder(self.btnRedacRemoveItem, self.tabWidget)
MainWindow.setTabOrder(self.tabWidget, self.tblDebugFlatData)
MainWindow.setTabOrder(self.tblDebugFlatData, self.tblDebugPersos)
MainWindow.setTabOrder(self.tblDebugPersos, self.tblDebugPersosInfos)
MainWindow.setTabOrder(self.tblDebugPersosInfos, self.tblDebugPlots)
MainWindow.setTabOrder(self.tblDebugPlots, self.tblDebugPlotsPersos)
MainWindow.setTabOrder(self.tblDebugPlotsPersos, self.tblDebugSubPlots)
MainWindow.setTabOrder(self.tblDebugSubPlots, self.treeDebugWorld)
MainWindow.setTabOrder(self.treeDebugWorld, self.treeDebugOutline)
MainWindow.setTabOrder(self.treeDebugOutline, self.lstDebugLabels)
MainWindow.setTabOrder(self.lstDebugLabels, self.lstDebugStatus)
MainWindow.setTabOrder(self.lstDebugStatus, self.lstTabs)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
@ -1418,10 +1510,8 @@ class Ui_MainWindow(object):
self.label_28.setText(_translate("MainWindow", "Result"))
self.tabPlot.setTabText(self.tabPlot.indexOf(self.infos_2), _translate("MainWindow", "Basic info"))
self.grpSubPlotSummary.setTitle(_translate("MainWindow", "Summary:"))
self.btnAddSubPlot.setToolTip(_translate("MainWindow", "Add plot step (CTRL+Enter)"))
self.btnAddSubPlot.setShortcut(_translate("MainWindow", "Ctrl+Return"))
self.btnRmSubPlot.setToolTip(_translate("MainWindow", "Remove selected plot step(s) (CTRL+Backspace)"))
self.btnRmSubPlot.setShortcut(_translate("MainWindow", "Ctrl+Backspace"))
self.btnAddSubPlot.setToolTip(_translate("MainWindow", "Add plot step"))
self.btnRmSubPlot.setToolTip(_translate("MainWindow", "Remove selected plot step(s)"))
self.tabPlot.setTabText(self.tabPlot.indexOf(self.tab_15), _translate("MainWindow", "Resolution steps"))
self.grpPlotSummary.setTitle(_translate("MainWindow", "Summary"))
self.comboBox_2.setItemText(0, _translate("MainWindow", "One paragraph"))
@ -1477,7 +1567,6 @@ class Ui_MainWindow(object):
self.actViewTree.setText(_translate("MainWindow", "Tree"))
self.actModeSimple.setText(_translate("MainWindow", "&Simple"))
self.actModeFiction.setText(_translate("MainWindow", "&Fiction"))
self.actModeSnowflake.setText(_translate("MainWindow", "S&nowflake"))
self.actViewCork.setText(_translate("MainWindow", "Index cards"))
self.actViewOutline.setText(_translate("MainWindow", "Outline"))
self.actSettings.setText(_translate("MainWindow", "S&ettings"))

Some files were not shown because too many files have changed in this diff Show more