mirror of
https://github.com/olivierkes/manuskript.git
synced 2024-05-17 11:22:28 +12:00
Merge branch 'develop' into characterscount
This commit is contained in:
commit
30b15b94dc
23
.gitignore
vendored
23
.gitignore
vendored
|
@ -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
|
||||
|
|
18
.travis.yml
18
.travis.yml
|
@ -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:
|
||||
|
|
293
CHANGELOG.md
293
CHANGELOG.md
|
@ -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)*
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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
BIN
i18n/manuskript_ar_SA.qm
Normal file
Binary file not shown.
4547
i18n/manuskript_ar_SA.ts
Normal file
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
BIN
i18n/manuskript_en_GB.qm
Normal file
Binary file not shown.
4541
i18n/manuskript_en_GB.ts
Normal file
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
1
i18n/manuskript_fa.qm
Normal file
|
@ -0,0 +1 @@
|
|||
<クdハ<>箆!ソ`。スン
|
4536
i18n/manuskript_fa.ts
Normal file
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
BIN
i18n/manuskript_hu.qm
Normal file
Binary file not shown.
4578
i18n/manuskript_hu.ts
Normal file
4578
i18n/manuskript_hu.ts
Normal file
File diff suppressed because it is too large
Load diff
BIN
i18n/manuskript_id.qm
Normal file
BIN
i18n/manuskript_id.qm
Normal file
Binary file not shown.
4536
i18n/manuskript_id.ts
Normal file
4536
i18n/manuskript_id.ts
Normal file
File diff suppressed because it is too large
Load diff
BIN
i18n/manuskript_it.qm
Normal file
BIN
i18n/manuskript_it.qm
Normal file
Binary file not shown.
4581
i18n/manuskript_it.ts
Normal file
4581
i18n/manuskript_it.ts
Normal file
File diff suppressed because it is too large
Load diff
BIN
i18n/manuskript_ja.qm
Normal file
BIN
i18n/manuskript_ja.qm
Normal file
Binary file not shown.
4543
i18n/manuskript_ja.ts
Normal file
4543
i18n/manuskript_ja.ts
Normal file
File diff suppressed because it is too large
Load diff
BIN
i18n/manuskript_ko.qm
Normal file
BIN
i18n/manuskript_ko.qm
Normal file
Binary file not shown.
4562
i18n/manuskript_ko.ts
Normal file
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
BIN
i18n/manuskript_nb_NO.qm
Normal file
Binary file not shown.
4556
i18n/manuskript_nb_NO.ts
Normal file
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
BIN
i18n/manuskript_nl.qm
Normal file
Binary file not shown.
4547
i18n/manuskript_nl.ts
Normal file
4547
i18n/manuskript_nl.ts
Normal file
File diff suppressed because it is too large
Load diff
BIN
i18n/manuskript_pl.qm
Normal file
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
BIN
i18n/manuskript_pt_BR.qm
Normal file
Binary file not shown.
4578
i18n/manuskript_pt_BR.ts
Normal file
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
BIN
i18n/manuskript_pt_PT.qm
Normal file
Binary file not shown.
4586
i18n/manuskript_pt_PT.ts
Normal file
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
BIN
i18n/manuskript_ro.qm
Normal file
Binary file not shown.
4536
i18n/manuskript_ro.ts
Normal file
4536
i18n/manuskript_ro.ts
Normal file
File diff suppressed because it is too large
Load diff
BIN
i18n/manuskript_ru.qm
Normal file
BIN
i18n/manuskript_ru.qm
Normal file
Binary file not shown.
4581
i18n/manuskript_ru.ts
Normal file
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
BIN
i18n/manuskript_tr.qm
Normal file
Binary file not shown.
4536
i18n/manuskript_tr.ts
Normal file
4536
i18n/manuskript_tr.ts
Normal file
File diff suppressed because it is too large
Load diff
BIN
i18n/manuskript_uk.qm
Normal file
BIN
i18n/manuskript_uk.qm
Normal file
Binary file not shown.
4558
i18n/manuskript_uk.ts
Normal file
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
BIN
i18n/manuskript_zh_CN.qm
Normal file
Binary file not shown.
4542
i18n/manuskript_zh_CN.ts
Normal file
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
BIN
i18n/manuskript_zh_HANT.qm
Normal file
Binary file not shown.
4536
i18n/manuskript_zh_HANT.ts
Normal file
4536
i18n/manuskript_zh_HANT.ts
Normal file
File diff suppressed because it is too large
Load diff
BIN
icons/Manuskript/manuskript.ico
Normal file
BIN
icons/Manuskript/manuskript.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 99 KiB |
17
makefile
17
makefile
|
@ -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 "$<"
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -16,6 +16,7 @@ class markdown(plainText):
|
|||
|
||||
exportVarName = "lastManuskriptMarkdown"
|
||||
exportFilter = "Markdown files (*.md);; Any files (*)"
|
||||
exportDefaultSuffix = ".md"
|
||||
icon = "text-x-markdown"
|
||||
|
||||
def settingsWidget(self):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -23,6 +23,7 @@ class PDF(abstractOutput):
|
|||
exportVarName = "lastPandocPDF"
|
||||
toFormat = "pdf"
|
||||
exportFilter = "PDF files (*.pdf);; Any files (*)"
|
||||
exportDefaultSuffix = ".pdf"
|
||||
requires = {
|
||||
"Settings": True,
|
||||
"Preview": True,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -10,6 +10,7 @@ class abstractOutput(abstractPlainText):
|
|||
toFormat = "SUBCLASSME"
|
||||
icon = "SUBCLASSME"
|
||||
exportFilter = "SUBCLASSME"
|
||||
exportDefaultSuffix = ".SUBCLASSME"
|
||||
requires = {
|
||||
"Settings": True,
|
||||
"Preview": False,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
429
manuskript/functions/spellchecker.py
Normal file
429
manuskript/functions/spellchecker.py
Normal 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)
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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.")
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -30,7 +30,7 @@ class aboutDialog(QWidget, Ui_about):
|
|||
+ " "*5 + """<a href="http://www.theologeek.ch/manuskript/">
|
||||
http://www.theologeek.ch/manuskript/
|
||||
</a><br>"""
|
||||
+ " "*5 + "Copyright © 2015-2017 Olivier Keshavjee<br>"
|
||||
+ " "*5 + "Copyright © 2015-2020 Olivier Keshavjee<br>"
|
||||
+ " "*5 + """<a href="https://www.gnu.org/licenses/gpl-3.0.en.html">
|
||||
GNU General Public License Version 3
|
||||
</a><br>"""
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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"):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"))
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue