forked from Mirrors/apostrophe
even with master
commit
bc24251461
|
@ -5,15 +5,13 @@ build/
|
|||
debian/uberwriter/DEBIAN
|
||||
debian/uberwriter/opt
|
||||
debian/uberwriter/usr
|
||||
bin/
|
||||
flatpak/*
|
||||
!flatpak/fonts-download
|
||||
!flatpak/pandoc-download
|
||||
!flatpak/pip-download
|
||||
!flatpak/uberwriter.json
|
||||
!flatpak/de.wolfvollprecht.UberWriter.*
|
||||
!flatpak/flatpak_texlive.json
|
||||
!flatpak/texlive_install.sh
|
||||
!flatpak/python3-enchant.json
|
||||
!flatpak/python3-packages.json
|
||||
*.py~
|
||||
data/ui/shortcut_handlers
|
||||
*.ui~
|
||||
|
@ -21,3 +19,5 @@ data/ui/shortcut_handlers
|
|||
*.glade~
|
||||
dist/uberwriter-2.0b0-py3.7.egg
|
||||
builddir/*
|
||||
dist/
|
||||
uberwriter.egg-info
|
||||
|
|
11
Makefile
11
Makefile
|
@ -1,6 +1,9 @@
|
|||
all:
|
||||
python3 ./setup.py build
|
||||
.PHONY: flatpak-user-install flatpak-generate-python-modules
|
||||
|
||||
install:
|
||||
python3 ./setup.py install --prefix=/app --skip-build --optimize=1
|
||||
flatpak-user-install:
|
||||
cd flatpak; flatpak-builder --force-clean --install --user _build uberwriter.json
|
||||
|
||||
flatpak-generate-python-modules:
|
||||
# gtkspellcheck's setup.py wants enchant to already be installed
|
||||
flatpak-pip-generator --output flatpak/python3-enchant.json pyenchant
|
||||
flatpak-pip-generator --output flatpak/python3-packages.json `grep -v enchant requirements.txt`
|
||||
|
|
2
PKGBUILD
2
PKGBUILD
|
@ -6,7 +6,7 @@ pkgdesc='A distraction free Markdown editor for GNU/Linux made with GTK+'
|
|||
arch=('any')
|
||||
url='http://uberwriter.github.io/uberwriter/'
|
||||
license=('GPL3')
|
||||
depends=('gtk3' 'pandoc' 'python-gtkspellcheck')
|
||||
depends=('gtk3' 'pandoc' 'gspell')
|
||||
makedepends=('python-setuptools')
|
||||
optdepends=('texlive-core' 'otf-fira-mono: Recommended font')
|
||||
provides=("$_pkgname")
|
||||
|
|
|
@ -34,8 +34,7 @@ but you'll need to install and compile the schemas before:
|
|||
|
||||
It's also possible to build, run and debug a flatpak package. You'll need flatpak-builder for this:
|
||||
|
||||
- cd to the flatpak dir of the repo
|
||||
- `flatpak-builder --install --force-clean some_folder_name uberwriter.json` (this installs and cleans the build folder)
|
||||
- `make flatpak-user-install` (this installs the Flatpak)
|
||||
- `flatpak run de.wolfvollprecht.UberWriter`
|
||||
|
||||
If you can't find Uberwriter after this, it's due to a Flatpak bug. Try to export it to a local repo before installing it:
|
||||
|
@ -54,4 +53,4 @@ If you want to update an existing installation, just run
|
|||
|
||||
You can also debug it with the following: `flatpak-builder --run --share=network some_folder_name uberwriter.json sh`
|
||||
|
||||
If you want to install it using setuptools, simply run `python3 setup.py build install`
|
||||
If you want to install it using setuptools, simply run `python3 setup.py build install`
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
#!/usr/bin/python3
|
||||
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
|
||||
### BEGIN LICENSE
|
||||
# Copyright (C) 2012, Wolf Vollprecht <w.vollprecht@gmail.com>
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License version 3, as published
|
||||
# by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranties of
|
||||
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
|
||||
# PURPOSE. See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
### END LICENSE
|
||||
|
||||
### DO NOT EDIT THIS FILE ###
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
import pkg_resources
|
||||
|
||||
import gettext
|
||||
import locale
|
||||
|
||||
# Add project root directory (enable symlink and trunk execution)
|
||||
PROJECT_ROOT_DIRECTORY = os.path.abspath(
|
||||
os.path.dirname(os.path.dirname(os.path.realpath(sys.argv[0]))))
|
||||
|
||||
# Set the path if needed. This allows uberwriter to run without installing it :)
|
||||
python_path = []
|
||||
if os.path.abspath(__file__).startswith('/opt'):
|
||||
gettext.bindtextdomain('uberwriter', '/opt/extras.ubuntu.com/uberwriter/share/locale')
|
||||
syspath = sys.path[:] # copy to avoid infinite loop in pending objects
|
||||
for path in syspath:
|
||||
opt_path = path.replace('/usr', '/opt/extras.ubuntu.com/uberwriter')
|
||||
python_path.insert(0, opt_path)
|
||||
sys.path.insert(0, opt_path)
|
||||
os.putenv("XDG_DATA_DIRS", "%s:%s" % ("/opt/extras.ubuntu.com/uberwriter/share/", os.getenv("XDG_DATA_DIRS", "/usr/local/share/:/usr/share/")))
|
||||
if (os.path.exists(os.path.join(PROJECT_ROOT_DIRECTORY, 'uberwriter'))
|
||||
and PROJECT_ROOT_DIRECTORY not in sys.path):
|
||||
python_path.insert(0, PROJECT_ROOT_DIRECTORY)
|
||||
sys.path.insert(0, PROJECT_ROOT_DIRECTORY)
|
||||
if python_path:
|
||||
os.putenv('PYTHONPATH', "%s:%s" % (os.getenv('PYTHONPATH', ''), ':'.join(python_path))) # for subprocesses
|
||||
|
||||
import uberwriter
|
||||
|
||||
locale_dir = os.path.abspath(os.path.join(os.path.dirname(uberwriter.__file__),'../po/'))
|
||||
|
||||
# L10n
|
||||
locale.textdomain('uberwriter')
|
||||
locale.bindtextdomain('uberwriter', locale_dir)
|
||||
gettext.textdomain('uberwriter')
|
||||
gettext.bindtextdomain('uberwriter', locale_dir)
|
||||
|
||||
uberwriter.main()
|
|
@ -28,6 +28,13 @@
|
|||
</screenshot>
|
||||
</screenshots>
|
||||
<releases>
|
||||
<release date="2019-04-17" version="2.2.0-beta1">
|
||||
<description>
|
||||
<ul>
|
||||
<li>...</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release date="2019-03-10" version="2.1.5">
|
||||
<description>
|
||||
<ul>
|
||||
|
|
|
@ -2,37 +2,57 @@
|
|||
|
||||
<schemalist>
|
||||
|
||||
<enum id='de.wolfvollprecht.UberWriter.Stat'>
|
||||
<value nick='characters' value='0' />
|
||||
<value nick='words' value='1' />
|
||||
<value nick='sentences' value='2' />
|
||||
<value nick='paragraphs' value='3' />
|
||||
<value nick='read_time' value='4' />
|
||||
</enum>
|
||||
|
||||
<schema path="/de/wolfvollprecht/UberWriter/" id="de.wolfvollprecht.UberWriter">
|
||||
|
||||
<key name='dark-mode-auto' type='b'>
|
||||
<default>true</default>
|
||||
<summary>Set dark mode automatically</summary>
|
||||
<description>
|
||||
Whether dark mode depends on the system theme, or is set to what the user specifies.
|
||||
</description>
|
||||
</key>
|
||||
<key name='dark-mode' type='b'>
|
||||
<default>false</default>
|
||||
<summary>Dark mode</summary>
|
||||
<summary>Force dark mode</summary>
|
||||
<description>
|
||||
If enabled, the window will be dark themed
|
||||
If disabled, the window will be light themed
|
||||
asked to install them manually.
|
||||
Enable or disable the dark mode.
|
||||
</description>
|
||||
</key>
|
||||
<key name='spellcheck' type='b'>
|
||||
<default>true</default>
|
||||
<summary>Spellcheck</summary>
|
||||
<summary>Check spelling while typing</summary>
|
||||
<description>
|
||||
Enable/disable spellchecking in the application.
|
||||
Enable or disable spellchecking.
|
||||
</description>
|
||||
</key>
|
||||
<key name='gradient-overlay' type='b'>
|
||||
<default>false</default>
|
||||
<summary>Show gradient overlay</summary>
|
||||
<summary>Draw scroll gradient</summary>
|
||||
<description>
|
||||
Show a gradient overlay over the text at the top anf bottom of the window.
|
||||
It can cause performance problems to some users
|
||||
It can cause performance problems to some users.
|
||||
</description>
|
||||
</key>
|
||||
<key name='input-format' type='s'>
|
||||
<default>"markdown"</default>
|
||||
<summary>Input format</summary>
|
||||
<description>
|
||||
Input format to use when previewing and exporting using Pandoc.
|
||||
</description>
|
||||
</key>
|
||||
<key name='poll-motion' type='b'>
|
||||
<default>true</default>
|
||||
<summary>Allow Uberwriter to poll cursor motion</summary>
|
||||
<description>
|
||||
Used for hidding the headerbar after 3 seconds if the cursor is not moving.
|
||||
Hide the header and status bars if the cursor is not moving.
|
||||
</description>
|
||||
</key>
|
||||
<key name='open-file-path' type='s'>
|
||||
|
@ -42,6 +62,13 @@
|
|||
Open file paths of the current session
|
||||
</description>
|
||||
</key>
|
||||
<key name='stat-default' enum='de.wolfvollprecht.UberWriter.Stat'>
|
||||
<default>"words"</default>
|
||||
<summary>Default statistic</summary>
|
||||
<description>
|
||||
Which statistic is shown on the main window.
|
||||
</description>
|
||||
</key>
|
||||
|
||||
</schema>
|
||||
|
||||
|
|
|
@ -1,213 +0,0 @@
|
|||
/*
|
||||
TODO:
|
||||
Look into compiling resources with glib-compile-resource etc. for
|
||||
inclusion in templates
|
||||
*/
|
||||
|
||||
@binding-set editor-bindings {
|
||||
bind "<ctl>i" { "insert-italic" () };
|
||||
bind "<ctl>b" { "insert-bold" () };
|
||||
bind "<ctl>r" { "insert-hrule" () };
|
||||
bind "<ctl>u" { "insert-ulistitem" () };
|
||||
bind "<ctl>h" { "insert-heading" () };
|
||||
bind "<ctl>z" { "undo" () };
|
||||
bind "<ctl>y" { "redo" () };
|
||||
bind "<ctl><shift>d" { "insert-strikeout" () };
|
||||
/*bind "<ctl>t" { "insert-at-cursor" ('[ ] ') };*/
|
||||
bind "<ctl><shift>z" { "redo" () };
|
||||
}
|
||||
|
||||
/* Main window and text colors */
|
||||
|
||||
.uberwriter_window {
|
||||
/*border-radius: 7px 7px 3px 3px;*/
|
||||
background: @background_color;
|
||||
caret-color: @foreground_color;
|
||||
}
|
||||
|
||||
.uberwriter_window.small .uberwriter-editor {
|
||||
font-family: 'Fira Mono', monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
.uberwriter_window grid {
|
||||
background-color: @background_color;
|
||||
}
|
||||
|
||||
#UberwriterWindow.medium .uberwriter-editor {
|
||||
font-family: 'Fira Mono', monospace;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
#UberwriterWindow.large .uberwriter-editor {
|
||||
font-family: 'Fira Mono', monospace;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
#titlebar_revealer {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.scrollbars-junction,
|
||||
.scrollbar.trough {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
#titlebar_container {
|
||||
background: @background_color;
|
||||
}
|
||||
|
||||
.uberwriter-editor {
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
text-decoration-color: #ff0000;
|
||||
/*-GtkWidget-cursor-color: shade(#4D9FCE, 0.9);*/
|
||||
/*-GtkWidget-cursor-aspect-ratio: 0.1;*/
|
||||
-gtk-key-bindings: editor-bindings;
|
||||
}
|
||||
|
||||
.uberwriter-editor text {
|
||||
background-color: @background_color;
|
||||
color: @foreground_color;
|
||||
caret-color: @foreground_color;
|
||||
}
|
||||
|
||||
.uberwriter-editor:selected {
|
||||
background-color: #4D9FCE;
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
.uberwriter-editor button {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
/*background: #CCC;*/
|
||||
}
|
||||
|
||||
.uberwriter-editor toolbar {
|
||||
/*background: transparent;*/
|
||||
border: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
|
||||
.status_bar_box label {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.status_bar_box button {
|
||||
/* finding reset */
|
||||
background-color: @background_color;
|
||||
text-shadow: inherit;
|
||||
/*icon-shadow: inherit;*/
|
||||
box-shadow: initial;
|
||||
background-clip: initial;
|
||||
background-origin: initial;
|
||||
background-size: initial;
|
||||
background-position: initial;
|
||||
background-repeat: initial;
|
||||
background-image: initial;
|
||||
border-image-source: initial;
|
||||
border-image-repeat: initial;
|
||||
border-image-slice: initial;
|
||||
border-image-width: initial;
|
||||
|
||||
border-style: none;
|
||||
-button-images: true;
|
||||
border-radius: 2px;
|
||||
color: #666;
|
||||
padding: 3px 5px;
|
||||
transition: 100ms ease-in;
|
||||
}
|
||||
|
||||
.status_bar_box button:hover,
|
||||
.status_bar_box button:checked {
|
||||
transition: 0s ease-in;
|
||||
color: @background_color;
|
||||
background-color: #666;
|
||||
}
|
||||
|
||||
.status_bar_box button:hover label,
|
||||
.status_bar_box button:checked label {
|
||||
color: @background_color;
|
||||
}
|
||||
|
||||
.status_bar_box button:active {
|
||||
color: #EEE;
|
||||
background-color: #EEE;
|
||||
background-image: none;
|
||||
box-shadow: 0 0 2px rgba(0,0,0,0.4)
|
||||
}
|
||||
|
||||
.status_bar_box separator {
|
||||
border-color: #999;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
#PreviewMenuItem image {
|
||||
border-radius: 2px;
|
||||
color: #666;
|
||||
padding: 3px 5px;
|
||||
border: none;
|
||||
background: #FFF;
|
||||
}
|
||||
|
||||
#UberwriterWindow treeview {
|
||||
padding: 3px 3px 3px 3px;
|
||||
}
|
||||
|
||||
#LexikonBubble {
|
||||
/*font: serif 10;*/
|
||||
font-family: serif;
|
||||
font-size: 10px;
|
||||
background: @background_color;
|
||||
border-radius: 4px;
|
||||
border-color: @background_color;
|
||||
margin: 5px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
/* .QuickPreviewPopup {
|
||||
padding: 5px;
|
||||
margin: 5px;
|
||||
border: 1px solid #333;
|
||||
background: @ligth_bg;
|
||||
border-radius: 3px;
|
||||
border-color: @background_color;
|
||||
} */
|
||||
|
||||
#LexikonBubble label {
|
||||
/*padding: 5px;*/
|
||||
}
|
||||
|
||||
#LexikonBubble {
|
||||
background-color: @background_color;
|
||||
border: 5px solid @background_color;
|
||||
}
|
||||
|
||||
#LexikonBubble .lexikon_heading {
|
||||
/*font: serif 12;*/
|
||||
font-family: serif;
|
||||
font-size: 12px;
|
||||
padding-bottom: 5px;
|
||||
padding-top: 5px;
|
||||
font-weight: bold;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
#LexikonBubble .lexikon_num {
|
||||
padding-right: 5px;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.QuickPreviewPopup {
|
||||
background-color: @background_color;
|
||||
}
|
||||
|
||||
.QuickPreviewPopup grid {
|
||||
background-color: @background_color;
|
||||
color: @foreground_color;
|
||||
border-color: @background_color;
|
||||
}
|
||||
|
||||
.QuickPreviewPopup label {
|
||||
color: @foreground_color;
|
||||
}
|
|
@ -0,0 +1,180 @@
|
|||
/*
|
||||
TODO:
|
||||
Look into compiling resources with glib-compile-resource etc. for
|
||||
inclusion in templates
|
||||
*/
|
||||
|
||||
@binding-set editor-bindings {
|
||||
bind "<ctl>i" { "insert-italic" () };
|
||||
bind "<ctl>b" { "insert-bold" () };
|
||||
bind "<ctl>r" { "insert-hrule" () };
|
||||
bind "<ctl>u" { "insert-listitem" () };
|
||||
bind "<ctl>h" { "insert-header" () };
|
||||
bind "<ctl>z" { "undo" () };
|
||||
bind "<ctl>y" { "redo" () };
|
||||
bind "<ctl><shift>d" { "insert-strikethrough" () };
|
||||
/*bind "<ctl>t" { "insert-at-cursor" ('[ ] ') };*/
|
||||
bind "<ctl><shift>z" { "redo" () };
|
||||
}
|
||||
|
||||
@define-color math_text_color mix(@theme_fg_color, #00b5ff, 0.15);
|
||||
|
||||
/* Main window and text colors */
|
||||
|
||||
.uberwriter-window {
|
||||
/*border-radius: 7px 7px 3px 3px;*/
|
||||
background: @theme_base_color;
|
||||
color: @theme_fg_color;
|
||||
caret-color: @theme_fg_color;
|
||||
}
|
||||
|
||||
.uberwriter-window .uberwriter-editor {
|
||||
font-family: 'Fira Mono', monospace;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.uberwriter-window.small .uberwriter-editor {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.uberwriter-window.large .uberwriter-editor {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
#titlebar-revealer {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.scrollbars-junction,
|
||||
.scrollbar.trough {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
#titlebar-container {
|
||||
background: @theme_base_color;
|
||||
}
|
||||
|
||||
.uberwriter-editor {
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
text-decoration-color: @error_color;
|
||||
-gtk-key-bindings: editor-bindings;
|
||||
}
|
||||
|
||||
.uberwriter-editor text {
|
||||
background-color: @theme_base_color;
|
||||
color: @theme_fg_color;
|
||||
caret-color: @theme_fg_color;
|
||||
}
|
||||
|
||||
.uberwriter-editor text selection {
|
||||
background-color: @theme_selected_bg_color;
|
||||
color: @theme_selected_fg_color;
|
||||
}
|
||||
|
||||
.uberwriter-editor button {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
/*background: #CCC;*/
|
||||
}
|
||||
|
||||
.uberwriter-editor toolbar {
|
||||
/*background: transparent;*/
|
||||
border: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.stats-counter {
|
||||
color: alpha(@theme_fg_color, 0.6);
|
||||
background-color: @theme_base_color;
|
||||
text-shadow: inherit;
|
||||
box-shadow: initial;
|
||||
background-clip: initial;
|
||||
background-origin: initial;
|
||||
background-size: initial;
|
||||
background-position: initial;
|
||||
background-repeat: initial;
|
||||
background-image: initial;
|
||||
border-image-source: initial;
|
||||
border-image-repeat: initial;
|
||||
border-image-slice: initial;
|
||||
border-image-width: initial;
|
||||
border-style: none;
|
||||
padding: 0px 16px;
|
||||
transition: 100ms ease-in;
|
||||
}
|
||||
|
||||
.stats-counter:hover,
|
||||
.stats-counter:checked {
|
||||
color: @theme_fg_color;
|
||||
background-color: mix(@theme_base_color, @theme_bg_color, 0.5);
|
||||
}
|
||||
|
||||
#PreviewMenuItem image {
|
||||
border-radius: 2px;
|
||||
color: #666;
|
||||
padding: 3px 5px;
|
||||
border: none;
|
||||
background: #FFF;
|
||||
}
|
||||
|
||||
.uberwriter-window treeview {
|
||||
padding: 3px 3px 3px 3px;
|
||||
}
|
||||
|
||||
#LexikonBubble {
|
||||
/*font: serif 10;*/
|
||||
font-family: serif;
|
||||
font-size: 10px;
|
||||
background: @theme_bg_color;
|
||||
border-radius: 4px;
|
||||
border-color: @theme_bg_color;
|
||||
margin: 5px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
/* .quick-preview-popup {
|
||||
padding: 5px;
|
||||
margin: 5px;
|
||||
border: 1px solid #333;
|
||||
background: @ligth_bg;
|
||||
border-radius: 3px;
|
||||
border-color: @theme_bg_color;
|
||||
} */
|
||||
|
||||
#LexikonBubble label {
|
||||
/*padding: 5px;*/
|
||||
}
|
||||
|
||||
#LexikonBubble {
|
||||
background-color: @theme_bg_color;
|
||||
border: 5px solid @theme_bg_color;
|
||||
}
|
||||
|
||||
#LexikonBubble .lexikon-heading {
|
||||
font-family: serif;
|
||||
font-size: 12px;
|
||||
padding-bottom: 5px;
|
||||
padding-top: 5px;
|
||||
font-weight: bold;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
#LexikonBubble .lexikon-num {
|
||||
padding-right: 5px;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.quick-preview-popup {
|
||||
background-color: @theme_bg_color;
|
||||
}
|
||||
|
||||
.quick-preview-popup grid {
|
||||
background-color: @theme_bg_color;
|
||||
color: @theme_fg_color;
|
||||
border-color: @theme_bg_color;
|
||||
}
|
||||
|
||||
.quick-preview-popup label {
|
||||
color: @theme_fg_color;
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
@define-color foreground_color #2e3436;
|
||||
@define-color background_color #f6f5f4;
|
||||
@define-color math_text_color #00364c;
|
||||
|
||||
@import url("_gtk_base.css");
|
|
@ -1,5 +0,0 @@
|
|||
@define-color foreground_color #eeeeec;
|
||||
@define-color background_color #353535;
|
||||
@define-color math_text_color #ffc9b3;
|
||||
|
||||
@import url("_gtk_base.css");
|
|
@ -1,5 +0,0 @@
|
|||
@define-color foreground_color #3b3e45;
|
||||
@define-color background_color #f5f6f7;
|
||||
@define-color math_text_color #00364c;
|
||||
|
||||
@import url("_gtk_base.css");
|
|
@ -1,5 +0,0 @@
|
|||
@define-color foreground_color #d3dae3;
|
||||
@define-color background_color #383c4a;
|
||||
@define-color math_text_color #ffc9b3;
|
||||
|
||||
@import url("_gtk_base.css");
|
|
@ -1,5 +0,0 @@
|
|||
@define-color foreground_color #3b3e45;
|
||||
@define-color background_color #f5f6f7;
|
||||
@define-color math_text_color #00364C;
|
||||
|
||||
@import url("_gtk_base.css");
|
|
@ -1,5 +0,0 @@
|
|||
@define-color foreground_color #000000;
|
||||
@define-color background_color #ffffff;
|
||||
@define-color math_text_color #000000;
|
||||
|
||||
@import url("_gtk_base.css");
|
|
@ -1,5 +0,0 @@
|
|||
@define-color foreground_color #ffffff;
|
||||
@define-color background_color #000000;
|
||||
@define-color math_text_color #ffffff;
|
||||
|
||||
@import url("_gtk_base.css");
|
|
@ -1,4 +1,4 @@
|
|||
@import url("_web_base.css");
|
||||
@import url("base.css");
|
||||
|
||||
:root {
|
||||
--text-color: #2e3436;
|
|
@ -1,4 +1,4 @@
|
|||
@import url("_web_base.css");
|
||||
@import url("web/web__base.css");
|
||||
|
||||
:root {
|
||||
--text-color: #eeeeec;
|
|
@ -1,4 +1,4 @@
|
|||
@import url("_web_base.css");
|
||||
@import url("web/web__base.css");
|
||||
|
||||
:root {
|
||||
--text-color: #3b3e45;
|
|
@ -1,4 +1,4 @@
|
|||
@import url("_web_base.css");
|
||||
@import url("web/web__base.css");
|
||||
|
||||
:root {
|
||||
--text-color: #d3dae3;
|
|
@ -0,0 +1 @@
|
|||
@import url("web/web_arc.css");
|
|
@ -1,11 +1,11 @@
|
|||
@font-face {
|
||||
font-family: fira-sans;
|
||||
src: url("../fonts/fira-sans-v9-vietnamese_latin_cyrillic-ext_cyrillic_greek-ext_latin-ext_greek-regular.woff2") format("woff2");
|
||||
src: url("../../fonts/fira-sans-v9-vietnamese_latin_cyrillic-ext_cyrillic_greek-ext_latin-ext_greek-regular.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: fira-mono;
|
||||
src: url("../fonts/fira-mono-v7-latin_cyrillic-ext_cyrillic_greek-ext_latin-ext_greek-regular.woff2") format("woff2");
|
||||
src: url("../../fonts/fira-mono-v7-latin_cyrillic-ext_cyrillic_greek-ext_latin-ext_greek-regular.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
|
@ -56,8 +56,9 @@ body {
|
|||
background-color: var(--background-color);
|
||||
font-family: "Fira Sans", fira-sans, sans-serif, color-emoji;
|
||||
line-height: 1.5;
|
||||
text-size-adjust: 100%;
|
||||
word-wrap: break-word;
|
||||
max-width: 978px;
|
||||
margin: auto;
|
||||
padding: 2em;
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
@import url("_web_base.css");
|
||||
@import url("web/web__base.css");
|
||||
|
||||
a {
|
||||
text-decoration: underline;
|
|
@ -1,4 +1,4 @@
|
|||
@import url("_web_base.css");
|
||||
@import url("web/web__base.css");
|
||||
|
||||
a {
|
||||
text-decoration: underline;
|
|
@ -1 +0,0 @@
|
|||
@import url("web_arc.css");
|
|
@ -6,7 +6,12 @@
|
|||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="margin_left">2</property>
|
||||
<property name="margin_right">2</property>
|
||||
<property name="margin_top">2</property>
|
||||
<property name="margin_bottom">2</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="row_spacing">2</property>
|
||||
<child>
|
||||
<object class="GtkGrid" id="advanced_export_grid">
|
||||
<property name="visible">True</property>
|
||||
|
@ -19,6 +24,7 @@
|
|||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="margin_right">8</property>
|
||||
<property name="label_xalign">0</property>
|
||||
<property name="shadow_type">out</property>
|
||||
<child>
|
||||
|
@ -30,8 +36,9 @@
|
|||
<object class="GtkBox" id="box6">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="margin_right">8</property>
|
||||
<property name="margin_top">4</property>
|
||||
<property name="margin_bottom">4</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="smart">
|
||||
|
@ -42,7 +49,6 @@
|
|||
<property name="receives_default">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Pandoc can automatically make "--" to a long dash and more</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="active">True</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
|
@ -120,7 +126,7 @@
|
|||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="incremental">
|
||||
<property name="label" translatable="yes">Slideshow incremental bullets</property>
|
||||
<property name="label" translatable="yes">Slideshow Incremental Bullets</property>
|
||||
<property name="use_action_appearance">False</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
|
@ -158,6 +164,7 @@
|
|||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="margin_right">8</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkFrame" id="frame1">
|
||||
|
@ -174,18 +181,16 @@
|
|||
<object class="GtkBox" id="box3">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="margin_top">4</property>
|
||||
<property name="margin_bottom">4</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="highlight">
|
||||
<property name="label" translatable="yes">Highlight syntax</property>
|
||||
<property name="use_action_appearance">False</property>
|
||||
<property name="label" translatable="yes">Highlight Syntax</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="active">True</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
|
@ -198,11 +203,13 @@
|
|||
<object class="GtkBox" id="box2">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_top">4</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Choose a color theme for syntax highlighting</property>
|
||||
<property name="margin_left">4</property>
|
||||
<property name="label" translatable="yes">Highlight style </property>
|
||||
</object>
|
||||
<packing>
|
||||
|
@ -216,6 +223,8 @@
|
|||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Choose a color theme for syntax highlighting</property>
|
||||
<property name="margin_left">8</property>
|
||||
<property name="margin_right">8</property>
|
||||
<property name="active">0</property>
|
||||
<property name="active_id">0</property>
|
||||
<items>
|
||||
|
@ -250,7 +259,7 @@
|
|||
<object class="GtkLabel" id="label3">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes"><b>Syntax highlighting</b> (HTML, LaTeX)</property>
|
||||
<property name="label" translatable="yes"><b>Syntax Highlighting</b> (HTML, LaTeX)</property>
|
||||
<property name="use_markup">True</property>
|
||||
</object>
|
||||
</child>
|
||||
|
@ -271,13 +280,40 @@
|
|||
<object class="GtkAlignment" id="alignment3">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_top">4</property>
|
||||
<property name="margin_bottom">4</property>
|
||||
<property name="left_padding">12</property>
|
||||
<child>
|
||||
<object class="GtkFileChooserButton" id="bib_filechooser">
|
||||
<object class="GtkBox" id="box7">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label7">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Choose a bibliography file</property>
|
||||
<property name="label" translatable="yes">File</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFileChooserButton" id="bib_filechooser">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Choose a bibliography file</property>
|
||||
<property name="margin_left">8</property>
|
||||
<property name="margin_right">8</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
|
@ -286,7 +322,7 @@
|
|||
<object class="GtkLabel" id="label4">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes"><b>Bibliography File</b></property>
|
||||
<property name="label" translatable="yes"><b>Bibliography </b></property>
|
||||
<property name="use_markup">True</property>
|
||||
</object>
|
||||
</child>
|
||||
|
@ -320,13 +356,13 @@
|
|||
<object class="GtkBox" id="box4">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="margin_top">4</property>
|
||||
<property name="margin_bottom">4</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="homogeneous">True</property>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="self_contained">
|
||||
<property name="label" translatable="yes">Self Contained</property>
|
||||
<property name="label" translatable="yes">Self-contained</property>
|
||||
<property name="use_action_appearance">False</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
|
@ -343,13 +379,14 @@
|
|||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="html5">
|
||||
<property name="label" translatable="yes">HTML 5</property>
|
||||
<property name="label" translatable="yes">HTML5</property>
|
||||
<property name="use_action_appearance">False</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Use HTML 5 syntax</property>
|
||||
<property name="tooltip_text" translatable="yes">Use HTML5 syntax</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="active">True</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
|
@ -367,6 +404,7 @@
|
|||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Choose a CSS File that you want to use</property>
|
||||
<property name="margin_left">4</property>
|
||||
<property name="label" translatable="yes">CSS File</property>
|
||||
</object>
|
||||
<packing>
|
||||
|
@ -380,6 +418,8 @@
|
|||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Choose a CSS File that you want to use</property>
|
||||
<property name="margin_left">8</property>
|
||||
<property name="margin_right">8</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
|
@ -426,7 +466,7 @@
|
|||
<property name="receives_default">True</property>
|
||||
<property name="has_tooltip">True</property>
|
||||
<property name="relief">none</property>
|
||||
<property name="uri">http://johnmacfarlane.net/pandoc/README.html</property>
|
||||
<property name="uri">https://pandoc.org/MANUAL.html</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
|
@ -547,6 +587,19 @@
|
|||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="transition_type">crossfade</property>
|
||||
<child>
|
||||
<object class="GtkFileChooserWidget" id="html">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="action">save</property>
|
||||
<property name="filter">html_filter</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">html</property>
|
||||
<property name="title" translatable="yes">HTML</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFileChooserWidget" id="pdf">
|
||||
<property name="visible">True</property>
|
||||
|
@ -570,32 +623,6 @@
|
|||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFileChooserWidget" id="html">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="action">save</property>
|
||||
<property name="filter">html_filter</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">html</property>
|
||||
<property name="title" translatable="yes">HTML</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFileChooserWidget" id="odt">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="action">save</property>
|
||||
<property name="filter">odt_filter</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">odt</property>
|
||||
<property name="title" translatable="yes">ODT</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFileChooserWidget" id="advanced">
|
||||
<property name="visible">True</property>
|
||||
|
|
|
@ -30,17 +30,7 @@
|
|||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Copy HTML</attribute>
|
||||
<attribute name="action">app.HTML_copy</attribute>
|
||||
</item>
|
||||
</section>
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Open Tutorial</attribute>
|
||||
<attribute name="action">app.open_examples</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="action">app.help</attribute>
|
||||
<attribute name="label" translatable="yes">Pandoc _Help</attribute>
|
||||
<attribute name="action">app.copy_html</attribute>
|
||||
</item>
|
||||
</section>
|
||||
<section>
|
||||
|
@ -52,6 +42,10 @@
|
|||
<attribute name="action">app.shortcuts</attribute>
|
||||
<attribute name="label" translatable="yes">_Keyboard Shortcuts</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Open Tutorial</attribute>
|
||||
<attribute name="action">app.open_tutorial</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="action">app.about</attribute>
|
||||
<attribute name="label" translatable="yes">_About UberWriter</attribute>
|
||||
|
|
|
@ -2,6 +2,11 @@
|
|||
<!-- Generated with glade 3.22.1 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.20"/>
|
||||
<object class="GtkImage" id="help">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">dialog-information-symbolic</property>
|
||||
</object>
|
||||
<object class="GtkWindow" id="PreferencesWindow">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="modal">True</property>
|
||||
|
@ -30,19 +35,18 @@
|
|||
<property name="can_focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="margin_left">30</property>
|
||||
<property name="margin_right">30</property>
|
||||
<property name="margin_top">30</property>
|
||||
<property name="margin_bottom">30</property>
|
||||
<property name="row_spacing">10</property>
|
||||
<property name="column_spacing">10</property>
|
||||
<property name="margin_left">16</property>
|
||||
<property name="margin_right">16</property>
|
||||
<property name="margin_top">16</property>
|
||||
<property name="margin_bottom">16</property>
|
||||
<property name="row_spacing">8</property>
|
||||
<property name="column_spacing">8</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="Dark_mode_label">
|
||||
<object class="GtkLabel" id="dark_mode_auto_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">end</property>
|
||||
<property name="valign">start</property>
|
||||
<property name="label" translatable="yes">Use dark mode</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Set dark mode automatically</property>
|
||||
<property name="justify">right</property>
|
||||
</object>
|
||||
<packing>
|
||||
|
@ -51,11 +55,22 @@
|
|||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="Spellcheck_label">
|
||||
<object class="GtkSwitch" id="dark_mode_auto_switch">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="halign">end</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">2</property>
|
||||
<property name="top_attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="dark_mode_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">end</property>
|
||||
<property name="label" translatable="yes">Autospellcheck</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Force dark mode</property>
|
||||
<property name="justify">right</property>
|
||||
</object>
|
||||
<packing>
|
||||
|
@ -64,67 +79,122 @@
|
|||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="Gradient_label">
|
||||
<object class="GtkSwitch" id="dark_mode_switch">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="halign">end</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">2</property>
|
||||
<property name="top_attach">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="spellcheck_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Check spelling while typing</property>
|
||||
<property name="justify">right</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSwitch" id="spellcheck_switch">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="halign">end</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">2</property>
|
||||
<property name="top_attach">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="gradient_overlay_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Draw scroll gradient</property>
|
||||
<property name="justify">right</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">2</property>
|
||||
<property name="top_attach">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSwitch" id="Dark_mode_switch">
|
||||
<object class="GtkSwitch" id="gradient_overlay_switch">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="action_name">app.dark_mode</property>
|
||||
<property name="halign">end</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="top_attach">0</property>
|
||||
<property name="left_attach">2</property>
|
||||
<property name="top_attach">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSwitch" id="Spellcheck_switch">
|
||||
<object class="GtkLabel" id="format_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="action_name">app.spellcheck</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Input format</property>
|
||||
<property name="justify">right</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="top_attach">1</property>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSwitch" id="Gradient_switch">
|
||||
<object class="GtkButton" id="input_format_help_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="valign">start</property>
|
||||
<property name="action_name">app.draw_gradient</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="image">help</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="top_attach">2</property>
|
||||
<property name="top_attach">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="tab_expand">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child type="tab">
|
||||
<object class="GtkLabel" id="Label1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">page 1</property>
|
||||
<child>
|
||||
<object class="GtkComboBox" id="input_format_combobox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="halign">end</property>
|
||||
<property name="active">0</property>
|
||||
<property name="active_id">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">2</property>
|
||||
<property name="top_attach">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="tab_fill">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child type="tab">
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
<requires lib="gtk+" version="3.20"/>
|
||||
<object class="GtkRecentFilter" id="recent_md_filter">
|
||||
<mime-types>
|
||||
<mime-type>text/markdown</mime-type>
|
||||
<mime-type>text/x-markdown</mime-type>
|
||||
</mime-types>
|
||||
</object>
|
||||
|
|
|
@ -1,548 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<!-- interface-requires gtk+ 3.0 -->
|
||||
<!-- interface-requires uberwriter_advanced_export_dialog 1.0 -->
|
||||
<object class="UberwriterAdvancedExportDialog" id="uberwriter_advanced_export_dialog">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="border_width">5</property>
|
||||
<property name="type_hint">dialog</property>
|
||||
<child internal-child="vbox">
|
||||
<object class="GtkBox" id="dialog-vbox1">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">2</property>
|
||||
<child internal-child="action_area">
|
||||
<object class="GtkButtonBox" id="dialog-action_area1">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="layout_style">end</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="button2">
|
||||
<property name="label">gtk-cancel</property>
|
||||
<property name="use_action_appearance">False</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="use_action_appearance">False</property>
|
||||
<property name="use_stock">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="button1">
|
||||
<property name="label" translatable="yes">Export</property>
|
||||
<property name="use_action_appearance">False</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="has_focus">True</property>
|
||||
<property name="is_focus">True</property>
|
||||
<property name="can_default">True</property>
|
||||
<property name="has_default">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="use_action_appearance">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="box1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkComboBox" id="choose_format">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFrame" id="frame4">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label_xalign">0</property>
|
||||
<property name="shadow_type">out</property>
|
||||
<child>
|
||||
<object class="GtkAlignment" id="alignment4">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="left_padding">12</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="box6">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="smart">
|
||||
<property name="label" translatable="yes">Smart</property>
|
||||
<property name="use_action_appearance">False</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Pandoc can automatically make "--" to a long dash and more</property>
|
||||
<property name="use_action_appearance">False</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="active">True</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="normalize">
|
||||
<property name="label" translatable="yes">Normalize</property>
|
||||
<property name="use_action_appearance">False</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Removes things like double spaces or spaces at the beginning of a paragraph</property>
|
||||
<property name="use_action_appearance">False</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="toc">
|
||||
<property name="label" translatable="yes">Table of Contents</property>
|
||||
<property name="use_action_appearance">False</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="use_action_appearance">False</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="standalone">
|
||||
<property name="label" translatable="yes">Standalone</property>
|
||||
<property name="use_action_appearance">False</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Use a header and footer to include things like stylesheets and meta information</property>
|
||||
<property name="use_action_appearance">False</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="active">True</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="number_sections">
|
||||
<property name="label" translatable="yes">Number Sections</property>
|
||||
<property name="use_action_appearance">False</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="use_action_appearance">False</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="strict">
|
||||
<property name="label" translatable="yes">Strict Markdown</property>
|
||||
<property name="use_action_appearance">False</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Use "strict" markdown instead of "pandoc" markdown</property>
|
||||
<property name="use_action_appearance">False</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">5</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="incremental">
|
||||
<property name="label" translatable="yes">Slideshow incremental bullets</property>
|
||||
<property name="use_action_appearance">False</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Show one bullet point after another in a slideshow</property>
|
||||
<property name="use_action_appearance">False</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">6</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child type="label">
|
||||
<object class="GtkLabel" id="label6">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes"><b>General Options</b></property>
|
||||
<property name="use_markup">True</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="padding">5</property>
|
||||
<property name="position">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFrame" id="frame1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label_xalign">0</property>
|
||||
<property name="shadow_type">out</property>
|
||||
<child>
|
||||
<object class="GtkAlignment" id="alignment1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="left_padding">12</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="box3">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="highlight">
|
||||
<property name="label" translatable="yes">Highlight syntax</property>
|
||||
<property name="use_action_appearance">False</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="use_action_appearance">False</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="active">True</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="box2">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Choose a color theme for syntax highlighting</property>
|
||||
<property name="label" translatable="yes">Highlight style </property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkComboBoxText" id="highlight_style">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Choose a color theme for syntax highlighting</property>
|
||||
<property name="active">0</property>
|
||||
<property name="entry_text_column">0</property>
|
||||
<property name="id_column">1</property>
|
||||
<property name="active_id">0</property>
|
||||
<items>
|
||||
<item>pygments</item>
|
||||
<item>kate</item>
|
||||
<item>monochrome</item>
|
||||
<item>espresso</item>
|
||||
<item>zenburn</item>
|
||||
<item>haddock</item>
|
||||
<item>tango</item>
|
||||
</items>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child type="label">
|
||||
<object class="GtkLabel" id="label3">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes"><b>Syntax highlighting</b> (HTML, LaTeX)</property>
|
||||
<property name="use_markup">True</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="padding">5</property>
|
||||
<property name="position">5</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFrame" id="frame2">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label_xalign">0</property>
|
||||
<property name="shadow_type">out</property>
|
||||
<child>
|
||||
<object class="GtkAlignment" id="alignment2">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="left_padding">12</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="box4">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="homogeneous">True</property>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="self_contained">
|
||||
<property name="label" translatable="yes">Self Contained</property>
|
||||
<property name="use_action_appearance">False</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Produces a HTML that has no external dependencies (all images and stylesheets are included)</property>
|
||||
<property name="use_action_appearance">False</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="html5">
|
||||
<property name="label" translatable="yes">HTML 5</property>
|
||||
<property name="use_action_appearance">False</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Use HTML 5 syntax</property>
|
||||
<property name="use_action_appearance">False</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="box5">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label5">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Choose a CSS File that you want to use</property>
|
||||
<property name="margin_right">10</property>
|
||||
<property name="label" translatable="yes">CSS File</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFileChooserButton" id="css_filechooser">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Choose a CSS File that you want to use</property>
|
||||
<property name="orientation">vertical</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child type="label">
|
||||
<object class="GtkLabel" id="label2">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes"><b>HTML Options</b></property>
|
||||
<property name="use_markup">True</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="padding">5</property>
|
||||
<property name="position">6</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFrame" id="frame3">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label_xalign">0</property>
|
||||
<property name="shadow_type">out</property>
|
||||
<child>
|
||||
<object class="GtkAlignment" id="alignment3">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="left_padding">12</property>
|
||||
<child>
|
||||
<object class="GtkFileChooserButton" id="bib_filechooser">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">5</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child type="label">
|
||||
<object class="GtkLabel" id="label4">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes"><b>Bibliography File</b></property>
|
||||
<property name="use_markup">True</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="padding">5</property>
|
||||
<property name="position">7</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="box7">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="homogeneous">True</property>
|
||||
<child>
|
||||
<object class="GtkLinkButton" id="linkbutton1">
|
||||
<property name="label" translatable="yes">Commandline Reference</property>
|
||||
<property name="use_action_appearance">False</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="has_tooltip">True</property>
|
||||
<property name="use_action_appearance">False</property>
|
||||
<property name="relief">none</property>
|
||||
<property name="uri">http://johnmacfarlane.net/pandoc/README.html</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="padding">5</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">9</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<action-widgets>
|
||||
<action-widget response="0">button2</action-widget>
|
||||
<action-widget response="1">button1</action-widget>
|
||||
</action-widgets>
|
||||
</object>
|
||||
</interface>
|
|
@ -16,7 +16,6 @@
|
|||
<object class="GtkImage" id="avall">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Next Match</property>
|
||||
<property name="icon_name">go-down-symbolic</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="ortografia1">
|
||||
|
@ -33,7 +32,6 @@
|
|||
<object class="GtkImage" id="reemplaza">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Open Replace</property>
|
||||
<property name="icon_name">edit-find-replace-symbolic</property>
|
||||
</object>
|
||||
<object class="GtkOverlay" id="FullscreenOverlay">
|
||||
|
@ -77,97 +75,20 @@
|
|||
<property name="can_focus">False</property>
|
||||
<property name="hexpand">True</property>
|
||||
<child>
|
||||
<object class="GtkRevealer" id="status_bar_revealer">
|
||||
<object class="GtkRevealer" id="stats_counter_revealer">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="transition_type">crossfade</property>
|
||||
<property name="transition_duration">750</property>
|
||||
<property name="reveal_child">True</property>
|
||||
<child>
|
||||
<object class="GtkGrid" id="status_bar_box">
|
||||
<object class="GtkButton" id="stats_counter">
|
||||
<property name="label" translatable="yes">0 Words</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label2">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">end</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="label" translatable="yes">Words:</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">3</property>
|
||||
<property name="top_attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="word_count">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">end</property>
|
||||
<property name="label">0</property>
|
||||
<property name="justify">right</property>
|
||||
<property name="width_chars">4</property>
|
||||
<property name="xalign">1</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">4</property>
|
||||
<property name="top_attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparator" id="separator1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">end</property>
|
||||
<property name="margin_left">10</property>
|
||||
<property name="margin_right">10</property>
|
||||
<property name="margin_start">10</property>
|
||||
<property name="margin_end">10</property>
|
||||
<property name="orientation">vertical</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">5</property>
|
||||
<property name="top_attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">end</property>
|
||||
<property name="label" translatable="yes">Characters:</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">6</property>
|
||||
<property name="top_attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="char_count">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">end</property>
|
||||
<property name="margin_right">11</property>
|
||||
<property name="margin_end">11</property>
|
||||
<property name="label">0</property>
|
||||
<property name="width_chars">6</property>
|
||||
<property name="xalign">1</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">7</property>
|
||||
<property name="top_attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Show Statistics</property>
|
||||
<property name="halign">end</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
|
@ -186,23 +107,16 @@
|
|||
<property name="vscroll_policy">natural</property>
|
||||
<property name="shadow_type">none</property>
|
||||
<child>
|
||||
<object class="GtkAlignment" id="editor_alignment">
|
||||
<object class="GtkScrolledWindow" id="editor_scrolledwindow">
|
||||
<property name="height_request">500</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="vexpand">True</property>
|
||||
<property name="vadjustment">adjustment1</property>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="editor_scrolledwindow">
|
||||
<property name="height_request">500</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="vadjustment">adjustment1</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
|
@ -290,6 +204,7 @@
|
|||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Next Match</property>
|
||||
<property name="image">avall</property>
|
||||
</object>
|
||||
<packing>
|
||||
|
@ -335,6 +250,7 @@
|
|||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Regular Expression</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
|
@ -347,6 +263,7 @@
|
|||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Open Replace</property>
|
||||
<property name="image">reemplaza</property>
|
||||
</object>
|
||||
<packing>
|
|
@ -1,9 +0,0 @@
|
|||
<glade-catalog name="uberwriter_advanced_export_dialog" domain="glade-3"
|
||||
depends="gtk+" version="1.0">
|
||||
<glade-widget-classes>
|
||||
<glade-widget-class title="Advanced Export Dialog" name="UberwriterAdvancedExportDialog"
|
||||
generic-name="uberwriter_advanced_export_dialog" parent="GtkDialog"
|
||||
icon-name="widget-gtk-dialog"/>
|
||||
</glade-widget-classes>
|
||||
|
||||
</glade-catalog>
|
|
@ -0,0 +1,35 @@
|
|||
|
||||
{
|
||||
"name": "pipdeps",
|
||||
"buildsystem": "simple",
|
||||
"build-commands": [
|
||||
"pip3 install --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} pyenchant regex pypandoc"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/5d/c1/45947333669b31bc6b4933308dd07c2aa2fedcec0a95b14eedae993bd449/wheel-0.31.0.tar.gz",
|
||||
"sha256": "1ae8153bed701cb062913b72429bcf854ba824f973735427681882a688cb55ce"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/ae/e8/2340d46ecadb1692a1e455f13f75e596d4eab3d11a57446f08259dee8f02/pip-10.0.1.tar.gz",
|
||||
"sha256": "f2bd08e0cd1b06e10218feaf6fef299f473ba706582eb3bd9d52203fdbd7ee68"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/71/81/00184643e5a10a456b4118fc12c96780823adb8ed974eb2289f29703b29b/pypandoc-1.4.tar.gz",
|
||||
"sha256": "e914e6d5f84a76764887e4d909b09d63308725f0cbb5293872c2c92f07c11a5b"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/a2/51/c39562cfed3272592c60cfd229e5464d715b78537e332eac2b695422dc49/regex-2018.02.21.tar.gz",
|
||||
"sha256": "b44624a38d07d3c954c84ad302c29f7930f4bf01443beef5589e9157b14e2a29"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/9e/54/04d88a59efa33fefb88133ceb638cdf754319030c28aadc5a379d82140ed/pyenchant-2.0.0.tar.gz",
|
||||
"sha256": "fc31cda72ace001da8fe5d42f11c26e514a91fa8c70468739216ddd8de64e2a0"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,134 +1,125 @@
|
|||
{
|
||||
"app-id": "de.wolfvollprecht.UberWriter",
|
||||
"runtime": "org.gnome.Platform",
|
||||
"runtime-version": "3.28",
|
||||
"sdk": "org.gnome.Sdk",
|
||||
"command": "/app/usr/bin/uberwriter",
|
||||
"finish-args": [
|
||||
"app-id":"de.wolfvollprecht.UberWriter",
|
||||
"runtime":"org.gnome.Platform",
|
||||
"runtime-version":"3.32",
|
||||
"sdk":"org.gnome.Sdk",
|
||||
"command":"start-uberwriter",
|
||||
"finish-args":[
|
||||
"--socket=x11",
|
||||
"--socket=wayland",
|
||||
"--share=ipc",
|
||||
"--share=network",
|
||||
"--filesystem=host",
|
||||
"--env=IN_FLATPAK=1",
|
||||
"--filesystem=xdg-run/dconf",
|
||||
"--filesystem=~/.config/dconf:ro",
|
||||
"--talk-name=ca.desrt.dconf",
|
||||
"--env=DCONF_USER_CONFIG_DIR=.config/dconf",
|
||||
"--env=XDG_DATA_DIRS=/app/usr/share",
|
||||
"--env=PATH=/app/extensions/TexLive/bin:/app/extensions/TexLive/2018/bin/x86_64-linux:/app/usr/bin:/app/bin"
|
||||
"--env=DCONF_USER_CONFIG_DIR=.config/dconf"
|
||||
],
|
||||
"build-options" : {
|
||||
"env": {
|
||||
"PYTHON": "python3",
|
||||
"IN_FLATPAK": "1"
|
||||
"add-extensions":{
|
||||
"de.wolfvollprecht.UberWriter.Plugin":{
|
||||
"directory":"extensions",
|
||||
"version":"stable",
|
||||
"subdirectories":true,
|
||||
"no-autodownload":true,
|
||||
"autodelete":true
|
||||
}
|
||||
},
|
||||
"add-extensions": {
|
||||
"de.wolfvollprecht.UberWriter.Plugin": {
|
||||
"directory": "extensions",
|
||||
"version": "stable",
|
||||
"subdirectories": true,
|
||||
"no-autodownload": true,
|
||||
"autodelete": true
|
||||
}
|
||||
},
|
||||
"modules": [
|
||||
"modules":[
|
||||
{
|
||||
"name": "uberwriter",
|
||||
"sources": [
|
||||
"name":"enchant",
|
||||
"config-opts":[
|
||||
"--disable-static",
|
||||
"--with-myspell-dir=/usr/share/hunspell"
|
||||
],
|
||||
"cleanup":[
|
||||
"/bin"
|
||||
],
|
||||
"sources":[
|
||||
{
|
||||
"type" : "git",
|
||||
"url" : "../",
|
||||
"branch" : "refactoring"
|
||||
}
|
||||
],
|
||||
"build-commands": [
|
||||
"install -Dm644 flatpak/de.wolfvollprecht.UberWriter.appdata.xml /app/share/appdata/de.wolfvollprecht.UberWriter.appdata.xml "
|
||||
],
|
||||
"post-install": [
|
||||
"glib-compile-schemas /app/usr/share/glib-2.0/schemas",
|
||||
"install -d /app/extensions"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "pandoc",
|
||||
"only-arches": [
|
||||
"x86_64"
|
||||
],
|
||||
"buildsystem": "simple",
|
||||
"build-commands": [
|
||||
"cp bin/pandoc /app/usr/bin/pandoc",
|
||||
"cp bin/pandoc-citeproc /app/usr/bin/pandoc-citeproc"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"type": "archive",
|
||||
"url": "https://github.com/jgm/pandoc/releases/download/2.2/pandoc-2.2-linux.tar.gz",
|
||||
"sha256": "06ecd882e42ef9b7390b1c82e1e71b3ea48679181289b9b810a8797825bed8ed"
|
||||
"type":"archive",
|
||||
"url":"https://github.com/AbiWord/enchant/releases/download/enchant-1-6-1/enchant-1.6.1.tar.gz",
|
||||
"sha256":"bef0d9c0fef2e4e8746956b68e4d6c6641f6b85bd2908d91731efb68eba9e3f5"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "pipdeps",
|
||||
"buildsystem": "simple",
|
||||
"build-commands": [
|
||||
"pip3 install --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} pyenchant regex pypandoc"
|
||||
],
|
||||
"sources": [
|
||||
"name":"gspell",
|
||||
"sources":[
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/5d/c1/45947333669b31bc6b4933308dd07c2aa2fedcec0a95b14eedae993bd449/wheel-0.31.0.tar.gz",
|
||||
"sha256": "1ae8153bed701cb062913b72429bcf854ba824f973735427681882a688cb55ce"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/ae/e8/2340d46ecadb1692a1e455f13f75e596d4eab3d11a57446f08259dee8f02/pip-10.0.1.tar.gz",
|
||||
"sha256": "f2bd08e0cd1b06e10218feaf6fef299f473ba706582eb3bd9d52203fdbd7ee68"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/71/81/00184643e5a10a456b4118fc12c96780823adb8ed974eb2289f29703b29b/pypandoc-1.4.tar.gz",
|
||||
"sha256": "e914e6d5f84a76764887e4d909b09d63308725f0cbb5293872c2c92f07c11a5b"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/a2/51/c39562cfed3272592c60cfd229e5464d715b78537e332eac2b695422dc49/regex-2018.02.21.tar.gz",
|
||||
"sha256": "b44624a38d07d3c954c84ad302c29f7930f4bf01443beef5589e9157b14e2a29"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/9e/54/04d88a59efa33fefb88133ceb638cdf754319030c28aadc5a379d82140ed/pyenchant-2.0.0.tar.gz",
|
||||
"sha256": "fc31cda72ace001da8fe5d42f11c26e514a91fa8c70468739216ddd8de64e2a0"
|
||||
"type":"archive",
|
||||
"url":"https://download.gnome.org/sources/gspell/1.8/gspell-1.8.1.tar.xz",
|
||||
"sha256":"819a1d23c7603000e73f5e738bdd284342e0cd345fb0c7650999c31ec741bbe5"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "fonts",
|
||||
"buildsystem": "simple",
|
||||
"build-commands": [
|
||||
"mkdir -p /app/share/fonts/",
|
||||
"cp ttf/* /app/share/fonts/"
|
||||
"name":"fonts",
|
||||
"buildsystem":"simple",
|
||||
"build-commands":[
|
||||
"mkdir -p /app/share/fonts/",
|
||||
"cp ttf/* /app/share/fonts/"
|
||||
],
|
||||
"sources": [
|
||||
"sources":[
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://github.com/mozilla/Fira",
|
||||
"tag": "4.202"
|
||||
"type":"git",
|
||||
"url":"https://github.com/mozilla/Fira",
|
||||
"tag":"4.202"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "appdata",
|
||||
"buildsystem": "simple",
|
||||
"build-commands": [
|
||||
"mkdir -p /app/share/appdata",
|
||||
"install -Dm644 de.wolfvollprecht.UberWriter.appdata.xml /app/share/appdata/de.wolfvollprecht.UberWriter.appdata.xml"
|
||||
"name":"pandoc",
|
||||
"only-arches":[
|
||||
"x86_64"
|
||||
],
|
||||
"sources": [
|
||||
"buildsystem":"simple",
|
||||
"build-commands":[
|
||||
"install -Dm 755 bin/pandoc /app/bin/pandoc",
|
||||
"install -Dm 755 bin/pandoc-citeproc /app/bin/pandoc-citeproc"
|
||||
],
|
||||
"sources":[
|
||||
{
|
||||
"type": "file",
|
||||
"path": "de.wolfvollprecht.UberWriter.appdata.xml"
|
||||
"type":"archive",
|
||||
"url":"https://github.com/jgm/pandoc/releases/download/2.2/pandoc-2.2-linux.tar.gz",
|
||||
"sha256":"06ecd882e42ef9b7390b1c82e1e71b3ea48679181289b9b810a8797825bed8ed"
|
||||
}
|
||||
]
|
||||
},
|
||||
"de.wolfvollprecht.UberWriter.pipdeps.json",
|
||||
{
|
||||
"name":"uberwriter",
|
||||
"buildsystem":"simple",
|
||||
"build-commands":[
|
||||
"desktop-file-edit --set-key=Exec --set-value='uberwriter.in %U' data/de.wolfvollprecht.UberWriter.desktop",
|
||||
"python3 -m pip install --prefix=/app --install-option=--optimize=1 ."
|
||||
],
|
||||
"sources":[
|
||||
{
|
||||
"type":"dir",
|
||||
"path":"../"
|
||||
}
|
||||
],
|
||||
"post-install":[
|
||||
"install -d /app/extensions",
|
||||
"glib-compile-schemas /app/share/glib-2.0/schemas"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name":"scripts",
|
||||
"buildsystem":"simple",
|
||||
"build-commands":[
|
||||
"install -Dm 755 start-uberwriter.sh /app/bin/start-uberwriter"
|
||||
],
|
||||
"sources":[
|
||||
{
|
||||
"type":"script",
|
||||
"dest-filename":"start-uberwriter.sh",
|
||||
"commands":[
|
||||
"export PATH=/app/extensions/TexLive/bin:/app/extensions/TexLive/2018/bin/$(uname -a)-linux:$PATH",
|
||||
"exec uberwriter.in \"$@\""
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
</info>
|
||||
|
||||
<title>UberWriter Preview</title>
|
||||
<p>There are 2 different ways to preview your MarkDown files in UberWriter and
|
||||
<p>There are 2 different ways to preview your Markdown files in UberWriter and
|
||||
quickly check, what you have written.
|
||||
</p>
|
||||
<section id="inline-preview">
|
||||
|
@ -29,6 +29,6 @@ quickly check, what you have written.
|
|||
<title>Complete Preview</title>
|
||||
<p>If you want a complete Preview of your document, you just need to hit the
|
||||
preview Button on the statusbar at the bottom of the UberWriter window.
|
||||
It will render the complete HTML Output of your MarkDown file.</p>
|
||||
It will render the complete HTML Output of your Markdown file.</p>
|
||||
</section>
|
||||
</page>
|
||||
|
|
|
@ -619,9 +619,9 @@ Because `_` is sometimes used inside words and identifiers, pandoc does not inte
|
|||
|
||||
feas*ible*, not feas*able*.
|
||||
|
||||
#### Strikeout
|
||||
#### Strikethrough
|
||||
|
||||
To strikeout a section of text with a horizontal line, begin and end it with `~~`. Thus, for example,
|
||||
To strikethrough a section of text with a horizontal line, begin and end it with `~~`. Thus, for example,
|
||||
|
||||
This ~~is deleted text.~~
|
||||
|
||||
|
@ -1183,7 +1183,7 @@ Sergey Astanin.
|
|||
[Slidy]: http://www.w3.org/Talks/Tools/Slidy/
|
||||
[Slideous]: http://goessner.net/articles/slideous/
|
||||
[HTML]: http://www.w3.org/TR/html40/
|
||||
[HTML 5]: http://www.w3.org/TR/html5/
|
||||
[HTML5]: http://www.w3.org/TR/html5/
|
||||
[XHTML]: http://www.w3.org/TR/xhtml1/
|
||||
[LaTeX]: http://www.latex-project.org/
|
||||
[beamer]: http://www.tex.ac.uk/CTAN/macros/latex/contrib/beamer
|
||||
|
|
|
@ -47,8 +47,8 @@ is *emphasized with asterisks*.</code></pre>
|
|||
<pre><code>This is * not emphasized *, and \*neither is this\*.</code></pre>
|
||||
<p>Because <code>_</code> is sometimes used inside words and identifiers, pandoc does not interpret a <code>_</code> surrounded by alphanumeric characters as an emphasis marker. If you want to emphasize just part of a word, use <code>*</code>:</p>
|
||||
<pre><code>feas*ible*, not feas*able*.</code></pre>
|
||||
<h4 id="strikeout">Strikeout</h4>
|
||||
<p>To strikeout a section of text with a horizontal line, begin and end it with <code>~~</code>. Thus, for example,</p>
|
||||
<h4 id="strikethrough">Strikethrough</h4>
|
||||
<p>To strikethrough a section of text with a horizontal line, begin and end it with <code>~~</code>. Thus, for example,</p>
|
||||
<pre><code>This ~~is deleted text.~~</code></pre>
|
||||
<h3 id="block-quotations">Block quotations</h3>
|
||||
<p>Markdown uses email conventions for quoting blocks of text. A block quotation is one or more paragraphs or other block elements (such as lists or headers), with each line preceded by a <code>></code> character and a space.</p>
|
||||
|
|
|
@ -67,9 +67,9 @@ Because `_` is sometimes used inside words and identifiers, pandoc does not inte
|
|||
|
||||
feas*ible*, not feas*able*.
|
||||
|
||||
### Strikeout
|
||||
### Strikethrough
|
||||
|
||||
To strikeout a section of text with a horizontal line, begin and end it with `~~`. Thus, for example,
|
||||
To strikethrough a section of text with a horizontal line, begin and end it with `~~`. Thus, for example,
|
||||
|
||||
This ~~is deleted text.~~
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
regex
|
||||
enchant
|
||||
python-gtkspellcheck
|
||||
pandoc
|
||||
pypandoc==1.4
|
||||
pyenchant
|
||||
pygtkspellcheck
|
||||
|
|
69
setup.py
69
setup.py
|
@ -2,16 +2,16 @@
|
|||
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
|
||||
### BEGIN LICENSE
|
||||
# Copyright (C) 2012, Wolf Vollprecht <w.vollprecht@gmail.com>
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License version 3, as published
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License version 3, as published
|
||||
# by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranties of
|
||||
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranties of
|
||||
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
|
||||
# PURPOSE. See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
### END LICENSE
|
||||
|
||||
|
@ -22,37 +22,33 @@
|
|||
from setuptools import setup
|
||||
import os
|
||||
|
||||
def package_files(directory):
|
||||
paths = []
|
||||
for (path, directories, filenames) in os.walk(directory):
|
||||
def data_files(basename):
|
||||
data = os.path.join('.', 'data')
|
||||
root = os.path.join(data, basename)
|
||||
extra_files = []
|
||||
for path, directories, filenames in os.walk(root):
|
||||
paths = []
|
||||
for filename in filenames:
|
||||
paths.append(os.path.join(path, filename))
|
||||
return paths
|
||||
extra_files.append(('share/uberwriter/data/{}'.format(os.path.relpath(path, data)), paths))
|
||||
return extra_files
|
||||
|
||||
extra_files_ui = package_files('./data/ui')
|
||||
extra_files_media = package_files('./data/media')
|
||||
extra_files_scripts = package_files('./data/lua')
|
||||
from pprint import pprint
|
||||
pprint(extra_files_ui)
|
||||
pprint(extra_files_media)
|
||||
|
||||
if os.path.isfile("/.flatpak-info"):
|
||||
app_prefix = '/app/'
|
||||
else:
|
||||
app_prefix = '/usr/'
|
||||
extra_files_ui = data_files('ui')
|
||||
extra_files_media = data_files('media')
|
||||
extra_files_scripts = data_files('lua')
|
||||
|
||||
setup(
|
||||
name='uberwriter',
|
||||
version='2.1.4',
|
||||
version='2.2.0-beta1',
|
||||
license='GPL-3',
|
||||
author='Wolf Vollprecht',
|
||||
author_email='w.vollprecht@gmail.com',
|
||||
description='A beautiful, simple and distraction free markdown editor.',
|
||||
long_description="""UberWriter, beautiful distraction free writing
|
||||
With UberWriter you get only one thing: An empty textbox, that is to
|
||||
fill with your ideas. There are no settings, you don't have to choose a
|
||||
font, it is only for writing.You can use markdown for all your markup
|
||||
needs. PDF, RTF and HTML are generated with pandoc. For PDF generation it
|
||||
With UberWriter you get only one thing: An empty textbox, that is to
|
||||
fill with your ideas. There are no settings, you don't have to choose a
|
||||
font, it is only for writing.You can use markdown for all your markup
|
||||
needs. PDF, RTF and HTML are generated with pandoc. For PDF generation it
|
||||
is also required that you choose to install the texlive-luatex package.""",
|
||||
url='https://github.com/wolfv/uberwriter/',
|
||||
# cmdclass={'install': InstallAndUpdateDataDirectory},
|
||||
|
@ -60,11 +56,9 @@ setup(
|
|||
# "": '/opt/uberwriter/'
|
||||
},
|
||||
packages=[
|
||||
"uberwriter.gtkspellcheck",
|
||||
"uberwriter.pylocales",
|
||||
# "uberwriter.pressagio",
|
||||
"uberwriter",
|
||||
"uberwriter",
|
||||
"po"
|
||||
# "uberwriter.plugins"
|
||||
# "uberwriter.plugins.bibtex"
|
||||
|
@ -75,13 +69,12 @@ setup(
|
|||
'uberwriter.pylocales' : ['locales.db'],
|
||||
},
|
||||
data_files=[
|
||||
(app_prefix + 'bin', ['bin/uberwriter']),
|
||||
(app_prefix + 'share/glib-2.0/schemas', ['data/de.wolfvollprecht.UberWriter.gschema.xml']),
|
||||
(app_prefix + 'share/icons/hicolor/scalable/apps', ['data/media/de.wolfvollprecht.UberWriter.svg']),
|
||||
(app_prefix + 'share/icons/hicolor/symbolic/apps', ['data/media/de.wolfvollprecht.UberWriter-symbolic.svg']),
|
||||
(app_prefix + 'share/applications', ['de.wolfvollprecht.UberWriter.desktop']),
|
||||
(app_prefix + 'share/uberwriter/data/ui', extra_files_ui),
|
||||
(app_prefix + 'share/uberwriter/data/media', extra_files_media),
|
||||
(app_prefix + 'share/uberwriter/data/lua', extra_files_scripts)
|
||||
('bin', ['uberwriter.in']),
|
||||
('share/applications', ['data/de.wolfvollprecht.UberWriter.desktop']),
|
||||
('share/metainfo', ['data/de.wolfvollprecht.UberWriter.appdata.xml']),
|
||||
('share/icons/hicolor/scalable/apps', ['data/media/de.wolfvollprecht.UberWriter.svg']),
|
||||
('share/icons/hicolor/symbolic/apps', ['data/media/de.wolfvollprecht.UberWriter-symbolic.svg']),
|
||||
('share/glib-2.0/schemas', ['data/de.wolfvollprecht.UberWriter.gschema.xml']),
|
||||
*(extra_files_ui + extra_files_media + extra_files_scripts)
|
||||
]
|
||||
)
|
||||
|
|
|
@ -15,36 +15,21 @@
|
|||
### END LICENSE
|
||||
import sys
|
||||
|
||||
import locale
|
||||
import os
|
||||
|
||||
import gettext
|
||||
from gettext import gettext as _
|
||||
|
||||
import gi
|
||||
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk # pylint: disable=E0611
|
||||
|
||||
from uberwriter import window
|
||||
from uberwriter import application
|
||||
from uberwriter.helpers import set_up_logging
|
||||
from uberwriter.uberwriterconfig import get_version
|
||||
from uberwriter.config import get_version
|
||||
|
||||
|
||||
def main():
|
||||
'constructor for your class instances'
|
||||
# (options, args) = parse_options()
|
||||
|
||||
|
||||
# Run the application.
|
||||
app = application.Application()
|
||||
|
||||
# ~ if args:
|
||||
# ~ for arg in args:
|
||||
# ~ pass
|
||||
# ~ else:
|
||||
# ~ pass
|
||||
# ~ if options.experimental_features:
|
||||
# ~ window.use_experimental_features(True)
|
||||
|
||||
|
||||
app.run(sys.argv)
|
||||
|
||||
|
|
|
@ -11,17 +11,14 @@
|
|||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import argparse
|
||||
import webbrowser
|
||||
from gettext import gettext as _
|
||||
|
||||
import gi
|
||||
|
||||
|
||||
gi.require_version('Gtk', '3.0') # pylint: disable=wrong-import-position
|
||||
from gi.repository import GLib, Gio, Gtk, Gdk, GdkPixbuf
|
||||
from gi.repository import GLib, Gio, Gtk, GdkPixbuf
|
||||
|
||||
from uberwriter import window
|
||||
from uberwriter.theme import Theme
|
||||
from uberwriter.settings import Settings
|
||||
from uberwriter.helpers import set_up_logging
|
||||
from uberwriter.preferences_dialog import PreferencesDialog
|
||||
|
@ -41,74 +38,9 @@ class Application(Gtk.Application):
|
|||
|
||||
Gtk.Application.do_startup(self)
|
||||
|
||||
# Actions
|
||||
self.settings.connect("changed", self.on_settings_changed)
|
||||
|
||||
action = Gio.SimpleAction.new("help", None)
|
||||
action.connect("activate", self.on_help)
|
||||
self.add_action(action)
|
||||
|
||||
action = Gio.SimpleAction.new("shortcuts", None)
|
||||
action.connect("activate", self.on_shortcuts)
|
||||
self.add_action(action)
|
||||
|
||||
action = Gio.SimpleAction.new("about", None)
|
||||
action.connect("activate", self.on_about)
|
||||
self.add_action(action)
|
||||
|
||||
action = Gio.SimpleAction.new("quit", None)
|
||||
action.connect("activate", self.on_quit)
|
||||
self.add_action(action)
|
||||
|
||||
set_dark_mode = self.settings.get_value("dark-mode")
|
||||
action = Gio.SimpleAction.new_stateful("dark_mode",
|
||||
None,
|
||||
GLib.Variant.new_boolean(set_dark_mode))
|
||||
action.connect("change-state", self.on_dark_mode)
|
||||
self.add_action(action)
|
||||
|
||||
action = Gio.SimpleAction.new_stateful("focus_mode",
|
||||
None,
|
||||
GLib.Variant.new_boolean(False))
|
||||
action.connect("change-state", self.on_focus_mode)
|
||||
self.add_action(action)
|
||||
|
||||
action = Gio.SimpleAction.new_stateful("hemingway_mode",
|
||||
None,
|
||||
GLib.Variant.new_boolean(False))
|
||||
action.connect("change-state", self.on_hemingway_mode)
|
||||
self.add_action(action)
|
||||
|
||||
action = Gio.SimpleAction.new_stateful("fullscreen",
|
||||
None,
|
||||
GLib.Variant.new_boolean(False))
|
||||
action.connect("change-state", self.on_fullscreen)
|
||||
self.add_action(action)
|
||||
|
||||
action = Gio.SimpleAction.new_stateful("preview",
|
||||
None,
|
||||
GLib.Variant.new_boolean(False))
|
||||
action.connect("change-state", self.on_preview)
|
||||
self.add_action(action)
|
||||
|
||||
action = Gio.SimpleAction.new("search", None)
|
||||
action.connect("activate", self.on_search)
|
||||
self.add_action(action)
|
||||
|
||||
set_spellcheck = self.settings.get_value("spellcheck")
|
||||
action = Gio.SimpleAction.new_stateful("spellcheck",
|
||||
None,
|
||||
GLib.Variant.new_boolean(set_spellcheck))
|
||||
action.connect("change-state", self.on_spellcheck)
|
||||
self.add_action(action)
|
||||
|
||||
set_gradient_overlay = self.settings.get_value("gradient-overlay")
|
||||
action = Gio.SimpleAction.new_stateful("draw_gradient",
|
||||
None,
|
||||
GLib.Variant.new_boolean(set_gradient_overlay))
|
||||
action.connect("change-state", self.on_draw_gradient)
|
||||
self.add_action(action)
|
||||
|
||||
# Menu Actions
|
||||
# Header bar
|
||||
|
||||
action = Gio.SimpleAction.new("new", None)
|
||||
action.connect("activate", self.on_new)
|
||||
|
@ -122,14 +54,36 @@ class Application(Gtk.Application):
|
|||
action.connect("activate", self.on_open_recent)
|
||||
self.add_action(action)
|
||||
|
||||
action = Gio.SimpleAction.new("open_examples", None)
|
||||
action.connect("activate", self.on_example)
|
||||
self.add_action(action)
|
||||
|
||||
action = Gio.SimpleAction.new("save", None)
|
||||
action.connect("activate", self.on_save)
|
||||
self.add_action(action)
|
||||
|
||||
action = Gio.SimpleAction.new("search", None)
|
||||
action.connect("activate", self.on_search)
|
||||
self.add_action(action)
|
||||
|
||||
# App Menu
|
||||
|
||||
action = Gio.SimpleAction.new_stateful(
|
||||
"focus_mode", None, GLib.Variant.new_boolean(False))
|
||||
action.connect("change-state", self.on_focus_mode)
|
||||
self.add_action(action)
|
||||
|
||||
action = Gio.SimpleAction.new_stateful(
|
||||
"hemingway_mode", None, GLib.Variant.new_boolean(False))
|
||||
action.connect("change-state", self.on_hemingway_mode)
|
||||
self.add_action(action)
|
||||
|
||||
action = Gio.SimpleAction.new_stateful(
|
||||
"preview", None, GLib.Variant.new_boolean(False))
|
||||
action.connect("change-state", self.on_preview)
|
||||
self.add_action(action)
|
||||
|
||||
action = Gio.SimpleAction.new_stateful(
|
||||
"fullscreen", None, GLib.Variant.new_boolean(False))
|
||||
action.connect("change-state", self.on_fullscreen)
|
||||
self.add_action(action)
|
||||
|
||||
action = Gio.SimpleAction.new("save_as", None)
|
||||
action.connect("activate", self.on_save_as)
|
||||
self.add_action(action)
|
||||
|
@ -138,17 +92,41 @@ class Application(Gtk.Application):
|
|||
action.connect("activate", self.on_export)
|
||||
self.add_action(action)
|
||||
|
||||
action = Gio.SimpleAction.new("HTML_copy", None)
|
||||
action.connect("activate", self.on_html_copy)
|
||||
action = Gio.SimpleAction.new("copy_html", None)
|
||||
action.connect("activate", self.on_copy_html)
|
||||
self.add_action(action)
|
||||
|
||||
action = Gio.SimpleAction.new("preferences", None)
|
||||
action.connect("activate", self.on_preferences)
|
||||
self.add_action(action)
|
||||
|
||||
action = Gio.SimpleAction.new("shortcuts", None)
|
||||
action.connect("activate", self.on_shortcuts)
|
||||
self.add_action(action)
|
||||
|
||||
action = Gio.SimpleAction.new("open_tutorial", None)
|
||||
action.connect("activate", self.on_open_tutorial)
|
||||
self.add_action(action)
|
||||
|
||||
action = Gio.SimpleAction.new("about", None)
|
||||
action.connect("activate", self.on_about)
|
||||
self.add_action(action)
|
||||
|
||||
action = Gio.SimpleAction.new("quit", None)
|
||||
action.connect("activate", self.on_quit)
|
||||
self.add_action(action)
|
||||
|
||||
# Stats Menu
|
||||
|
||||
stat_default = self.settings.get_string("stat-default")
|
||||
action = Gio.SimpleAction.new_stateful(
|
||||
"stat_default", GLib.VariantType.new('s'), GLib.Variant.new_string(stat_default))
|
||||
action.connect("activate", self.on_stat_default)
|
||||
self.add_action(action)
|
||||
|
||||
# Shortcuts
|
||||
|
||||
# TODO: be aware that a couple of shortcuts are defined in _gtk_base.css
|
||||
# TODO: be aware that a couple of shortcuts are defined in base.css
|
||||
|
||||
self.set_accels_for_action("app.focus_mode", ["<Ctl>d"])
|
||||
self.set_accels_for_action("app.hemingway_mode", ["<Ctl>t"])
|
||||
|
@ -163,8 +141,6 @@ class Application(Gtk.Application):
|
|||
self.set_accels_for_action("app.save_as", ["<Ctl><shift>s"])
|
||||
self.set_accels_for_action("app.quit", ["<Ctl>w", "<Ctl>q"])
|
||||
|
||||
self.apply_current_theme()
|
||||
|
||||
def do_activate(self, *args, **kwargs):
|
||||
# We only allow a single window and raise any existing ones
|
||||
if not self.window:
|
||||
|
@ -174,8 +150,6 @@ class Application(Gtk.Application):
|
|||
self.window = window.Window(self)
|
||||
if self.args:
|
||||
self.window.load_file(self.args[0])
|
||||
if self.options.experimental_features:
|
||||
self.window.use_experimental_features(True)
|
||||
|
||||
self.window.present()
|
||||
|
||||
|
@ -187,8 +161,7 @@ class Application(Gtk.Application):
|
|||
help=_("Show debug messages (-vv debugs uberwriter also)"))
|
||||
parser.add_argument(
|
||||
"-e", "--experimental-features", help=_("Use experimental features"),
|
||||
action='store_true'
|
||||
)
|
||||
action='store_true')
|
||||
(self.options, self.args) = parser.parse_known_args()
|
||||
|
||||
set_up_logging(self.options)
|
||||
|
@ -196,90 +169,17 @@ class Application(Gtk.Application):
|
|||
self.activate()
|
||||
return 0
|
||||
|
||||
def apply_current_theme(self):
|
||||
# get current theme
|
||||
theme = Theme.get_current()
|
||||
|
||||
# set theme variant (dark/light)
|
||||
Gtk.Settings.get_default().set_property(
|
||||
"gtk-application-prefer-dark-theme",
|
||||
GLib.Variant("b", theme.is_dark))
|
||||
|
||||
# set theme css
|
||||
style_provider = Gtk.CssProvider()
|
||||
style_provider.load_from_path(theme.gtk_css_path)
|
||||
Gtk.StyleContext.add_provider_for_screen(
|
||||
Gdk.Screen.get_default(), style_provider,
|
||||
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
|
||||
)
|
||||
|
||||
def on_about(self, _action, _param):
|
||||
builder = get_builder('About')
|
||||
about_dialog = builder.get_object("AboutDialog")
|
||||
about_dialog.set_transient_for(self.window)
|
||||
|
||||
logo_file = get_media_path("de.wolfvollprecht.UberWriter.svg")
|
||||
logo = GdkPixbuf.Pixbuf.new_from_file(logo_file)
|
||||
|
||||
about_dialog.set_logo(logo)
|
||||
|
||||
about_dialog.present()
|
||||
|
||||
def on_help(self, _action, _param):
|
||||
"""open pandoc markdown web
|
||||
"""
|
||||
webbrowser.open(
|
||||
"http://johnmacfarlane.net/pandoc/README.html#pandocs-markdown")
|
||||
|
||||
def on_shortcuts(self, _action, _param):
|
||||
builder = get_builder('Shortcuts')
|
||||
builder.get_object("shortcuts").set_transient_for(self.window)
|
||||
builder.get_object("shortcuts").show()
|
||||
|
||||
def on_dark_mode(self, action, value):
|
||||
action.set_state(value)
|
||||
self.settings.set_value("dark-mode", GLib.Variant("b", value))
|
||||
|
||||
# this changes the headerbar theme accordingly
|
||||
self.apply_current_theme()
|
||||
|
||||
# adjust window for theme
|
||||
self.window.apply_current_theme()
|
||||
|
||||
def on_focus_mode(self, action, value):
|
||||
action.set_state(value)
|
||||
self.window.set_focus_mode(value)
|
||||
|
||||
def on_hemingway_mode(self, action, value):
|
||||
action.set_state(value)
|
||||
self.window.set_hemingway_mode(value)
|
||||
|
||||
def on_fullscreen(self, action, value):
|
||||
action.set_state(value)
|
||||
self.window.set_fullscreen(value)
|
||||
|
||||
def on_preview(self, action, value):
|
||||
action.set_state(value)
|
||||
self.window.toggle_preview(value)
|
||||
|
||||
def on_search(self, _action, _value):
|
||||
self.window.open_search_and_replace()
|
||||
|
||||
def on_spellcheck(self, action, value):
|
||||
action.set_state(value)
|
||||
self.settings.set_value("spellcheck",
|
||||
GLib.Variant("b", value))
|
||||
self.window.toggle_spellcheck(value)
|
||||
|
||||
def on_draw_gradient(self, action, value):
|
||||
action.set_state(value)
|
||||
self.settings.set_value("gradient-overlay",
|
||||
GLib.Variant("b", value))
|
||||
if value:
|
||||
self.window.overlay = self.window.scrolled_window.connect_after(
|
||||
"draw", self.window.draw_gradient)
|
||||
else:
|
||||
self.window.scrolled_window.disconnect(self.window.overlay)
|
||||
def on_settings_changed(self, settings, key):
|
||||
if key == "dark-mode-auto" or key == "dark-mode":
|
||||
self.window.apply_current_theme()
|
||||
elif key == "spellcheck":
|
||||
self.window.toggle_spellcheck(settings.get_value(key))
|
||||
elif key == "gradient-overlay":
|
||||
self.window.toggle_gradient_overlay(settings.get_value(key))
|
||||
elif key == "input-format":
|
||||
self.window.reload_preview()
|
||||
elif key == "stat-default":
|
||||
self.window.update_default_stat()
|
||||
|
||||
def on_new(self, _action, _value):
|
||||
self.window.new_document()
|
||||
|
@ -290,30 +190,66 @@ class Application(Gtk.Application):
|
|||
def on_open_recent(self, file):
|
||||
self.window.load_file(file.get_current_uri())
|
||||
|
||||
def on_example(self, _action, _value):
|
||||
self.window.open_uberwriter_markdown()
|
||||
|
||||
def on_save(self, _action, _value):
|
||||
self.window.save_document()
|
||||
|
||||
def on_search(self, _action, _value):
|
||||
self.window.open_search_and_replace()
|
||||
|
||||
def on_focus_mode(self, action, value):
|
||||
action.set_state(value)
|
||||
self.window.set_focus_mode(value)
|
||||
|
||||
def on_hemingway_mode(self, action, value):
|
||||
action.set_state(value)
|
||||
self.window.set_hemingway_mode(value)
|
||||
|
||||
def on_preview(self, action, value):
|
||||
action.set_state(value)
|
||||
self.window.toggle_preview(value)
|
||||
|
||||
def on_fullscreen(self, action, value):
|
||||
action.set_state(value)
|
||||
self.window.set_fullscreen(value)
|
||||
|
||||
def on_save_as(self, _action, _value):
|
||||
self.window.save_document_as()
|
||||
|
||||
def on_export(self, _action, _value):
|
||||
self.window.open_advanced_export()
|
||||
|
||||
def on_html_copy(self, _action, _value):
|
||||
def on_copy_html(self, _action, _value):
|
||||
self.window.copy_html_to_clipboard()
|
||||
|
||||
def on_preferences(self, _action, _value):
|
||||
preferences_window = PreferencesDialog()
|
||||
preferences_window.set_application(self)
|
||||
preferences_window.set_transient_for(self.window)
|
||||
preferences_window.show()
|
||||
PreferencesDialog(self.settings).show(self.window)
|
||||
|
||||
def on_shortcuts(self, _action, _param):
|
||||
builder = get_builder('Shortcuts')
|
||||
builder.get_object("shortcuts").set_transient_for(self.window)
|
||||
builder.get_object("shortcuts").show()
|
||||
|
||||
def on_open_tutorial(self, _action, _value):
|
||||
self.window.open_uberwriter_markdown()
|
||||
|
||||
def on_about(self, _action, _param):
|
||||
builder = get_builder('About')
|
||||
about_dialog = builder.get_object("AboutDialog")
|
||||
about_dialog.set_transient_for(self.window)
|
||||
|
||||
logo_file = get_media_path("de.wolfvollprecht.UberWriter.svg")
|
||||
logo = GdkPixbuf.Pixbuf.new_from_file(logo_file)
|
||||
|
||||
about_dialog.set_logo(logo)
|
||||
about_dialog.present()
|
||||
|
||||
def on_quit(self, _action, _param):
|
||||
self.quit()
|
||||
|
||||
def on_stat_default(self, action, value):
|
||||
action.set_state(value)
|
||||
self.settings.set_string("stat-default", value.get_string())
|
||||
|
||||
# ~ if __name__ == "__main__":
|
||||
# ~ app = Application()
|
||||
# ~ app.run(sys.argv)
|
||||
|
|
|
@ -53,7 +53,6 @@ def get_data_path():
|
|||
"""
|
||||
|
||||
# Get pathname absolute or relative.
|
||||
# TODO: Abstract this (the old env IN_FLATPAK)
|
||||
if os.path.isfile("/.flatpak-info"):
|
||||
return '/app/share/uberwriter/data/'
|
||||
|
|
@ -17,14 +17,11 @@
|
|||
"""
|
||||
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import logging
|
||||
# import gettext
|
||||
|
||||
import os
|
||||
from gettext import gettext as _
|
||||
import gi
|
||||
|
||||
import gi
|
||||
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
|
@ -35,6 +32,7 @@ from uberwriter.helpers import get_builder
|
|||
|
||||
LOGGER = logging.getLogger('uberwriter')
|
||||
|
||||
|
||||
class Export:
|
||||
"""
|
||||
Manages all the export operations and dialogs
|
||||
|
@ -42,6 +40,114 @@ class Export:
|
|||
|
||||
__gtype_name__ = "export_dialog"
|
||||
|
||||
formats = [
|
||||
{
|
||||
"name": "LaTeX (pdf)",
|
||||
"ext": "pdf",
|
||||
"to": "pdf"
|
||||
},
|
||||
{
|
||||
"name": "LaTeX Beamer Slideshow (pdf)",
|
||||
"ext": "pdf",
|
||||
"to": "beamer"
|
||||
},
|
||||
{
|
||||
"name": "LaTeX (tex)",
|
||||
"ext": "tex",
|
||||
"to": "latex"
|
||||
},
|
||||
{
|
||||
"name": "LaTeX Beamer Slideshow (tex)",
|
||||
"ext": "tex",
|
||||
"to": "beamer"
|
||||
},
|
||||
{
|
||||
"name": "ConTeXt",
|
||||
"ext": "tex",
|
||||
"to": "context"
|
||||
},
|
||||
{
|
||||
"name": "HTML",
|
||||
"ext": "html",
|
||||
"to": "html"
|
||||
},
|
||||
{
|
||||
"name": "HTML and JavaScript Slideshow (Slidy)",
|
||||
"ext": "html",
|
||||
"to": "slidy"
|
||||
},
|
||||
{
|
||||
"name": "HTML and JavaScript Slideshow (Slideous)",
|
||||
"ext": "html",
|
||||
"to": "slideous"
|
||||
},
|
||||
{
|
||||
"name": "HTML5 and JavaScript Slideshow (DZSlides)",
|
||||
"ext": "html",
|
||||
"to": "dzslides"
|
||||
},
|
||||
{
|
||||
"name": "HTML5 and JavaScript Slideshow (reveal.js)",
|
||||
"ext": "html",
|
||||
"to": "revealjs"
|
||||
},
|
||||
{
|
||||
"name": "HTML and JavaScript Slideshow (S5)",
|
||||
"ext": "html",
|
||||
"to": "s5"
|
||||
},
|
||||
{
|
||||
"name": "Textile",
|
||||
"ext": "txt",
|
||||
"to": "textile"
|
||||
},
|
||||
{
|
||||
"name": "reStructuredText",
|
||||
"ext": "txt",
|
||||
"to": "rst"
|
||||
},
|
||||
{
|
||||
"name": "MediaWiki Markup",
|
||||
"ext": "txt",
|
||||
"to": "mediawiki"
|
||||
},
|
||||
{
|
||||
"name": "OpenDocument (xml)",
|
||||
"ext": "xml",
|
||||
"to": "opendocument"
|
||||
},
|
||||
{
|
||||
"name": "OpenDocument (texi)",
|
||||
"ext": "texi",
|
||||
"to": "texinfo"
|
||||
},
|
||||
{
|
||||
"name": "OpenOffice Text Document",
|
||||
"ext": "odt",
|
||||
"to": "odt"
|
||||
},
|
||||
{
|
||||
"name": "Microsoft Word (docx)",
|
||||
"ext": "docx",
|
||||
"to": "docx"
|
||||
},
|
||||
{
|
||||
"name": "Rich Text Format",
|
||||
"ext": "rtf",
|
||||
"to": "rtf"
|
||||
},
|
||||
{
|
||||
"name": "Groff Man",
|
||||
"ext": "man",
|
||||
"to": "man"
|
||||
},
|
||||
{
|
||||
"name": "EPUB v3",
|
||||
"ext": "epub",
|
||||
"to": "epub"
|
||||
}
|
||||
]
|
||||
|
||||
def __init__(self, filename):
|
||||
"""Set up the about dialog"""
|
||||
self.builder = get_builder('Export')
|
||||
|
@ -52,8 +158,8 @@ class Export:
|
|||
stack_pdf_disabled = self.builder.get_object("pdf_disabled")
|
||||
filename = filename or _("Untitled document.md")
|
||||
|
||||
self.filechoosers = {export_format:self.stack.get_child_by_name(export_format)\
|
||||
for export_format in ["pdf", "html", "odt", "advanced"]}
|
||||
self.filechoosers = {export_format: self.stack.get_child_by_name(export_format)
|
||||
for export_format in ["pdf", "html", "advanced"]}
|
||||
for export_format, filechooser in self.filechoosers.items():
|
||||
filechooser.set_do_overwrite_confirmation(True)
|
||||
filechooser.set_current_folder(os.path.dirname(filename))
|
||||
|
@ -75,9 +181,12 @@ class Export:
|
|||
|
||||
self.builder.get_object("highlight_style").set_active(0)
|
||||
|
||||
self.builder.get_object("css_filechooser").set_uri(
|
||||
helpers.path_to_file(Theme.get_current().web_css_path))
|
||||
|
||||
format_store = Gtk.ListStore(int, str)
|
||||
for fmt_id in self.formats_dict:
|
||||
format_store.append([fmt_id, self.formats_dict[fmt_id]["name"]])
|
||||
for i, fmt in enumerate(self.formats):
|
||||
format_store.append([i, fmt["name"]])
|
||||
self.format_field = self.builder.get_object('choose_format')
|
||||
self.format_field.set_model(format_store)
|
||||
|
||||
|
@ -86,171 +195,56 @@ class Export:
|
|||
self.format_field.add_attribute(format_renderer, "text", 1)
|
||||
self.format_field.set_active(0)
|
||||
|
||||
formats_dict = {
|
||||
1: {
|
||||
"name": "LaTeX Source",
|
||||
"ext": "tex",
|
||||
"to": "latex"
|
||||
},
|
||||
2: {
|
||||
"name": "LaTeX PDF",
|
||||
"ext": "pdf",
|
||||
"to": "pdf"
|
||||
},
|
||||
3: {
|
||||
"name": "LaTeX beamer slide show Source .tex",
|
||||
"ext": "tex",
|
||||
"to": "beamer"
|
||||
},
|
||||
4: {
|
||||
"name": "LaTeX beamer slide show PDF",
|
||||
"ext": "pdf",
|
||||
"to": "beamer"
|
||||
},
|
||||
5: {
|
||||
"name": "HTML",
|
||||
"ext": "html",
|
||||
"to": "html"
|
||||
},
|
||||
6: {
|
||||
"name": "Textile",
|
||||
"ext": "txt",
|
||||
"to": "textile"
|
||||
},
|
||||
7: {
|
||||
"name": "OpenOffice text document",
|
||||
"ext": "odt",
|
||||
"to": "odt"
|
||||
},
|
||||
8: {
|
||||
"name": "Word docx",
|
||||
"ext": "docx",
|
||||
"to": "docx"
|
||||
},
|
||||
9: {
|
||||
"name": "reStructuredText txt",
|
||||
"ext": "txt",
|
||||
"to": "rst"
|
||||
},
|
||||
10: {
|
||||
"name": "ConTeXt tex",
|
||||
"ext": "tex",
|
||||
"to": "context"
|
||||
},
|
||||
11: {
|
||||
"name": "groff man",
|
||||
"ext": "man",
|
||||
"to": "man"
|
||||
},
|
||||
12: {
|
||||
"name": "MediaWiki markup",
|
||||
"ext": "txt",
|
||||
"to": "mediawiki"
|
||||
},
|
||||
13: {
|
||||
"name": "OpenDocument XML",
|
||||
"ext": "xml",
|
||||
"to": "opendocument"
|
||||
},
|
||||
14: {
|
||||
"name": "OpenDocument XML",
|
||||
"ext": "texi",
|
||||
"to": "texinfo"
|
||||
},
|
||||
15: {
|
||||
"name": "Slidy HTML and javascript slide show",
|
||||
"ext": "html",
|
||||
"to": "slidy"
|
||||
},
|
||||
16: {
|
||||
"name": "Slideous HTML and javascript slide show",
|
||||
"ext": "html",
|
||||
"to": "slideous"
|
||||
},
|
||||
17: {
|
||||
"name": "HTML5 + javascript slide show",
|
||||
"ext": "html",
|
||||
"to": "dzslides"
|
||||
},
|
||||
18: {
|
||||
"name": "S5 HTML and javascript slide show",
|
||||
"ext": "html",
|
||||
"to": "s5"
|
||||
},
|
||||
19: {
|
||||
"name": "EPub electronic publication",
|
||||
"ext": "epub",
|
||||
"to": "epub"
|
||||
},
|
||||
20: {
|
||||
"name": "RTF Rich Text Format",
|
||||
"ext": "rtf",
|
||||
"to": "rtf"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
def export(self, text=""):
|
||||
"""Export to pdf, html or odt the given text
|
||||
"""Export the given text using the specified format.
|
||||
For advanced export, this includes special flags for the enabled options.
|
||||
|
||||
Keyword Arguments:
|
||||
text {str} -- Text to export (default: {""})
|
||||
"""
|
||||
|
||||
export_format = self.stack.get_visible_child_name()
|
||||
export_type = self.stack.get_visible_child_name()
|
||||
args = []
|
||||
if export_type == "advanced":
|
||||
filename = self.adv_export_name.get_text()
|
||||
output_dir = os.path.abspath(self.filechoosers["advanced"].get_current_folder())
|
||||
basename = os.path.basename(filename)
|
||||
|
||||
fmt = self.formats[self.format_field.get_active()]
|
||||
to = fmt["to"]
|
||||
ext = fmt["ext"]
|
||||
|
||||
if self.builder.get_object("html5").get_active() and to == "html":
|
||||
to = "html5"
|
||||
if self.builder.get_object("smart").get_active():
|
||||
to += "+smart"
|
||||
|
||||
args.extend(self.get_advanced_arguments())
|
||||
|
||||
if export_format == "advanced":
|
||||
self.advanced_export(text)
|
||||
else:
|
||||
filename = self.filechoosers[export_format].get_filename()
|
||||
if filename.endswith("." + export_format):
|
||||
filename = filename[:-len(export_format)-1]
|
||||
|
||||
filename = self.filechoosers[export_type].get_filename()
|
||||
if filename.endswith("." + export_type):
|
||||
filename = filename[:-len(export_type)-1]
|
||||
output_dir = os.path.abspath(os.path.join(filename, os.path.pardir))
|
||||
basename = os.path.basename(filename)
|
||||
|
||||
args = ['pandoc', '--from=markdown', '-s']
|
||||
to = export_type
|
||||
ext = export_type
|
||||
|
||||
if export_format == "pdf":
|
||||
args.append("-o%s.pdf" % basename)
|
||||
|
||||
elif export_format == "odt":
|
||||
args.append("-o%s.odt" % basename)
|
||||
|
||||
elif export_format == "html":
|
||||
css = Theme.ADWAITA.get_gtk_css_file()
|
||||
relativize = helpers.get_script_path('relative_to_absolute.lua')
|
||||
task_list = helpers.get_script_path('task-list.lua')
|
||||
args.append("-c%s" % css)
|
||||
args.append("-o%s.html" % basename)
|
||||
if export_type == "html":
|
||||
to = "html5"
|
||||
args.append("--standalone")
|
||||
args.append("--css=%s" % Theme.get_current().web_css_path)
|
||||
args.append("--mathjax")
|
||||
args.append("--lua-filter=" + relativize)
|
||||
args.append("--lua-filter=" + task_list)
|
||||
args.append("--lua-filter=%s" % helpers.get_script_path('relative_to_absolute.lua'))
|
||||
args.append("--lua-filter=%s" % helpers.get_script_path('task-list.lua'))
|
||||
|
||||
proc = subprocess.Popen(args, stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE, cwd=output_dir)
|
||||
_ = proc.communicate(text)[0]
|
||||
helpers.pandoc_convert(
|
||||
text, to=to, args=args,
|
||||
outputfile="%s/%s.%s" % (output_dir, basename, ext))
|
||||
|
||||
def advanced_export(self, text=""):
|
||||
"""Export the given text to special formats with the enabled flags
|
||||
|
||||
Keyword Arguments:
|
||||
text {str} -- The text to export (default: {""})
|
||||
"""
|
||||
|
||||
filename = self.adv_export_name.get_text()
|
||||
output_dir = os.path.abspath(self.filechoosers["advanced"].get_current_folder())
|
||||
basename = os.path.basename(filename)
|
||||
args = self.set_arguments(basename)
|
||||
|
||||
LOGGER.info(args)
|
||||
|
||||
proc = subprocess.Popen(args, stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE, cwd=output_dir)
|
||||
_ = proc.communicate(text)[0]
|
||||
|
||||
def set_arguments(self, basename):
|
||||
"""Retrieve a list of the selected arguments
|
||||
def get_advanced_arguments(self):
|
||||
"""Retrieve a list of the selected advanced arguments
|
||||
|
||||
For most of the advanced option checkboxes, returns a list
|
||||
of the related pandoc flags
|
||||
|
@ -264,78 +258,49 @@ class Export:
|
|||
|
||||
highlight_style = self.builder.get_object("highlight_style").get_active_text()
|
||||
|
||||
conditions_dict = {
|
||||
1: {
|
||||
conditions = [
|
||||
{
|
||||
"condition": self.builder.get_object("toc").get_active(),
|
||||
"yes": "--toc",
|
||||
"no": None
|
||||
},
|
||||
2: {
|
||||
{
|
||||
"condition": self.builder.get_object("highlight").get_active(),
|
||||
"yes": "--highlight-style=%s" % highlight_style,
|
||||
"no": "--no-highlight"
|
||||
},
|
||||
3: {
|
||||
{
|
||||
"condition": self.builder.get_object("standalone").get_active(),
|
||||
"yes": "--standalone",
|
||||
"no": None
|
||||
},
|
||||
4: {
|
||||
{
|
||||
"condition": self.builder.get_object("number_sections").get_active(),
|
||||
"yes": "--number-sections",
|
||||
"no": None
|
||||
},
|
||||
5: {
|
||||
{
|
||||
"condition": self.builder.get_object("strict").get_active(),
|
||||
"yes": "--strict",
|
||||
"no": None
|
||||
},
|
||||
6: {
|
||||
{
|
||||
"condition": self.builder.get_object("incremental").get_active(),
|
||||
"yes": "--incremental",
|
||||
"no": None
|
||||
},
|
||||
7: {
|
||||
{
|
||||
"condition": self.builder.get_object("self_contained").get_active(),
|
||||
"yes": "--self-contained",
|
||||
"no": None
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
tree_iter = self.format_field.get_active_iter()
|
||||
if tree_iter is not None:
|
||||
model = self.format_field.get_model()
|
||||
row_id, _ = model[tree_iter][:2]
|
||||
args = []
|
||||
|
||||
fmt = self.formats_dict[row_id]
|
||||
args.extend([c["yes"] if c["condition"] else c["no"] for c in conditions])
|
||||
|
||||
args = ['pandoc', '--from=markdown']
|
||||
|
||||
extension = "--to=%s" % fmt["to"]
|
||||
|
||||
if basename.endswith("." + fmt["ext"]):
|
||||
output_file = "--output=%s" % basename
|
||||
else:
|
||||
output_file = "--output=%s.%s" % (basename, fmt["ext"])
|
||||
|
||||
args.extend([conditions_dict[c_id]["yes"]\
|
||||
if conditions_dict[c_id]["condition"]\
|
||||
else conditions_dict[c_id]["no"]\
|
||||
for c_id in conditions_dict])
|
||||
|
||||
args = list(filter(None, args))
|
||||
|
||||
if self.builder.get_object("html5").get_active():
|
||||
if fmt["to"] == "html":
|
||||
extension = "--to=%s" % "html5"
|
||||
|
||||
if self.builder.get_object("smart").get_active():
|
||||
extension += '+smart'
|
||||
else:
|
||||
extension += '-smart'
|
||||
|
||||
if fmt["to"] != "pdf":
|
||||
args.append(extension)
|
||||
args = list(filter(lambda arg: arg is not None, args))
|
||||
|
||||
css_uri = self.builder.get_object("css_filechooser").get_uri()
|
||||
if css_uri:
|
||||
|
@ -349,8 +314,6 @@ class Export:
|
|||
bib_uri = bib_uri[7:]
|
||||
args.append("--bibliography=%s" % bib_uri)
|
||||
|
||||
args.append(output_file)
|
||||
|
||||
return args
|
||||
|
||||
def allow_export(self, widget, data, signal):
|
||||
|
|
|
@ -17,18 +17,14 @@
|
|||
|
||||
from gettext import gettext as _
|
||||
|
||||
from uberwriter.markup_buffer import MarkupBuffer
|
||||
|
||||
|
||||
class FormatShortcuts():
|
||||
"""Manage the insertion of formatting for insert them using shortcuts
|
||||
"""
|
||||
|
||||
|
||||
def __init__(self, textbuffer, texteditor):
|
||||
self.text_buffer = textbuffer
|
||||
self.text_editor = texteditor
|
||||
self.regex = MarkupBuffer.regex
|
||||
|
||||
def rule(self):
|
||||
"""insert ruler at cursor
|
||||
|
|
|
@ -1,53 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2012, Maximilian Köhl <linuxmaxi@googlemail.com>
|
||||
# Copyright (C) 2012, Carlos Jenkins <carlos@jenkins.co.cr>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
__version__ = '4.0.5'
|
||||
__project__ = 'Python GTK Spellcheck'
|
||||
__short_name__ = 'pygtkspellcheck'
|
||||
__authors__ = 'Maximilian Köhl & Carlos Jenkins'
|
||||
__emails__ = 'linuxmaxi@googlemail.com & carlos@jenkins.co.cr'
|
||||
__website__ = 'http://koehlma.github.com/projects/pygtkspellcheck.html'
|
||||
__download_url__ = 'https://github.com/koehlma/pygtkspellcheck/tarball/master'
|
||||
__source__ = 'https://github.com/koehlma/pygtkspellcheck/'
|
||||
__vcs__ = 'git://github.com/koehlma/pygtkspellcheck.git'
|
||||
__copyright__ = '2012, Maximilian Köhl & Carlos Jenkins'
|
||||
__desc_short__ = ('a simple but quite powerful Python spell checking library '
|
||||
'for GtkTextViews based on Enchant')
|
||||
__desc_long__ = ('A simple but quite powerful spellchecking library written in '
|
||||
'pure Python for Gtk based on Enchant. It supports PyGObject '
|
||||
'as well as PyGtk for Python 2 and 3 with automatic switching '
|
||||
'and binding detection. For automatic translation of the user '
|
||||
'interface it can use Gedit’s translation files.')
|
||||
|
||||
__metadata__ = {'__version__' : __version__,
|
||||
'__project__' : __project__,
|
||||
'__short_name__' : __short_name__,
|
||||
'__authors__' : __authors__,
|
||||
'__emails__' : __emails__,
|
||||
'__website__' : __website__,
|
||||
'__download_url__' : __download_url__,
|
||||
'__source__' : __source__,
|
||||
'__vcs__' : __vcs__,
|
||||
'__copyright__' : __copyright__,
|
||||
'__desc_short__' : __desc_short__,
|
||||
'__desc_long__' : __desc_long__}
|
||||
|
||||
from .spellcheck import (SpellChecker, NoDictionariesFound,
|
||||
NoGtkBindingFound)
|
|
@ -1,294 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2012, Carlos Jenkins <carlos@jenkins.co.cr>
|
||||
# Copyright (C) 2012-2016, Maximilian Köhl <mail@koehlma.de>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
This module extracts the .dic and .aff (Hunspell) dictionaries from any given
|
||||
.oxt extension.
|
||||
|
||||
Extensions could be found at:
|
||||
|
||||
http://extensions.services.openoffice.org/dictionary
|
||||
"""
|
||||
|
||||
import functools
|
||||
import gettext
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import warnings
|
||||
import xml.dom.minidom
|
||||
import xml.parsers.expat
|
||||
import zipfile
|
||||
|
||||
# enable deprecation warnings
|
||||
warnings.simplefilter('always', DeprecationWarning)
|
||||
|
||||
# public objects
|
||||
__all__ = ['extract_oxt', 'batch_extract', 'BadXml', 'BadExtensionFile',
|
||||
'ExtractPathIsNoDirectory', 'BATCH_SUCCESS', 'BATCH_ERROR',
|
||||
'BATCH_WARNING']
|
||||
|
||||
# logger
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# translation
|
||||
locale_name = 'py{}gtkspellcheck'.format(sys.version_info.major)
|
||||
_ = gettext.translation(locale_name, fallback=True).gettext
|
||||
|
||||
class BadXml(Exception):
|
||||
"""
|
||||
The XML dictionary registry is not valid XML.
|
||||
"""
|
||||
|
||||
class BadExtensionFile(Exception):
|
||||
"""
|
||||
The extension has a wrong file format, should be a ZIP file.
|
||||
"""
|
||||
|
||||
class ExtractPathIsNoDirectory(Exception):
|
||||
"""
|
||||
The given `extract_path` is no directory.
|
||||
"""
|
||||
|
||||
|
||||
def find_dictionaries(registry):
|
||||
def oor_name(name, element):
|
||||
return element.attributes['oor:name'].value.lower() == name
|
||||
|
||||
def get_property(name, properties):
|
||||
property = list(filter(functools.partial(oor_name, name),
|
||||
properties))
|
||||
if property:
|
||||
return property[0].getElementsByTagName('value')[0]
|
||||
|
||||
result = []
|
||||
|
||||
# find all "node" elements which have "dictionaries" as "oor:name" attribute
|
||||
for dictionaries in filter(functools.partial(oor_name, 'dictionaries'),
|
||||
registry.getElementsByTagName('node')):
|
||||
# for all "node" elements in this dictionary nodes
|
||||
for dictionary in dictionaries.getElementsByTagName('node'):
|
||||
# get all "prop" elements
|
||||
properties = dictionary.getElementsByTagName('prop')
|
||||
# get the format property as text
|
||||
format = get_property('format', properties).firstChild.data.strip()
|
||||
if format and format == 'DICT_SPELL':
|
||||
# find the locations property
|
||||
locations = get_property('locations', properties)
|
||||
# if the location property is text:
|
||||
# %origin%/dictionary.aff %origin%/dictionary.dic
|
||||
if locations.firstChild.nodeType == xml.dom.Node.TEXT_NODE:
|
||||
locations = locations.firstChild.data
|
||||
locations = locations.replace('%origin%/', '').strip()
|
||||
result.append(locations.split())
|
||||
# otherwise:
|
||||
# <i>%origin%/dictionary.aff</i> <i>%origin%/dictionary.dic</i>
|
||||
else:
|
||||
locations = [item.firshChild.data.replace('%origin%/', '') \
|
||||
.strip() for item in
|
||||
locations.getElementsByTagName('it')]
|
||||
result.append(locations)
|
||||
|
||||
return result
|
||||
|
||||
def extract(filename, target, override=False):
|
||||
"""
|
||||
Extract Hunspell dictionaries out of LibreOffice ``.oxt`` extensions.
|
||||
|
||||
:param filename: path to the ``.oxt`` extension
|
||||
:param target: path to extract Hunspell dictionaries to
|
||||
:param override: override existing files in the target directory
|
||||
:rtype: list of the extracted dictionaries
|
||||
|
||||
This function extracts the Hunspell dictionaries (``.dic`` and ``.aff``
|
||||
files) from the given ``.oxt`` extension found to ``target``.
|
||||
|
||||
Extensions could be found at:
|
||||
|
||||
http://extensions.services.openoffice.org/dictionary
|
||||
"""
|
||||
# TODO 5.0: remove this function
|
||||
warnings.warn(('call to deprecated function "{}", '
|
||||
'moved to separate package "oxt_extract", '
|
||||
'will be removed in pygtkspellcheck 5.0').format(extract.__name__),
|
||||
category=DeprecationWarning)
|
||||
try:
|
||||
with zipfile.ZipFile(filename, 'r') as extension:
|
||||
files = extension.namelist()
|
||||
|
||||
registry = 'dictionaries.xcu'
|
||||
if not registry in files:
|
||||
for filename in files:
|
||||
if filename.lower().endswith(registry):
|
||||
registry = filename
|
||||
|
||||
if registry in files:
|
||||
registry = xml.dom.minidom.parse(extension.open(registry))
|
||||
dictionaries = find_dictionaries(registry)
|
||||
extracted = []
|
||||
for dictionary in dictionaries:
|
||||
for filename in dictionary:
|
||||
dict_file = os.path.join(target,
|
||||
os.path.basename(filename))
|
||||
if (not os.path.exists(dict_file)
|
||||
or (override and os.path.isfile(dict_file))):
|
||||
if filename in files:
|
||||
with open(dict_file, 'wb') as _target:
|
||||
with extension.open(filename, 'r') as _source:
|
||||
extracted.append(os.path.basename(filename))
|
||||
_target.write(_source.read())
|
||||
else:
|
||||
logger.warning('dictionary exists in registry '
|
||||
'but not in the extension zip')
|
||||
else:
|
||||
logging.warning(('dictionary file "{}" already exists '
|
||||
'and not overriding it'
|
||||
).format(dict_file))
|
||||
return extracted
|
||||
except zipfile.BadZipfile:
|
||||
raise BadExtensionFile('extension is not a valid ZIP file')
|
||||
except xml.parsers.expat.ExpatError:
|
||||
raise BadXml('dictionary registry is not valid XML')
|
||||
|
||||
BATCH_SUCCESS = 'success'
|
||||
BATCH_ERROR = 'error'
|
||||
BATCH_WARNING = 'warning'
|
||||
|
||||
def batch_extract(oxt_path, extract_path, override=False, move_path=None):
|
||||
"""
|
||||
Uncompress, read and install LibreOffice ``.oxt`` dictionaries extensions.
|
||||
|
||||
:param oxt_path: path to a directory containing the ``.oxt`` extensions
|
||||
:param extract_path: path to extract Hunspell dictionaries files to
|
||||
:param override: override already existing files
|
||||
:param move_path: optional path to move the ``.oxt`` files after processing
|
||||
:rtype: generator over all extensions, yielding result, extension name,
|
||||
error, extracted dictionaries and translated error message - result
|
||||
would be :const:`BATCH_SUCCESS` for success, :const:`BATCH_ERROR` if
|
||||
some error happened or :const:`BATCH_WARNING` which contain some warning
|
||||
messages instead of errors
|
||||
|
||||
This function extracts the Hunspell dictionaries (``.dic`` and ``.aff``
|
||||
files) from all the ``.oxt`` extensions found on ``oxt_path`` directory to
|
||||
the ``extract_path`` directory.
|
||||
|
||||
Extensions could be found at:
|
||||
|
||||
http://extensions.services.openoffice.org/dictionary
|
||||
|
||||
In detail, this functions does the following:
|
||||
|
||||
1. find all the ``.oxt`` extension files within ``oxt_path``
|
||||
2. open (unzip) each extension
|
||||
3. find the dictionary definition file within (*dictionaries.xcu*)
|
||||
4. parse the dictionary definition file and locate the dictionaries files
|
||||
5. uncompress those files to ``extract_path``
|
||||
|
||||
|
||||
By default file overriding is disabled, set ``override`` parameter to True
|
||||
if you want to enable it. As additional option, each processed extension can
|
||||
be moved to ``move_path``.
|
||||
|
||||
Example::
|
||||
|
||||
for result, name, error, dictionaries, message in oxt_extract.batch_extract(...):
|
||||
if result == oxt_extract.BATCH_SUCCESS:
|
||||
print('successfully extracted extension "{}"'.format(name))
|
||||
elif result == oxt_extract.BATCH_ERROR:
|
||||
print('could not extract extension "{}"'.format(name))
|
||||
print(message)
|
||||
print('error {}'.format(error))
|
||||
elif result == oxt_extract.BATCH_WARNING:
|
||||
print('warning during processing extension "{}"'.format(name))
|
||||
print(message)
|
||||
print(error)
|
||||
|
||||
"""
|
||||
|
||||
# TODO 5.0: remove this function
|
||||
warnings.warn(('call to deprecated function "{}", '
|
||||
'moved to separate package "oxt_extract", '
|
||||
'will be removed in pygtkspellcheck 5.0').format(extract.__name__),
|
||||
category=DeprecationWarning)
|
||||
|
||||
# get the real, absolute and normalized path
|
||||
oxt_path = os.path.normpath(os.path.abspath(os.path.realpath(oxt_path)))
|
||||
|
||||
# check that the input directory exists
|
||||
if not os.path.isdir(oxt_path):
|
||||
return
|
||||
|
||||
# create extract directory if not exists
|
||||
if not os.path.exists(extract_path):
|
||||
os.makedirs(extract_path)
|
||||
|
||||
# check that the extract path is a directory
|
||||
if not os.path.isdir(extract_path):
|
||||
raise ExtractPathIsNoDirectory('extract path is not a valid directory')
|
||||
|
||||
# get all .oxt extension at given path
|
||||
oxt_files = [extension for extension in os.listdir(oxt_path)
|
||||
if extension.lower().endswith('.oxt')]
|
||||
|
||||
for extension_name in oxt_files:
|
||||
extension_path = os.path.join(oxt_path, extension_name)
|
||||
|
||||
try:
|
||||
dictionaries = extract(extension_path, extract_path, override)
|
||||
yield BATCH_SUCCESS, extension_name, None, dictionaries, ''
|
||||
except BadExtensionFile as error:
|
||||
logger.error(('extension "{}" is not a valid ZIP file'
|
||||
).format(extension_name))
|
||||
yield (BATCH_ERROR, extension_name, error, [],
|
||||
_('extension "{}" is not a valid ZIP file'
|
||||
).format(extension_name))
|
||||
except BadXml as error:
|
||||
logger.error(('extension "{}" has no valid XML dictionary registry'
|
||||
).format(extension_name))
|
||||
yield (BATCH_ERROR, extension_name, error, [],
|
||||
_('extension "{}" has no valid XML dictionary registry'
|
||||
).format(extension_name))
|
||||
|
||||
# move the extension after processing if user requires it
|
||||
if move_path is not None:
|
||||
# create move path if it doesn't exists
|
||||
if not os.path.exists(move_path):
|
||||
os.makedirs(move_path)
|
||||
# move to the given path only if it is a directory and target
|
||||
# doesn't exists
|
||||
if os.path.isdir(move_path):
|
||||
if (not os.path.exists(os.path.join(move_path, extension_name))
|
||||
or override):
|
||||
shutil.move(extension_path, move_path)
|
||||
else:
|
||||
logger.warning(('unable to move extension, file with same '
|
||||
'name exists within move_path'))
|
||||
yield (BATCH_WARNING, extension_name,
|
||||
('unable to move extension, file with same name '
|
||||
'exists within move_path'), [],
|
||||
_('unable to move extension, file with same name '
|
||||
'exists within move_path'))
|
||||
else:
|
||||
logger.warning(('unable to move extension, move_path is not a '
|
||||
'directory'))
|
||||
yield (BATCH_WARNING, extension_name,
|
||||
('unable to move extension, move_path is not a '
|
||||
'directory'), [],
|
||||
_('unable to move extension, move_path is not a '
|
||||
'directory'))
|
|
@ -1,660 +0,0 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2012, Maximilian Köhl <linuxmaxi@googlemail.com>
|
||||
# Copyright (C) 2012, Carlos Jenkins <carlos@jenkins.co.cr>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
A simple but quite powerful spellchecking library written in pure Python for Gtk
|
||||
based on Enchant. It supports PyGObject as well as PyGtk for Python 2 and 3 with
|
||||
automatic switching and binding detection. For automatic translation of the user
|
||||
interface it can use Gedit’s translation files.
|
||||
"""
|
||||
|
||||
import enchant
|
||||
import gettext
|
||||
import logging
|
||||
import re
|
||||
import sys
|
||||
|
||||
from uberwriter.pylocales import code_to_name as _code_to_name
|
||||
from uberwriter.pylocales import LanguageNotFound, CountryNotFound
|
||||
|
||||
# public objects
|
||||
__all__ = ['SpellChecker', 'NoDictionariesFound', 'NoGtkBindingFound']
|
||||
|
||||
# logger
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class NoDictionariesFound(Exception):
|
||||
"""
|
||||
There aren't any dictionaries installed on the current system so
|
||||
spellchecking could not work in any way.
|
||||
"""
|
||||
|
||||
class NoGtkBindingFound(Exception):
|
||||
"""
|
||||
Could not find any loaded Gtk binding.
|
||||
"""
|
||||
|
||||
if sys.version_info.major == 3:
|
||||
_py3k = True
|
||||
else:
|
||||
_py3k = False
|
||||
|
||||
if _py3k:
|
||||
# there is only the gi binding for Python 3
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk as gtk
|
||||
_pygobject = True
|
||||
else:
|
||||
# find any loaded gtk binding
|
||||
if 'gi.repository.Gtk' in sys.modules:
|
||||
gtk = sys.modules['gi.repository.Gtk']
|
||||
_pygobject = True
|
||||
elif 'gtk' in sys.modules:
|
||||
gtk = sys.modules['gtk']
|
||||
_pygobject = False
|
||||
else:
|
||||
raise NoGtkBindingFound('could not find any loaded Gtk binding')
|
||||
|
||||
# select base list class
|
||||
try:
|
||||
from collections import UserList
|
||||
_list = UserList
|
||||
except ImportError:
|
||||
_list = list
|
||||
|
||||
|
||||
|
||||
# select base string
|
||||
if _py3k:
|
||||
basestring = str
|
||||
|
||||
# map between Gedit's translation and PyGtkSpellcheck's
|
||||
_GEDIT_MAP = {'Languages' : 'Languages',
|
||||
'Ignore All' : 'Ignore _All',
|
||||
'Suggestions' : 'Suggestions',
|
||||
'(no suggestions)' : '(no suggested words)',
|
||||
'Add "{}" to Dictionary' : 'Add w_ord',
|
||||
'Unknown' : 'Unknown'}
|
||||
|
||||
# translation
|
||||
if gettext.find('gedit'):
|
||||
_gedit = gettext.translation('gedit', fallback=True).gettext
|
||||
_ = lambda message: _gedit(_GEDIT_MAP[message]).replace('_', '')
|
||||
else:
|
||||
locale_name = 'py{}gtkspellcheck'.format(sys.version_info.major)
|
||||
_ = gettext.translation(locale_name, fallback=True).gettext
|
||||
|
||||
def code_to_name(code, separator='_'):
|
||||
try:
|
||||
return _code_to_name(code, separator)
|
||||
except (LanguageNotFound, CountryNotFound):
|
||||
return '{} ({})'.format(_('Unknown'), code)
|
||||
|
||||
class SpellChecker(object):
|
||||
"""
|
||||
Main spellchecking class, everything important happens here.
|
||||
|
||||
:param view: GtkTextView the SpellChecker should be attached to.
|
||||
:param language: The language which should be used for spellchecking.
|
||||
Use a combination of two letter lower-case ISO 639 language code with a
|
||||
two letter upper-case ISO 3166 country code, for example en_US or de_DE.
|
||||
:param prefix: A prefix for some internal GtkTextMarks.
|
||||
:param collapse: Enclose suggestions in its own menu.
|
||||
:param params: Dictionary with Enchant broker parameters that should be set
|
||||
e.g. `enchant.myspell.dictionary.path`.
|
||||
|
||||
.. attribute:: languages
|
||||
|
||||
A list of supported languages.
|
||||
|
||||
.. function:: exists(language)
|
||||
|
||||
Checks if a language exists.
|
||||
|
||||
:param language: language to check
|
||||
"""
|
||||
FILTER_WORD = 'word'
|
||||
FILTER_LINE = 'line'
|
||||
FILTER_TEXT = 'text'
|
||||
|
||||
DEFAULT_FILTERS = {FILTER_WORD : [r'[0-9.,]+'],
|
||||
FILTER_LINE : [(r'(https?|ftp|file):((//)|(\\\\))+[\w\d:'
|
||||
r'#@%/;$()~_?+-=\\.&]+'),
|
||||
r'[\w\d]+@[\w\d.]+'],
|
||||
FILTER_TEXT : []}
|
||||
|
||||
class _LanguageList(_list):
|
||||
def __init__(self, *args, **kwargs):
|
||||
if sys.version_info.major == 3:
|
||||
super().__init__(*args, **kwargs)
|
||||
else:
|
||||
_list.__init__(self, *args, **kwargs)
|
||||
self.mapping = dict(self)
|
||||
|
||||
@classmethod
|
||||
def from_broker(cls, broker):
|
||||
return cls(sorted([(language, code_to_name(language))
|
||||
for language in broker.list_languages()],
|
||||
key=lambda language: language[1]))
|
||||
|
||||
def exists(self, language):
|
||||
return language in self.mapping
|
||||
|
||||
class _Mark():
|
||||
def __init__(self, buffer, name, start):
|
||||
self._buffer = buffer
|
||||
self._name = name
|
||||
self._mark = self._buffer.create_mark(self._name, start, True)
|
||||
|
||||
@property
|
||||
def iter(self):
|
||||
return self._buffer.get_iter_at_mark(self._mark)
|
||||
|
||||
@property
|
||||
def inside_word(self):
|
||||
return self.iter.inside_word()
|
||||
|
||||
@property
|
||||
def word(self):
|
||||
start = self.iter
|
||||
if not start.starts_word():
|
||||
start.backward_word_start()
|
||||
end = self.iter
|
||||
if end.inside_word():
|
||||
end.forward_word_end()
|
||||
return start, end
|
||||
|
||||
def move(self, location):
|
||||
self._buffer.move_mark(self._mark, location)
|
||||
|
||||
def __init__(self, view, language='en', prefix='gtkspellchecker',
|
||||
collapse=True, params={}):
|
||||
self._view = view
|
||||
self.collapse = collapse
|
||||
self._view.connect('populate-popup',
|
||||
lambda entry, menu:self._extend_menu(menu))
|
||||
self._view.connect('popup-menu', self._click_move_popup)
|
||||
self._view.connect('button-press-event', self._click_move_button)
|
||||
self._prefix = prefix
|
||||
self._broker = enchant.Broker()
|
||||
for param, value in params.items(): self._broker.set_param(param, value)
|
||||
self.languages = SpellChecker._LanguageList.from_broker(self._broker)
|
||||
if self.languages.exists(language):
|
||||
self._language = language
|
||||
elif self.languages.exists('en'):
|
||||
logger.warning(('no installed dictionary for language "{}", '
|
||||
'fallback to english'.format(language)))
|
||||
self._language = 'en'
|
||||
else:
|
||||
if self.languages:
|
||||
self._language = self.languages[0][0]
|
||||
logger.warning(('no installed dictionary for language "{}" '
|
||||
'and english, fallback to first language in'
|
||||
'language list ("{}")').format(language,
|
||||
self._language))
|
||||
else:
|
||||
logger.critical('no dictionaries found')
|
||||
raise NoDictionariesFound()
|
||||
self._dictionary = self._broker.request_dict(self._language)
|
||||
self._deferred_check = False
|
||||
self._filters = dict(SpellChecker.DEFAULT_FILTERS)
|
||||
self._regexes = {SpellChecker.FILTER_WORD : re.compile('|'.join(
|
||||
self._filters[SpellChecker.FILTER_WORD])),
|
||||
SpellChecker.FILTER_LINE : re.compile('|'.join(
|
||||
self._filters[SpellChecker.FILTER_LINE])),
|
||||
SpellChecker.FILTER_TEXT : re.compile('|'.join(
|
||||
self._filters[SpellChecker.FILTER_TEXT]),
|
||||
re.MULTILINE)}
|
||||
self._enabled = True
|
||||
self.buffer_initialize()
|
||||
|
||||
@property
|
||||
def language(self):
|
||||
"""
|
||||
The language used for spellchecking.
|
||||
"""
|
||||
return self._language
|
||||
|
||||
@language.setter
|
||||
def language(self, language):
|
||||
if language != self._language and self.languages.exists(language):
|
||||
self._language = language
|
||||
self._dictionary = self._broker.request_dict(language)
|
||||
self.recheck()
|
||||
|
||||
@property
|
||||
def enabled(self):
|
||||
"""
|
||||
Enable or disable spellchecking.
|
||||
"""
|
||||
return self._enabled
|
||||
|
||||
@enabled.setter
|
||||
def enabled(self, enabled):
|
||||
if enabled and not self._enabled:
|
||||
self.enable()
|
||||
elif not enabled and self._enabled:
|
||||
self.disable()
|
||||
|
||||
def buffer_initialize(self):
|
||||
"""
|
||||
Initialize the GtkTextBuffer associated with the GtkTextView. If you
|
||||
have associated a new GtkTextBuffer with the GtkTextView call this
|
||||
method.
|
||||
"""
|
||||
if _pygobject:
|
||||
self._misspelled = gtk.TextTag.new('{}-misspelled'\
|
||||
.format(self._prefix))
|
||||
else:
|
||||
self._misspelled = gtk.TextTag('{}-misspelled'.format(self._prefix))
|
||||
self._misspelled.set_property('underline', 4)
|
||||
self._buffer = self._view.get_buffer()
|
||||
self._buffer.connect('insert-text', self._before_text_insert)
|
||||
self._buffer.connect_after('insert-text', self._after_text_insert)
|
||||
self._buffer.connect_after('delete-range', self._range_delete)
|
||||
self._buffer.connect_after('mark-set', self._mark_set)
|
||||
start = self._buffer.get_bounds()[0]
|
||||
self._marks = {'insert-start' : SpellChecker._Mark(self._buffer,
|
||||
'{}-insert-start'.format(self._prefix), start),
|
||||
'insert-end' : SpellChecker._Mark(self._buffer,
|
||||
'{}-insert-end'.format(self._prefix), start),
|
||||
'click' : SpellChecker._Mark(self._buffer,
|
||||
'{}-click'.format(self._prefix), start)}
|
||||
self._table = self._buffer.get_tag_table()
|
||||
self._table.add(self._misspelled)
|
||||
self.ignored_tags = []
|
||||
def tag_added(tag, *args):
|
||||
if hasattr(tag, 'spell_check') and not getattr(tag, 'spell_check'):
|
||||
self.ignored_tags.append(tag)
|
||||
def tag_removed(tag, *args):
|
||||
if tag in self.ignored_tags:
|
||||
self.ignored_tags.remove(tag)
|
||||
self._table.connect('tag-added', tag_added)
|
||||
self._table.connect('tag-removed', tag_removed)
|
||||
self._table.foreach(tag_added, None)
|
||||
self.no_spell_check = self._table.lookup('no-spell-check')
|
||||
if not self.no_spell_check:
|
||||
if _pygobject:
|
||||
self.no_spell_check = gtk.TextTag.new('no-spell-check')
|
||||
else:
|
||||
self.no_spell_check = gtk.TextTag('no-spell-check')
|
||||
self._table.add(self.no_spell_check)
|
||||
self.recheck()
|
||||
|
||||
def recheck(self):
|
||||
"""
|
||||
Rechecks the spelling of the whole text.
|
||||
"""
|
||||
start, end = self._buffer.get_bounds()
|
||||
self.check_range(start, end, True)
|
||||
|
||||
def disable(self):
|
||||
"""
|
||||
Disable spellchecking.
|
||||
"""
|
||||
self._enabled = False
|
||||
start, end = self._buffer.get_bounds()
|
||||
self._buffer.remove_tag(self._misspelled, start, end)
|
||||
|
||||
def enable(self):
|
||||
"""
|
||||
Enable spellchecking.
|
||||
"""
|
||||
self._enabled = True
|
||||
self.recheck()
|
||||
|
||||
def append_filter(self, regex, filter_type):
|
||||
"""
|
||||
Append a new filter to the filter list. Filters are useful to ignore
|
||||
some misspelled words based on regular expressions.
|
||||
|
||||
:param regex: The regex used for filtering.
|
||||
:param filter_type: The type of the filter.
|
||||
|
||||
Filter Types:
|
||||
|
||||
:const:`SpellChecker.FILTER_WORD`: The regex must match the whole word
|
||||
you want to filter. The word separation is done by Pango's word
|
||||
separation algorithm so, for example, urls won't work here because
|
||||
they are split in many words.
|
||||
|
||||
:const:`SpellChecker.FILTER_LINE`: If the expression you want to match
|
||||
is a single line expression use this type. It should not be an open
|
||||
end expression because then the rest of the line with the text you
|
||||
want to filter will become correct.
|
||||
|
||||
:const:`SpellChecker.FILTER_TEXT`: Use this if you want to filter
|
||||
multiline expressions. The regex will be compiled with the
|
||||
`re.MULTILINE` flag. Same with open end expressions apply here.
|
||||
"""
|
||||
self._filters[filter_type].append(regex)
|
||||
if filter_type == SpellChecker.FILTER_TEXT:
|
||||
self._regexes[filter_type] = re.compile('|'.join(
|
||||
self._filters[filter_type]), re.MULTILINE)
|
||||
else:
|
||||
self._regexes[filter_type] = re.compile('|'.join(
|
||||
self._filters[filter_type]))
|
||||
|
||||
def remove_filter(self, regex, filter_type):
|
||||
"""
|
||||
Remove a filter from the filter list.
|
||||
|
||||
:param regex: The regex which used for filtering.
|
||||
:param filter_type: The type of the filter.
|
||||
"""
|
||||
self._filters[filter_type].remove(regex)
|
||||
if filter_type == SpellChecker.FILTER_TEXT:
|
||||
self._regexes[filter_type] = re.compile('|'.join(
|
||||
self._filters[filter_type]), re.MULTILINE)
|
||||
else:
|
||||
self._regexes[filter_type] = re.compile('|'.join(
|
||||
self._filters[filter_type]))
|
||||
|
||||
def append_ignore_tag(self, tag):
|
||||
"""
|
||||
Appends a tag to the list of ignored tags. A string will be automatic
|
||||
resolved into a tag object.
|
||||
|
||||
:param tag: Tag object or tag name.
|
||||
"""
|
||||
if isinstance(tag, basestring):
|
||||
tag = self._table.lookup(tag)
|
||||
self.ignored_tags.append(tag)
|
||||
|
||||
def remove_ignore_tag(self, tag):
|
||||
"""
|
||||
Removes a tag from the list of ignored tags. A string will be automatic
|
||||
resolved into a tag object.
|
||||
|
||||
:param tag: Tag object or tag name.
|
||||
"""
|
||||
if isinstance(tag, basestring):
|
||||
tag = self._table.lookup(tag)
|
||||
self.ignored_tags.remove(tag)
|
||||
|
||||
def add_to_dictionary(self, word):
|
||||
"""
|
||||
Adds a word to user's dictionary.
|
||||
|
||||
:param word: The word to add.
|
||||
"""
|
||||
self._dictionary.add_to_pwl(word)
|
||||
self.recheck()
|
||||
|
||||
def ignore_all(self, word):
|
||||
"""
|
||||
Ignores a word for the current session.
|
||||
|
||||
:param word: The word to ignore.
|
||||
"""
|
||||
self._dictionary.add_to_session(word)
|
||||
self.recheck()
|
||||
|
||||
def check_range(self, start, end, force_all=False):
|
||||
"""
|
||||
Checks a specified range between two GtkTextIters.
|
||||
|
||||
:param start: Start iter - checking starts here.
|
||||
:param end: End iter - checking ends here.
|
||||
"""
|
||||
if not self._enabled:
|
||||
return
|
||||
if end.inside_word(): end.forward_word_end()
|
||||
if not start.starts_word() and (start.inside_word() or
|
||||
start.ends_word()):
|
||||
start.backward_word_start()
|
||||
self._buffer.remove_tag(self._misspelled, start, end)
|
||||
cursor = self._buffer.get_iter_at_mark(self._buffer.get_insert())
|
||||
precursor = cursor.copy()
|
||||
precursor.backward_char()
|
||||
highlight = (cursor.has_tag(self._misspelled) or
|
||||
precursor.has_tag(self._misspelled))
|
||||
if not start.get_offset():
|
||||
start.forward_word_end()
|
||||
start.backward_word_start()
|
||||
word_start = start.copy()
|
||||
while word_start.compare(end) < 0:
|
||||
word_end = word_start.copy()
|
||||
word_end.forward_word_end()
|
||||
in_word = ((word_start.compare(cursor) < 0) and
|
||||
(cursor.compare(word_end) <= 0))
|
||||
if in_word and not force_all:
|
||||
if highlight:
|
||||
self._check_word(word_start, word_end)
|
||||
else:
|
||||
self._deferred_check = True
|
||||
else:
|
||||
self._check_word(word_start, word_end)
|
||||
self._deferred_check = False
|
||||
word_end.forward_word_end()
|
||||
word_end.backward_word_start()
|
||||
if word_start.equal(word_end):
|
||||
break
|
||||
word_start = word_end.copy()
|
||||
|
||||
def _languages_menu(self):
|
||||
def _set_language(item, code):
|
||||
self.language = code
|
||||
if _pygobject:
|
||||
menu = gtk.Menu.new()
|
||||
group = []
|
||||
else:
|
||||
menu = gtk.Menu()
|
||||
group = gtk.RadioMenuItem()
|
||||
connect = []
|
||||
for code, name in self.languages:
|
||||
if _pygobject:
|
||||
item = gtk.RadioMenuItem.new_with_label(group, name)
|
||||
group.append(item)
|
||||
else:
|
||||
item = gtk.RadioMenuItem(group, name)
|
||||
if code == self.language:
|
||||
item.set_active(True)
|
||||
connect.append((item, code))
|
||||
menu.append(item)
|
||||
for item, code in connect:
|
||||
item.connect('activate', _set_language, code)
|
||||
return menu
|
||||
|
||||
def _suggestion_menu(self, word):
|
||||
menu = []
|
||||
suggestions = self._dictionary.suggest(word)
|
||||
if not suggestions:
|
||||
if _pygobject:
|
||||
item = gtk.MenuItem.new()
|
||||
label = gtk.Label.new('')
|
||||
else:
|
||||
item = gtk.MenuItem()
|
||||
label = gtk.Label()
|
||||
try:
|
||||
label.set_halign(gtk.Align.LEFT)
|
||||
except AttributeError:
|
||||
label.set_alignment(0.0, 0.5)
|
||||
label.set_markup('<i>{text}</i>'.format(text=_('(no suggestions)')))
|
||||
item.add(label)
|
||||
menu.append(item)
|
||||
else:
|
||||
for suggestion in suggestions:
|
||||
if _pygobject:
|
||||
item = gtk.MenuItem.new()
|
||||
label = gtk.Label.new('')
|
||||
else:
|
||||
item = gtk.MenuItem()
|
||||
label = gtk.Label()
|
||||
label.set_markup('<b>{text}</b>'.format(text=suggestion))
|
||||
try:
|
||||
label.set_halign(gtk.Align.LEFT)
|
||||
except AttributeError:
|
||||
label.set_alignment(0.0, 0.5)
|
||||
item.add(label)
|
||||
item.connect('activate', self._replace_word, word, suggestion)
|
||||
menu.append(item)
|
||||
if _pygobject:
|
||||
menu.append(gtk.SeparatorMenuItem.new())
|
||||
item = gtk.MenuItem.new_with_label(
|
||||
_('Add "{}" to Dictionary').format(word))
|
||||
else:
|
||||
menu.append(gtk.SeparatorMenuItem())
|
||||
item = gtk.MenuItem(_('Add "{}" to Dictionary').format(word))
|
||||
item.connect('activate', lambda *args: self.add_to_dictionary(word))
|
||||
menu.append(item)
|
||||
if _pygobject:
|
||||
item = gtk.MenuItem.new_with_label(_('Ignore All'))
|
||||
else:
|
||||
item = gtk.MenuItem(_('Ignore All'))
|
||||
item.connect('activate', lambda *args: self.ignore_all(word))
|
||||
menu.append(item)
|
||||
return menu
|
||||
|
||||
def _extend_menu(self, menu):
|
||||
if not self._enabled:
|
||||
return
|
||||
if _pygobject:
|
||||
separator = gtk.SeparatorMenuItem.new()
|
||||
else:
|
||||
separator = gtk.SeparatorMenuItem()
|
||||
separator.show()
|
||||
menu.prepend(separator)
|
||||
if _pygobject:
|
||||
languages = gtk.MenuItem.new_with_label(_('Languages'))
|
||||
else:
|
||||
languages = gtk.MenuItem(_('Languages'))
|
||||
languages.set_submenu(self._languages_menu())
|
||||
languages.show_all()
|
||||
menu.prepend(languages)
|
||||
if self._marks['click'].inside_word:
|
||||
start, end = self._marks['click'].word
|
||||
if start.has_tag(self._misspelled):
|
||||
if _py3k:
|
||||
word = self._buffer.get_text(start, end, False)
|
||||
else:
|
||||
word = self._buffer.get_text(start, end,
|
||||
False).decode('utf-8')
|
||||
items = self._suggestion_menu(word)
|
||||
if self.collapse:
|
||||
if _pygobject:
|
||||
suggestions = gtk.MenuItem.new_with_label(
|
||||
_('Suggestions'))
|
||||
submenu = gtk.Menu.new()
|
||||
else:
|
||||
suggestions = gtk.MenuItem(_('Suggestions'))
|
||||
submenu = gtk.Menu()
|
||||
for item in items:
|
||||
submenu.append(item)
|
||||
suggestions.set_submenu(submenu)
|
||||
suggestions.show_all()
|
||||
menu.prepend(suggestions)
|
||||
else:
|
||||
items.reverse()
|
||||
for item in items:
|
||||
menu.prepend(item)
|
||||
menu.show_all()
|
||||
|
||||
def _click_move_popup(self, *args):
|
||||
self._marks['click'].move(self._buffer.get_iter_at_mark(
|
||||
self._buffer.get_insert()))
|
||||
return False
|
||||
|
||||
def _click_move_button(self, widget, event):
|
||||
if event.button == 3:
|
||||
if self._deferred_check: self._check_deferred_range(True)
|
||||
x, y = self._view.window_to_buffer_coords(2, int(event.x),
|
||||
int(event.y))
|
||||
iter = self._view.get_iter_at_location(x, y)
|
||||
if isinstance(iter, tuple):
|
||||
iter = iter[1]
|
||||
self._marks['click'].move(iter)
|
||||
return False
|
||||
|
||||
def _before_text_insert(self, textbuffer, location, text, length):
|
||||
self._marks['insert-start'].move(location)
|
||||
|
||||
def _after_text_insert(self, textbuffer, location, text, length):
|
||||
start = self._marks['insert-start'].iter
|
||||
self.check_range(start, location)
|
||||
self._marks['insert-end'].move(location)
|
||||
|
||||
def _range_delete(self, textbuffer, start, end):
|
||||
self.check_range(start, end)
|
||||
|
||||
def _mark_set(self, textbuffer, location, mark):
|
||||
if mark == self._buffer.get_insert() and self._deferred_check:
|
||||
self._check_deferred_range(False)
|
||||
|
||||
def _replace_word(self, item, old_word, new_word):
|
||||
start, end = self._marks['click'].word
|
||||
offset = start.get_offset()
|
||||
self._buffer.begin_user_action()
|
||||
self._buffer.delete(start, end)
|
||||
self._buffer.insert(self._buffer.get_iter_at_offset(offset), new_word)
|
||||
self._buffer.end_user_action()
|
||||
self._dictionary.store_replacement(old_word, new_word)
|
||||
|
||||
def _check_deferred_range(self, force_all):
|
||||
start = self._marks['insert-start'].iter
|
||||
end = self._marks['insert-end'].iter
|
||||
self.check_range(start, end, force_all)
|
||||
|
||||
def _check_word(self, start, end):
|
||||
if start.has_tag(self.no_spell_check):
|
||||
return
|
||||
for tag in self.ignored_tags:
|
||||
if start.has_tag(tag):
|
||||
return
|
||||
if _py3k:
|
||||
word = self._buffer.get_text(start, end, False).strip()
|
||||
else:
|
||||
word = self._buffer.get_text(start, end, False).decode('utf-8').strip()
|
||||
if not word:
|
||||
return
|
||||
if len(self._filters[SpellChecker.FILTER_WORD]):
|
||||
if self._regexes[SpellChecker.FILTER_WORD].match(word):
|
||||
return
|
||||
if len(self._filters[SpellChecker.FILTER_LINE]):
|
||||
line_start = self._buffer.get_iter_at_line(start.get_line())
|
||||
line_end = end.copy()
|
||||
line_end.forward_to_line_end()
|
||||
if _py3k:
|
||||
line = self._buffer.get_text(line_start, line_end, False)
|
||||
else:
|
||||
line = self._buffer.get_text(line_start, line_end,
|
||||
False).decode('utf-8')
|
||||
for match in self._regexes[SpellChecker.FILTER_LINE].finditer(line):
|
||||
if match.start() <= start.get_line_offset() <= match.end():
|
||||
start = self._buffer.get_iter_at_line_offset(
|
||||
start.get_line(), match.start())
|
||||
end = self._buffer.get_iter_at_line_offset(start.get_line(),
|
||||
match.end())
|
||||
self._buffer.remove_tag(self._misspelled, start, end)
|
||||
return
|
||||
if len(self._filters[SpellChecker.FILTER_TEXT]):
|
||||
text_start, text_end = self._buffer.get_bounds()
|
||||
if _py3k:
|
||||
text = self._buffer.get_text(text_start, text_end, False)
|
||||
else:
|
||||
text = self._buffer.get_text(text_start, text_end,
|
||||
False).decode('utf-8')
|
||||
for match in self._regexes[SpellChecker.FILTER_TEXT].finditer(text):
|
||||
if match.start() <= start.get_offset() <= match.end():
|
||||
start = self._buffer.get_iter_at_offset(match.start())
|
||||
end = self._buffer.get_iter_at_offset(match.end())
|
||||
self._buffer.remove_tag(self._misspelled, start, end)
|
||||
return
|
||||
if not self._dictionary.check(word):
|
||||
self._buffer.apply_tag(self._misspelled, start, end)
|
|
@ -20,12 +20,12 @@ from collections import namedtuple
|
|||
from gettext import gettext as _
|
||||
|
||||
import gi
|
||||
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
from uberwriter.helpers import get_builder
|
||||
from uberwriter.helpers import get_descendant
|
||||
|
||||
from uberwriter.application import Application as app
|
||||
|
||||
class MainHeaderbar: #pylint: disable=too-few-public-methods
|
||||
"""Sets up the main application headerbar
|
||||
|
@ -36,14 +36,14 @@ class MainHeaderbar: #pylint: disable=too-few-public-methods
|
|||
self.hb.props.show_close_button = True
|
||||
self.hb.get_style_context().add_class("titlebar")
|
||||
|
||||
self.hb_revealer = Gtk.Revealer(name='titlebar_revealer')
|
||||
self.hb_revealer = Gtk.Revealer(name='titlebar-revealer')
|
||||
self.hb_revealer.add(self.hb)
|
||||
self.hb_revealer.props.transition_duration = 1000
|
||||
self.hb_revealer.props.transition_duration = 750
|
||||
self.hb_revealer.props.transition_type = Gtk.RevealerTransitionType.CROSSFADE
|
||||
self.hb_revealer.show()
|
||||
self.hb_revealer.set_reveal_child(True)
|
||||
|
||||
self.hb_container = Gtk.Frame(name='titlebar_container')
|
||||
self.hb_container = Gtk.Frame(name='titlebar-container')
|
||||
self.hb_container.set_shadow_type(Gtk.ShadowType.NONE)
|
||||
self.hb_container.add(self.hb_revealer)
|
||||
self.hb_container.show()
|
||||
|
@ -54,7 +54,7 @@ class MainHeaderbar: #pylint: disable=too-few-public-methods
|
|||
self.hb.show_all()
|
||||
|
||||
|
||||
class FsHeaderbar:
|
||||
class FullscreenHeaderbar:
|
||||
"""Sets up and manages the fullscreen headerbar and his events
|
||||
"""
|
||||
|
||||
|
@ -141,7 +141,7 @@ def buttons(app):
|
|||
btn.open_recent.pack_start(open_button, False, False, 0)
|
||||
btn.open_recent.pack_end(recents_button, False, False, 0)
|
||||
|
||||
btn.search.set_tooltip_text(_("Search and replace"))
|
||||
btn.search.set_tooltip_text(_("Search and Replace"))
|
||||
btn.menu.set_tooltip_text(_("Menu"))
|
||||
btn.menu.set_image(Gtk.Image.new_from_icon_name("open-menu-symbolic",
|
||||
Gtk.IconSize.BUTTON))
|
||||
|
@ -153,6 +153,7 @@ def buttons(app):
|
|||
|
||||
return btn
|
||||
|
||||
|
||||
def pack_buttons(headerbar, btn, btn_exit=None):
|
||||
"""Pack the given buttons in the given headerbar
|
||||
|
||||
|
|
|
@ -21,12 +21,16 @@ import logging
|
|||
import os
|
||||
import shutil
|
||||
|
||||
|
||||
import gi
|
||||
import pypandoc
|
||||
from gi.overrides.Pango import Pango
|
||||
|
||||
from uberwriter.settings import Settings
|
||||
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk # pylint: disable=E0611
|
||||
|
||||
from uberwriter.uberwriterconfig import get_data_file
|
||||
from uberwriter.config import get_data_file
|
||||
from uberwriter.builder import Builder
|
||||
|
||||
|
||||
|
@ -48,13 +52,18 @@ def get_builder(builder_file_name):
|
|||
return builder
|
||||
|
||||
|
||||
# Owais Lone : To get quick access to icons and stuff.
|
||||
def path_to_file(path):
|
||||
"""Return a file path (file:///) for the given path"""
|
||||
|
||||
return "file:///" + path
|
||||
|
||||
|
||||
def get_media_file(media_file_path):
|
||||
"""Return the full path of a given filename under the media dir
|
||||
(starts with file:///)
|
||||
"""
|
||||
|
||||
return "file:///" + get_media_path(media_file_path)
|
||||
return path_to_file(get_media_path(media_file_path))
|
||||
|
||||
|
||||
def get_media_path(media_file_name):
|
||||
|
@ -160,6 +169,7 @@ def exist_executable(command):
|
|||
|
||||
return shutil.which(command) is not None
|
||||
|
||||
|
||||
def get_descendant(widget, child_name, level, doPrint=False):
|
||||
if widget is not None:
|
||||
if doPrint: print("-"*level + str(Gtk.Buildable.get_name(widget)) +
|
||||
|
@ -188,3 +198,14 @@ def get_descendant(widget, child_name, level, doPrint=False):
|
|||
if child is not None:
|
||||
found = get_descendant(child, child_name, level+1, doPrint) # //search the child
|
||||
if found: return found
|
||||
|
||||
|
||||
def get_char_width(widget):
|
||||
return Pango.units_to_double(
|
||||
widget.get_pango_context().get_metrics().get_approximate_char_width())
|
||||
|
||||
|
||||
def pandoc_convert(text, to="html5", args=[], outputfile=None):
|
||||
fr = Settings.new().get_value('input-format').get_string() or "markdown"
|
||||
args.extend(["--quiet"])
|
||||
return pypandoc.convert_text(text, to, fr, extra_args=args, outputfile=outputfile)
|
||||
|
|
|
@ -14,32 +14,29 @@
|
|||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
# END LICENSE
|
||||
|
||||
import re
|
||||
import urllib
|
||||
from urllib.error import URLError
|
||||
import webbrowser
|
||||
import subprocess
|
||||
import tempfile
|
||||
import logging
|
||||
import threading
|
||||
import re
|
||||
import subprocess
|
||||
import telnetlib
|
||||
|
||||
import tempfile
|
||||
import threading
|
||||
import urllib
|
||||
import webbrowser
|
||||
from gettext import gettext as _
|
||||
from urllib.error import URLError
|
||||
from urllib.parse import unquote
|
||||
|
||||
import gi
|
||||
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk, Gdk, GdkPixbuf, GObject
|
||||
from uberwriter import latex_to_PNG
|
||||
from uberwriter import latex_to_PNG, text_view_markup_handler
|
||||
from uberwriter.settings import Settings
|
||||
|
||||
from uberwriter.fix_table import FixTable
|
||||
|
||||
from uberwriter.markup_buffer import MarkupBuffer
|
||||
|
||||
LOGGER = logging.getLogger('uberwriter')
|
||||
|
||||
GObject.threads_init() # Still needed?
|
||||
|
||||
# TODO:
|
||||
# - Don't insert a span with id, it breaks the text to often
|
||||
# Would be better to search for the nearest title and generate
|
||||
|
@ -240,7 +237,7 @@ def fill_lexikon_bubble(vocab, lexikon_dict):
|
|||
if lexikon_dict:
|
||||
for entry in lexikon_dict:
|
||||
vocab_label = Gtk.Label.new(vocab + ' ~ ' + entry['class'])
|
||||
vocab_label.get_style_context().add_class('lexikon_heading')
|
||||
vocab_label.get_style_context().add_class('lexikon-heading')
|
||||
vocab_label.set_halign(Gtk.Align.START)
|
||||
vocab_label.set_justify(Gtk.Justification.LEFT)
|
||||
grid.attach(vocab_label, 0, i, 3, 1)
|
||||
|
@ -248,14 +245,14 @@ def fill_lexikon_bubble(vocab, lexikon_dict):
|
|||
for definition in entry['defs']:
|
||||
i = i + 1
|
||||
num_label = Gtk.Label.new(definition['num'])
|
||||
num_label.get_style_context().add_class('lexikon_num')
|
||||
num_label.get_style_context().add_class('lexikon-num')
|
||||
num_label.set_justify(Gtk.Justification.RIGHT)
|
||||
grid.attach(num_label, 0, i, 1, 1)
|
||||
|
||||
def_label = Gtk.Label.new(' '.join(definition['description']))
|
||||
def_label.set_halign(Gtk.Align.START)
|
||||
def_label.set_justify(Gtk.Justification.LEFT)
|
||||
def_label.get_style_context().add_class('lexikon_definition')
|
||||
def_label.get_style_context().add_class('lexikon-definition')
|
||||
def_label.props.wrap = True
|
||||
grid.attach(def_label, 1, i, 1, 1)
|
||||
i = i + 1
|
||||
|
@ -264,11 +261,11 @@ def fill_lexikon_bubble(vocab, lexikon_dict):
|
|||
return None
|
||||
|
||||
|
||||
class InlinePreview():
|
||||
class InlinePreview:
|
||||
|
||||
def __init__(self, view, text_buffer):
|
||||
self.text_view = view
|
||||
self.text_buffer = text_buffer
|
||||
def __init__(self, text_view):
|
||||
self.text_view = text_view
|
||||
self.text_buffer = text_view.get_buffer()
|
||||
self.latex_converter = latex_to_PNG.LatexToPNG()
|
||||
cursor_mark = self.text_buffer.get_insert()
|
||||
cursor_iter = self.text_buffer.get_iter_at_mark(cursor_mark)
|
||||
|
@ -307,7 +304,7 @@ class InlinePreview():
|
|||
# b.show_all()
|
||||
# a.show_all()
|
||||
self.popover = Gtk.Popover.new(lbl)
|
||||
self.popover.get_style_context().add_class("QuickPreviewPopup")
|
||||
self.popover.get_style_context().add_class("quick-preview-popup")
|
||||
self.popover.add(alignment)
|
||||
# a.add(alignment)
|
||||
_dismiss, rect = self.popover.get_pointing_to()
|
||||
|
@ -363,8 +360,8 @@ class InlinePreview():
|
|||
|
||||
text = self.text_buffer.get_text(start_iter, end_iter, False)
|
||||
|
||||
math = MarkupBuffer.regex["MATH"]
|
||||
link = MarkupBuffer.regex["LINK"]
|
||||
math = text_view_markup_handler.regex["MATH"]
|
||||
link = text_view_markup_handler.regex["LINK"]
|
||||
|
||||
footnote = re.compile(r'\[\^([^\s]+?)\]')
|
||||
image = re.compile(r"!\[(.*?)\]\((.+?)\)")
|
||||
|
|
|
@ -1,321 +0,0 @@
|
|||
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
|
||||
### BEGIN LICENSE
|
||||
# Copyright (C) 2012, Wolf Vollprecht <w.vollprecht@gmail.com>
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License version 3, as published
|
||||
# by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranties of
|
||||
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
|
||||
# PURPOSE. See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
### END LICENSE
|
||||
|
||||
import re
|
||||
import gi
|
||||
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Pango
|
||||
|
||||
|
||||
class MarkupBuffer():
|
||||
|
||||
def __init__(self, Parent, TextBuffer, base_leftmargin):
|
||||
self.multiplier = 10
|
||||
self.parent = Parent
|
||||
self.text_buffer = TextBuffer
|
||||
|
||||
# Styles
|
||||
self.italic = self.text_buffer.create_tag("italic",
|
||||
style=Pango.Style.ITALIC)
|
||||
|
||||
self.emph = self.text_buffer.create_tag("emph",
|
||||
weight=Pango.Weight.BOLD,
|
||||
style=Pango.Style.NORMAL)
|
||||
|
||||
self.bolditalic = self.text_buffer.create_tag("bolditalic",
|
||||
weight=Pango.Weight.BOLD,
|
||||
style=Pango.Style.ITALIC)
|
||||
|
||||
self.headline_two = self.text_buffer.create_tag("headline_two",
|
||||
weight=Pango.Weight.BOLD,
|
||||
style=Pango.Style.NORMAL)
|
||||
|
||||
self.normal_indent = self.text_buffer.create_tag('normal_indent', indent=100)
|
||||
|
||||
self.math_text = self.text_buffer.create_tag('math_text')
|
||||
|
||||
self.unfocused_text = self.text_buffer.create_tag('graytag',
|
||||
foreground="gray")
|
||||
|
||||
self.underline = self.text_buffer.create_tag("underline",
|
||||
underline=Pango.Underline.SINGLE)
|
||||
|
||||
self.underline.set_property('weight', Pango.Weight.BOLD)
|
||||
|
||||
self.strikethrough = self.text_buffer.create_tag("strikethrough",
|
||||
strikethrough=True)
|
||||
|
||||
self.centertext = self.text_buffer.create_tag("centertext",
|
||||
justification=Gtk.Justification.CENTER)
|
||||
|
||||
self.text_buffer.apply_tag(
|
||||
self.normal_indent,
|
||||
self.text_buffer.get_start_iter(),
|
||||
self.text_buffer.get_end_iter()
|
||||
)
|
||||
|
||||
self.rev_leftmargin = []
|
||||
for i in range(0, 6):
|
||||
name = "rev_marg_indent_left" + str(i)
|
||||
self.rev_leftmargin.append(self.text_buffer.create_tag(name))
|
||||
self.rev_leftmargin[i].set_property("left-margin", 90 - 10 * (i + 1))
|
||||
self.rev_leftmargin[i].set_property("indent", - 10 * (i + 1) - 10)
|
||||
#self.leftmargin[i].set_property("background", "gray")
|
||||
|
||||
self.leftmargin = []
|
||||
|
||||
for i in range(0, 6):
|
||||
name = "marg_indent_left" + str(i)
|
||||
self.leftmargin.append(self.text_buffer.create_tag(name))
|
||||
self.leftmargin[i].set_property("left-margin", base_leftmargin + 10 + 10 * (i + 1))
|
||||
self.leftmargin[i].set_property("indent", - 10 * (i + 1) - 10)
|
||||
|
||||
self.leftindent = []
|
||||
|
||||
for i in range(0, 15):
|
||||
name = "indent_left" + str(i)
|
||||
self.leftindent.append(self.text_buffer.create_tag(name))
|
||||
self.leftindent[i].set_property("indent", - 10 * (i + 1) - 20)
|
||||
|
||||
self.table_env = self.text_buffer.create_tag('table_env')
|
||||
self.table_env.set_property('wrap-mode', Gtk.WrapMode.NONE)
|
||||
# self.table_env.set_property('font', 'Ubuntu Mono 13px')
|
||||
self.table_env.set_property('pixels-above-lines', 0)
|
||||
self.table_env.set_property('pixels-below-lines', 0)
|
||||
|
||||
self.update_style()
|
||||
|
||||
regex = {
|
||||
"ITALIC": re.compile(r"(\*|_)(.*?)\1", re.UNICODE), # *asdasd* // _asdasd asd asd_
|
||||
"STRONG": re.compile(r"(\*\*|__)(.*?)\1", re.UNICODE), # **as das** // __asdasd asd ad a__
|
||||
"STRONGITALIC": re.compile(r"(\*\*\*|___)(.*?)\1"),
|
||||
"BLOCKQUOTE": re.compile(r"^([\>]+ )", re.MULTILINE),
|
||||
"STRIKETHROUGH": re.compile(r"~~[^ `~\n].+?~~"),
|
||||
"LIST": re.compile(r"^[\-\*\+] ", re.MULTILINE),
|
||||
"NUMERICLIST": re.compile(r"^((\d|[a-z]|\#)+[\.\)]) ", re.MULTILINE),
|
||||
"INDENTEDLIST": re.compile(r"^(\t{1,6})((\d|[a-z]|\#)+[\.\)]|[\-\*\+]) ", re.MULTILINE),
|
||||
"HEADINDICATOR": re.compile(r"^(#{1,6}) ", re.MULTILINE),
|
||||
"HEADLINE": re.compile(r"^(#{1,6} [^\n]+)", re.MULTILINE),
|
||||
"HEADLINE_TWO": re.compile(r"^\w.+\n[\=\-]{3,}", re.MULTILINE),
|
||||
"MATH": re.compile(r"[\$]{1,2}([^` ].+?[^`\\ ])[\$]{1,2}"),
|
||||
"HORIZONTALRULE": re.compile(r"(\n\n[\*\-]{3,}\n)"),
|
||||
"TABLE": re.compile(r"^[\-\+]{5,}\n(.+?)\n[\-\+]{5,}\n", re.DOTALL),
|
||||
"LINK": re.compile(r"\(http(.+?)\)")
|
||||
}
|
||||
|
||||
def update_style(self):
|
||||
(found, color) = self.parent.get_style_context().lookup_color('math_text_color')
|
||||
if not found:
|
||||
(_, color) = self.parent.get_style_context().lookup_color('foreground_color')
|
||||
|
||||
self.math_text.set_property("foreground", color.to_string())
|
||||
|
||||
def markup_buffer(self, mode=0):
|
||||
buf = self.text_buffer
|
||||
|
||||
# Test for shifting first line
|
||||
# bbs = buf.get_start_iter()
|
||||
# bbb = buf.get_iter_at_offset(3)
|
||||
|
||||
# buf.apply_tag(self.ftag, bbs, bbb)
|
||||
|
||||
# Modes:
|
||||
# 0 -> start to end
|
||||
# 1 -> around the cursor
|
||||
# 2 -> n.d.
|
||||
|
||||
if mode == 0:
|
||||
context_start = buf.get_start_iter()
|
||||
context_end = buf.get_end_iter()
|
||||
context_offset = 0
|
||||
elif mode == 1:
|
||||
cursor_mark = buf.get_insert()
|
||||
context_start = buf.get_iter_at_mark(cursor_mark)
|
||||
context_start.backward_lines(3)
|
||||
context_end = buf.get_iter_at_mark(cursor_mark)
|
||||
context_end.forward_lines(2)
|
||||
context_offset = context_start.get_offset()
|
||||
|
||||
text = buf.get_slice(context_start, context_end, False)
|
||||
|
||||
self.text_buffer.remove_tag(self.italic, context_start, context_end)
|
||||
|
||||
matches = re.finditer(self.regex["ITALIC"], text)
|
||||
for match in matches:
|
||||
start_iter = buf.get_iter_at_offset(context_offset + match.start())
|
||||
end_iter = buf.get_iter_at_offset(context_offset + match.end())
|
||||
self.text_buffer.apply_tag(self.italic, start_iter, end_iter)
|
||||
|
||||
self.text_buffer.remove_tag(self.emph, context_start, context_end)
|
||||
|
||||
matches = re.finditer(self.regex["STRONG"], text)
|
||||
for match in matches:
|
||||
start_iter = buf.get_iter_at_offset(context_offset + match.start())
|
||||
end_iter = buf.get_iter_at_offset(context_offset + match.end())
|
||||
self.text_buffer.apply_tag(self.emph, start_iter, end_iter)
|
||||
|
||||
matches = re.finditer(self.regex["STRONGITALIC"], text)
|
||||
for match in matches:
|
||||
start_iter = buf.get_iter_at_offset(context_offset + match.start())
|
||||
end_iter = buf.get_iter_at_offset(context_offset + match.end())
|
||||
self.text_buffer.apply_tag(self.bolditalic, start_iter, end_iter)
|
||||
|
||||
self.text_buffer.remove_tag(self.strikethrough, context_start, context_end)
|
||||
|
||||
matches = re.finditer(self.regex["STRIKETHROUGH"], text)
|
||||
for match in matches:
|
||||
start_iter = buf.get_iter_at_offset(context_offset + match.start())
|
||||
end_iter = buf.get_iter_at_offset(context_offset + match.end())
|
||||
self.text_buffer.apply_tag(self.strikethrough, start_iter, end_iter)
|
||||
|
||||
self.text_buffer.remove_tag(self.math_text, context_start, context_end)
|
||||
|
||||
matches = re.finditer(self.regex["MATH"], text)
|
||||
for match in matches:
|
||||
start_iter = buf.get_iter_at_offset(context_offset + match.start())
|
||||
end_iter = buf.get_iter_at_offset(context_offset + match.end())
|
||||
self.text_buffer.apply_tag(self.math_text, start_iter, end_iter)
|
||||
|
||||
for margin in self.rev_leftmargin:
|
||||
self.text_buffer.remove_tag(margin, context_start, context_end)
|
||||
|
||||
matches = re.finditer(self.regex["LIST"], text)
|
||||
for match in matches:
|
||||
start_iter = buf.get_iter_at_offset(context_offset + match.start())
|
||||
end_iter = buf.get_iter_at_offset(context_offset + match.end())
|
||||
self.text_buffer.apply_tag(self.rev_leftmargin[0], start_iter, end_iter)
|
||||
|
||||
matches = re.finditer(self.regex["NUMERICLIST"], text)
|
||||
for match in matches:
|
||||
start_iter = buf.get_iter_at_offset(context_offset + match.start())
|
||||
end_iter = buf.get_iter_at_offset(context_offset + match.end())
|
||||
index = len(match.group(1)) - 1
|
||||
if index < len(self.rev_leftmargin):
|
||||
margin = self.rev_leftmargin[index]
|
||||
self.text_buffer.apply_tag(margin, start_iter, end_iter)
|
||||
|
||||
matches = re.finditer(self.regex["BLOCKQUOTE"], text)
|
||||
for match in matches:
|
||||
start_iter = buf.get_iter_at_offset(context_offset + match.start())
|
||||
end_iter = buf.get_iter_at_offset(context_offset + match.end())
|
||||
index = len(match.group(1)) - 2
|
||||
if index < len(self.leftmargin):
|
||||
self.text_buffer.apply_tag(self.leftmargin[index], start_iter, end_iter)
|
||||
|
||||
for leftindent in self.leftindent:
|
||||
self.text_buffer.remove_tag(leftindent, context_start, context_end)
|
||||
|
||||
matches = re.finditer(self.regex["INDENTEDLIST"], text)
|
||||
for match in matches:
|
||||
start_iter = buf.get_iter_at_offset(context_offset + match.start())
|
||||
end_iter = buf.get_iter_at_offset(context_offset + match.end())
|
||||
index = (len(match.group(1)) - 1) * 2 + len(match.group(2))
|
||||
if index < len(self.leftindent):
|
||||
self.text_buffer.apply_tag(self.leftindent[index], start_iter, end_iter)
|
||||
|
||||
matches = re.finditer(self.regex["HEADINDICATOR"], text)
|
||||
for match in matches:
|
||||
start_iter = buf.get_iter_at_offset(context_offset + match.start())
|
||||
end_iter = buf.get_iter_at_offset(context_offset + match.end())
|
||||
index = len(match.group(1)) - 1
|
||||
if index < len(self.rev_leftmargin):
|
||||
margin = self.rev_leftmargin[index]
|
||||
self.text_buffer.apply_tag(margin, start_iter, end_iter)
|
||||
|
||||
matches = re.finditer(self.regex["HORIZONTALRULE"], text)
|
||||
rulecontext = context_start.copy()
|
||||
rulecontext.forward_lines(3)
|
||||
self.text_buffer.remove_tag(self.centertext, rulecontext, context_end)
|
||||
|
||||
for match in matches:
|
||||
start_iter = buf.get_iter_at_offset(context_offset + match.start())
|
||||
start_iter.forward_chars(2)
|
||||
end_iter = buf.get_iter_at_offset(context_offset + match.end())
|
||||
self.text_buffer.apply_tag(self.centertext, start_iter, end_iter)
|
||||
|
||||
matches = re.finditer(self.regex["HEADLINE"], text)
|
||||
for match in matches:
|
||||
start_iter = buf.get_iter_at_offset(context_offset + match.start())
|
||||
end_iter = buf.get_iter_at_offset(context_offset + match.end())
|
||||
self.text_buffer.apply_tag(self.emph, start_iter, end_iter)
|
||||
|
||||
matches = re.finditer(self.regex["HEADLINE_TWO"], text)
|
||||
self.text_buffer.remove_tag(self.headline_two, rulecontext, context_end)
|
||||
for match in matches:
|
||||
start_iter = buf.get_iter_at_offset(context_offset + match.start())
|
||||
end_iter = buf.get_iter_at_offset(context_offset + match.end())
|
||||
self.text_buffer.apply_tag(self.headline_two, start_iter, end_iter)
|
||||
|
||||
matches = re.finditer(self.regex["TABLE"], text)
|
||||
for match in matches:
|
||||
start_iter = buf.get_iter_at_offset(context_offset + match.start())
|
||||
end_iter = buf.get_iter_at_offset(context_offset + match.end())
|
||||
self.text_buffer.apply_tag(self.table_env, start_iter, end_iter)
|
||||
|
||||
if self.parent.focusmode:
|
||||
self.focusmode_highlight()
|
||||
|
||||
def focusmode_highlight(self):
|
||||
start_document = self.text_buffer.get_start_iter()
|
||||
end_document = self.text_buffer.get_end_iter()
|
||||
|
||||
self.text_buffer.remove_tag(
|
||||
self.unfocused_text,
|
||||
start_document,
|
||||
end_document)
|
||||
|
||||
cursor = self.text_buffer.get_mark("insert")
|
||||
cursor_iter = self.text_buffer.get_iter_at_mark(cursor)
|
||||
|
||||
end_sentence = cursor_iter.copy()
|
||||
end_sentence.forward_sentence_end()
|
||||
|
||||
end_line = cursor_iter.copy()
|
||||
end_line.forward_to_line_end()
|
||||
|
||||
comp = end_line.compare(end_sentence)
|
||||
# if comp < 0, end_line is BEFORE end_sentence
|
||||
if comp <= 0:
|
||||
end_sentence = end_line
|
||||
|
||||
start_sentence = cursor_iter.copy()
|
||||
start_sentence.backward_sentence_start()
|
||||
|
||||
# grey out everything before
|
||||
self.text_buffer.apply_tag(
|
||||
self.unfocused_text,
|
||||
self.text_buffer.get_start_iter(), start_sentence)
|
||||
|
||||
self.text_buffer.apply_tag(
|
||||
self.unfocused_text,
|
||||
end_sentence, self.text_buffer.get_end_iter())
|
||||
|
||||
def set_multiplier(self, multiplier):
|
||||
self.multiplier = multiplier
|
||||
|
||||
def recalculate(self, lm):
|
||||
multiplier = self.multiplier
|
||||
for i in range(0, 6):
|
||||
new_margin = (lm - multiplier) - multiplier * (i + 1)
|
||||
self.rev_leftmargin[i].set_property("left-margin", 0 if new_margin < 0 else new_margin)
|
||||
self.rev_leftmargin[i].set_property("indent", - multiplier * (i + 1) - multiplier)
|
||||
|
||||
for i in range(0, 6):
|
||||
new_margin = (lm - multiplier) + multiplier + multiplier * (i + 1)
|
||||
self.leftmargin[i].set_property("left-margin", 0 if new_margin < 0 else new_margin)
|
||||
self.leftmargin[i].set_property("indent", - (multiplier - 1) * (i + 1) - multiplier)
|
|
@ -18,23 +18,119 @@
|
|||
|
||||
"""this dialog adjusts values in gsettings
|
||||
"""
|
||||
import webbrowser
|
||||
|
||||
import gi
|
||||
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk # pylint: disable=E0611
|
||||
from gi.repository import Gtk, Pango, GLib # pylint: disable=E0611
|
||||
import logging
|
||||
logger = logging.getLogger('uberwriter')
|
||||
|
||||
from uberwriter.helpers import get_builder, show_uri, get_help_uri
|
||||
from uberwriter.helpers import get_builder
|
||||
|
||||
|
||||
class PreferencesDialog:
|
||||
|
||||
class PreferencesDialog(Gtk.Window):
|
||||
__gtype_name__ = "PreferencesDialog"
|
||||
|
||||
def __new__(cls):
|
||||
"""Special static method that's automatically called by Python when
|
||||
constructing a new instance of this class.
|
||||
|
||||
Returns a fully instantiated PreferencesDialog object.
|
||||
"""
|
||||
builder = get_builder('Preferences')
|
||||
new_object = builder.get_object("PreferencesWindow")
|
||||
return new_object
|
||||
formats = [
|
||||
{
|
||||
"name": "Pandoc's Markdown",
|
||||
"format": "markdown",
|
||||
"help": "https://pandoc.org/MANUAL.html#pandocs-markdown"
|
||||
},
|
||||
{
|
||||
"name": "CommonMark",
|
||||
"format": "commonmark",
|
||||
"help": "https://commonmark.org"
|
||||
},
|
||||
{
|
||||
"name": "GitHub Flavored Markdown",
|
||||
"format": "gfm",
|
||||
"help": "https://help.github.com/en/categories/writing-on-github"
|
||||
},
|
||||
{
|
||||
"name": "MultiMarkdown",
|
||||
"format": "markdown_mmd",
|
||||
"help": "https://fletcherpenney.net/multimarkdown"
|
||||
},
|
||||
{
|
||||
"name": "Plain Markdown",
|
||||
"format": "markdown_strict",
|
||||
"help": "https://daringfireball.net/projects/markdown"
|
||||
}
|
||||
]
|
||||
|
||||
def __init__(self, settings):
|
||||
self.settings = settings
|
||||
self.builder = get_builder("Preferences")
|
||||
|
||||
self.dark_mode_auto_switch = self.builder.get_object("dark_mode_auto_switch")
|
||||
self.dark_mode_auto_switch.set_active(self.settings.get_value("dark-mode-auto"))
|
||||
self.dark_mode_auto_switch.connect("state-set", self.on_dark_mode_auto)
|
||||
|
||||
self.dark_mode_switch = self.builder.get_object("dark_mode_switch")
|
||||
self.dark_mode_switch.set_active(self.settings.get_value("dark-mode"))
|
||||
self.dark_mode_switch.connect("state-set", self.on_dark_mode)
|
||||
|
||||
self.spellcheck_switch = self.builder.get_object("spellcheck_switch")
|
||||
self.spellcheck_switch.set_active(self.settings.get_value("spellcheck"))
|
||||
self.spellcheck_switch.connect("state-set", self.on_spellcheck)
|
||||
|
||||
self.gradient_overlay_switch = self.builder.get_object("gradient_overlay_switch")
|
||||
self.gradient_overlay_switch.set_active(self.settings.get_value("gradient-overlay"))
|
||||
self.gradient_overlay_switch.connect("state-set", self.on_gradient_overlay)
|
||||
|
||||
input_format_store = Gtk.ListStore(int, str)
|
||||
input_format = self.settings.get_value("input-format").get_string()
|
||||
input_format_active = 0
|
||||
for i, fmt in enumerate(self.formats):
|
||||
input_format_store.append([i, fmt["name"]])
|
||||
if fmt["format"] == input_format:
|
||||
input_format_active = i
|
||||
self.input_format_combobox = self.builder.get_object("input_format_combobox")
|
||||
self.input_format_combobox.set_model(input_format_store)
|
||||
input_format_renderer = Gtk.CellRendererText()
|
||||
self.input_format_combobox.pack_start(input_format_renderer, True)
|
||||
self.input_format_combobox.add_attribute(input_format_renderer, "text", 1)
|
||||
self.input_format_combobox.set_active(input_format_active)
|
||||
self.input_format_combobox.connect("changed", self.on_input_format)
|
||||
|
||||
self.input_format_help_button = self.builder.get_object("input_format_help_button")
|
||||
self.input_format_help_button.connect('clicked', self.on_input_format_help)
|
||||
|
||||
def show(self, window):
|
||||
preferences_window = self.builder.get_object("PreferencesWindow")
|
||||
preferences_window.set_application(window.get_application())
|
||||
preferences_window.set_transient_for(window)
|
||||
preferences_window.show()
|
||||
|
||||
def on_dark_mode_auto(self, _, state):
|
||||
self.settings.set_value("dark-mode-auto", GLib.Variant.new_boolean(state))
|
||||
if state and self.dark_mode_switch.get_active():
|
||||
self.dark_mode_switch.set_active(GLib.Variant.new_boolean(False))
|
||||
return False
|
||||
|
||||
def on_dark_mode(self, _, state):
|
||||
self.settings.set_value("dark-mode", GLib.Variant.new_boolean(state))
|
||||
if state and self.dark_mode_auto_switch.get_active():
|
||||
self.dark_mode_auto_switch.set_active(GLib.Variant.new_boolean(False))
|
||||
return False
|
||||
|
||||
def on_spellcheck(self, _, state):
|
||||
self.settings.set_value("spellcheck", GLib.Variant.new_boolean(state))
|
||||
return False
|
||||
|
||||
def on_gradient_overlay(self, _, state):
|
||||
self.settings.set_value("gradient-overlay", GLib.Variant.new_boolean(state))
|
||||
return False
|
||||
|
||||
def on_input_format(self, combobox):
|
||||
fmt = self.formats[combobox.get_active()]
|
||||
self.settings.set_value("input-format", GLib.Variant.new_string(fmt["format"]))
|
||||
|
||||
def on_input_format_help(self, _):
|
||||
fmt = self.formats[self.input_format_combobox.get_active()]
|
||||
webbrowser.open(fmt["help"])
|
||||
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
class Scroller:
|
||||
def __init__(self, scrolled_window, source_pos, target_pos):
|
||||
super().__init__()
|
||||
|
||||
self.scrolled_window = scrolled_window
|
||||
self.source_pos = source_pos
|
||||
self.target_pos = target_pos
|
||||
self.duration = max(200, (target_pos - source_pos) / 50) * 1000
|
||||
|
||||
self.is_started = False
|
||||
self.is_setup = False
|
||||
self.start_time = 0
|
||||
self.end_time = 0
|
||||
self.tick_callback_id = 0
|
||||
|
||||
def start(self):
|
||||
self.is_started = True
|
||||
self.tick_callback_id = self.scrolled_window.add_tick_callback(self.on_tick)
|
||||
|
||||
def end(self):
|
||||
self.scrolled_window.remove_tick_callback(self.tick_callback_id)
|
||||
self.is_started = False
|
||||
|
||||
def setup(self, time):
|
||||
self.start_time = time
|
||||
self.end_time = time + self.duration
|
||||
self.is_setup = True
|
||||
|
||||
def on_tick(self, widget, frame_clock):
|
||||
def ease_out_cubic(value):
|
||||
return pow(value - 1, 3) + 1
|
||||
|
||||
now = frame_clock.get_frame_time()
|
||||
if not self.is_setup:
|
||||
self.setup(now)
|
||||
|
||||
if now < self.end_time:
|
||||
time = float(now - self.start_time) / float(self.end_time - self.start_time)
|
||||
else:
|
||||
time = 1
|
||||
self.end()
|
||||
|
||||
time = ease_out_cubic(time)
|
||||
pos = self.source_pos + (time * (self.target_pos - self.source_pos))
|
||||
widget.get_vadjustment().props.value = pos
|
||||
return True
|
||||
|
||||
|
|
@ -14,22 +14,30 @@
|
|||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
### END LICENSE
|
||||
|
||||
import re
|
||||
import logging
|
||||
import re
|
||||
|
||||
import gi
|
||||
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk, Gdk
|
||||
from gi.repository import Gdk
|
||||
|
||||
# from plugins import plugins
|
||||
|
||||
LOGGER = logging.getLogger('uberwriter')
|
||||
|
||||
class SearchAndReplace():
|
||||
|
||||
class SearchAndReplace:
|
||||
"""
|
||||
Adds (regex) search and replace functionality to
|
||||
uberwriter
|
||||
"""
|
||||
def __init__(self, parentwindow):
|
||||
|
||||
def __init__(self, parentwindow, textview):
|
||||
self.parentwindow = parentwindow
|
||||
self.textview = textview
|
||||
self.textbuffer = textview.get_buffer()
|
||||
|
||||
self.box = parentwindow.builder.get_object("searchbar_placeholder")
|
||||
self.box.set_reveal_child(False)
|
||||
self.searchbar = parentwindow.builder.get_object("searchbar")
|
||||
|
@ -41,9 +49,6 @@ class SearchAndReplace():
|
|||
self.open_replace_button = parentwindow.builder.get_object("replace")
|
||||
self.open_replace_button.connect("toggled", self.toggle_replace)
|
||||
|
||||
self.textbuffer = parentwindow.text_buffer
|
||||
self.texteditor = parentwindow.text_editor
|
||||
|
||||
self.nextbutton = parentwindow.builder.get_object("next_result")
|
||||
self.prevbutton = parentwindow.builder.get_object("previous_result")
|
||||
self.regexbutton = parentwindow.builder.get_object("regex")
|
||||
|
@ -63,18 +68,17 @@ class SearchAndReplace():
|
|||
self.prevbutton.connect('clicked', self.scrolltoprev)
|
||||
self.regexbutton.connect('toggled', self.search)
|
||||
self.casesensitivebutton.connect('toggled', self.search)
|
||||
self.highlight = self.textbuffer.create_tag('search_highlight',
|
||||
background="yellow")
|
||||
self.highlight = self.textbuffer.create_tag('search_highlight', background="yellow")
|
||||
|
||||
self.texteditor.connect("focus-in-event", self.focused_texteditor)
|
||||
self.textview.connect("focus-in-event", self.focused_texteditor)
|
||||
|
||||
self.matches = []
|
||||
self.active = 0
|
||||
|
||||
def toggle_replace(self, widget, _data=None):
|
||||
"""toggle the replace box
|
||||
"""
|
||||
if widget.get_active():
|
||||
self.replacebox.set_reveal_child(True)
|
||||
else:
|
||||
self.replacebox.set_reveal_child(False)
|
||||
self.replacebox.set_reveal_child(widget.get_active())
|
||||
|
||||
# TODO: refactorize!
|
||||
def key_pressed(self, _widget, event, _data=None):
|
||||
|
@ -100,13 +104,11 @@ class SearchAndReplace():
|
|||
self.hide()
|
||||
self.open_replace_button.set_active(False)
|
||||
|
||||
|
||||
def search(self, _widget=None, _data=None, scroll=True):
|
||||
searchtext = self.searchentry.get_text()
|
||||
buf = self.textbuffer
|
||||
context_start = buf.get_start_iter()
|
||||
context_end = buf.get_end_iter()
|
||||
text = buf.get_slice(context_start, context_end, False)
|
||||
context_start = self.textbuffer.get_start_iter()
|
||||
context_end = self.textbuffer.get_end_iter()
|
||||
text = self.textbuffer.get_slice(context_start, context_end, False)
|
||||
|
||||
self.textbuffer.remove_tag(self.highlight, context_start, context_end)
|
||||
|
||||
|
@ -121,12 +123,12 @@ class SearchAndReplace():
|
|||
|
||||
matches = re.finditer(searchtext, text, flags)
|
||||
|
||||
self.matchiters = []
|
||||
self.matches = []
|
||||
self.active = 0
|
||||
for match in matches:
|
||||
start_iter = buf.get_iter_at_offset(match.start())
|
||||
end_iter = buf.get_iter_at_offset(match.end())
|
||||
self.matchiters.append((start_iter, end_iter))
|
||||
self.matches.append((match.start(), match.end()))
|
||||
start_iter = self.textbuffer.get_iter_at_offset(match.start())
|
||||
end_iter = self.textbuffer.get_iter_at_offset(match.end())
|
||||
self.textbuffer.apply_tag(self.highlight, start_iter, end_iter)
|
||||
if scroll:
|
||||
self.scrollto(self.active)
|
||||
|
@ -139,43 +141,40 @@ class SearchAndReplace():
|
|||
self.scrollto(self.active - 1)
|
||||
|
||||
def scrollto(self, index):
|
||||
if not self.matchiters:
|
||||
if not self.matches:
|
||||
return
|
||||
if index < len(self.matchiters):
|
||||
self.active = index
|
||||
else:
|
||||
self.active = 0
|
||||
self.active = index % len(self.matches)
|
||||
|
||||
matchiter = self.matchiters[self.active]
|
||||
self.texteditor.get_buffer().select_range(matchiter[0], matchiter[1])
|
||||
|
||||
# self.texteditor.scroll_to_iter(matchiter[0], 0.0, True, 0.0, 0.5)
|
||||
match = self.matches[self.active]
|
||||
start_iter = self.textbuffer.get_iter_at_offset(match[0])
|
||||
end_iter = self.textbuffer.get_iter_at_offset(match[1])
|
||||
self.textbuffer.select_range(start_iter, end_iter)
|
||||
|
||||
def hide(self):
|
||||
self.replacebox.set_reveal_child(False)
|
||||
self.box.set_reveal_child(False)
|
||||
self.textbuffer.remove_tag(self.highlight,
|
||||
self.textbuffer.get_start_iter(),
|
||||
self.textbuffer.get_end_iter())
|
||||
self.texteditor.grab_focus()
|
||||
|
||||
self.textview.grab_focus()
|
||||
|
||||
def replace_clicked(self, _widget, _data=None):
|
||||
self.replace(self.active)
|
||||
|
||||
def replace_all(self, _widget=None, _data=None):
|
||||
while self.matchiters:
|
||||
match = self.matchiters[0]
|
||||
self.textbuffer.delete(match[0], match[1])
|
||||
self.textbuffer.insert(match[0], self.replaceentry.get_text())
|
||||
self.search(scroll=False)
|
||||
for match in reversed(self.matches):
|
||||
self.do_replace(match)
|
||||
self.search(scroll=False)
|
||||
|
||||
def replace(self, searchindex, _inloop=False):
|
||||
match = self.matchiters[searchindex]
|
||||
self.textbuffer.delete(match[0], match[1])
|
||||
self.textbuffer.insert(match[0], self.replaceentry.get_text())
|
||||
self.do_replace(self.matches[searchindex])
|
||||
active = self.active
|
||||
self.search(scroll=False)
|
||||
self.active = active
|
||||
self.parentwindow.MarkupBuffer.markup_buffer()
|
||||
self.scrollto(self.active)
|
||||
|
||||
def do_replace(self, match):
|
||||
start_iter = self.textbuffer.get_iter_at_offset(match[0])
|
||||
end_iter = self.textbuffer.get_iter_at_offset(match[1])
|
||||
self.textbuffer.delete(start_iter, end_iter)
|
||||
start_iter = self.textbuffer.get_iter_at_offset(match[0])
|
||||
self.textbuffer.insert(start_iter, self.replaceentry.get_text())
|
||||
|
|
|
@ -27,7 +27,8 @@ class Settings(Gio.Settings):
|
|||
"""
|
||||
Gio.Settings.__init__(self)
|
||||
|
||||
def new():
|
||||
@classmethod
|
||||
def new(cls):
|
||||
"""
|
||||
Return a new Settings object
|
||||
"""
|
||||
|
|
|
@ -58,7 +58,7 @@ class Sidebar():
|
|||
Presentational class for shelves and files managed by the "sidebar"
|
||||
|
||||
parentwindow:
|
||||
Reference to UberwriterWindow
|
||||
Reference to Window
|
||||
"""
|
||||
def __init__(self, parentwindow):
|
||||
"""
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
import math
|
||||
import re
|
||||
from queue import Queue
|
||||
from threading import Thread
|
||||
|
||||
from gi.repository import GLib
|
||||
|
||||
from uberwriter import helpers
|
||||
|
||||
|
||||
class StatsCounter:
|
||||
"""Counts characters, words, sentences and read time using a background thread."""
|
||||
|
||||
# Regexp that matches any character, except for newlines and subsequent spaces.
|
||||
CHARACTERS = re.compile(r"[^\s]|(?:[^\S\n](?!\s))")
|
||||
|
||||
# Regexp that matches Asian letters, general symbols and hieroglyphs,
|
||||
# as well as sequences of word characters optionally containing non-word characters in-between.
|
||||
WORDS = re.compile(r"[\u3040-\uffff]|(?:\w+\S?\w*)+", re.UNICODE)
|
||||
|
||||
# Regexp that matches sentence-ending punctuation characters, ie. full stop, question mark,
|
||||
# exclamation mark, paragraph, and variants.
|
||||
SENTENCES = re.compile(r"[^\n][.。।෴۔።?՞;⸮؟?፧꘏⳺⳻⁇﹖⁈⁉‽!﹗!՜߹႟᥄\n]+")
|
||||
|
||||
# Regexp that matches paragraphs, ie. anything separated by newlines.
|
||||
PARAGRAPHS = re.compile(r".+\n?")
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.queue = Queue()
|
||||
worker = Thread(target=self.__do_count, name="stats-counter")
|
||||
worker.daemon = True
|
||||
worker.start()
|
||||
|
||||
def count(self, text, callback):
|
||||
"""Count stats for text, calling callback with a result when done.
|
||||
|
||||
The callback argument contains the result, in the form:
|
||||
|
||||
(characters, words, sentences, (hours, minutes, seconds))"""
|
||||
|
||||
self.queue.put((text, callback))
|
||||
|
||||
def stop(self):
|
||||
"""Stops the background worker. StatsCounter shouldn't be used after this."""
|
||||
|
||||
self.queue.put((None, None))
|
||||
|
||||
def __do_count(self):
|
||||
while True:
|
||||
while True:
|
||||
(text, callback) = self.queue.get()
|
||||
if text is None and callback is None:
|
||||
return
|
||||
if self.queue.empty():
|
||||
break
|
||||
|
||||
text = helpers.pandoc_convert(text, to="plain")
|
||||
|
||||
character_count = len(re.findall(self.CHARACTERS, text))
|
||||
|
||||
word_count = len(re.findall(self.WORDS, text))
|
||||
|
||||
sentence_count = len(re.findall(self.SENTENCES, text))
|
||||
|
||||
paragraph_count = len(re.findall(self.PARAGRAPHS, text))
|
||||
|
||||
read_m, read_s = divmod(word_count / 200 * 60, 60)
|
||||
read_h, read_m = divmod(read_m, 60)
|
||||
read_time = (int(read_h), int(read_m), int(read_s))
|
||||
|
||||
GLib.idle_add(
|
||||
callback,
|
||||
(character_count, word_count, sentence_count, paragraph_count, read_time))
|
|
@ -0,0 +1,98 @@
|
|||
import math
|
||||
import re
|
||||
from gettext import gettext as _
|
||||
from queue import Queue
|
||||
from threading import Thread
|
||||
|
||||
from gi.repository import GLib, Gio, Gtk
|
||||
|
||||
from uberwriter import helpers
|
||||
from uberwriter.helpers import get_builder
|
||||
from uberwriter.settings import Settings
|
||||
from uberwriter.stats_counter import StatsCounter
|
||||
|
||||
|
||||
class StatsHandler:
|
||||
"""Shows a default statistic on the stats button, and allows the user to toggle which one."""
|
||||
|
||||
def __init__(self, stats_button, text_view):
|
||||
super().__init__()
|
||||
|
||||
self.stats_button = stats_button
|
||||
self.stats_button.connect("clicked", self.on_stats_button_clicked)
|
||||
self.stats_button.connect("destroy", self.on_destroy)
|
||||
|
||||
self.text_view = text_view
|
||||
self.text_view.get_buffer().connect("changed", self.on_text_changed)
|
||||
|
||||
self.popover = None
|
||||
|
||||
self.characters = 0
|
||||
self.words = 0
|
||||
self.sentences = 0
|
||||
self.paragraphs = 0
|
||||
self.read_time = (0, 0, 0)
|
||||
|
||||
self.settings = Settings.new()
|
||||
self.default_stat = self.settings.get_enum("stat-default")
|
||||
|
||||
self.stats_counter = StatsCounter()
|
||||
|
||||
self.update_default_stat()
|
||||
|
||||
def on_stats_button_clicked(self, _button):
|
||||
self.stats_button.set_state_flags(Gtk.StateFlags.CHECKED, False)
|
||||
|
||||
menu = Gio.Menu()
|
||||
stats = self.settings.props.settings_schema.get_key("stat-default").get_range()[1]
|
||||
for i, stat in enumerate(stats):
|
||||
menu_item = Gio.MenuItem.new(self.get_text_for_stat(i), None)
|
||||
menu_item.set_action_and_target_value("app.stat_default", GLib.Variant.new_string(stat))
|
||||
menu.append_item(menu_item)
|
||||
self.popover = Gtk.Popover.new_from_model(self.stats_button, menu)
|
||||
self.popover.connect('closed', self.on_popover_closed)
|
||||
self.popover.popup()
|
||||
|
||||
def on_popover_closed(self, _popover):
|
||||
self.stats_button.unset_state_flags(Gtk.StateFlags.CHECKED)
|
||||
|
||||
self.popover = None
|
||||
self.text_view.grab_focus()
|
||||
|
||||
def on_text_changed(self, buf):
|
||||
self.stats_counter.count(
|
||||
buf.get_text(buf.get_start_iter(), buf.get_end_iter(), False),
|
||||
self.update_stats)
|
||||
|
||||
def get_text_for_stat(self, stat):
|
||||
if stat == 0:
|
||||
return _("{:n} Characters".format(self.characters))
|
||||
elif stat == 1:
|
||||
return _("{:n} Words".format(self.words))
|
||||
elif stat == 2:
|
||||
return _("{:n} Sentences".format(self.sentences))
|
||||
elif stat == 3:
|
||||
return _("{:n} Paragraphs".format(self.paragraphs))
|
||||
elif stat == 4:
|
||||
return _("{:d}:{:02d}:{:02d} Read Time".format(*self.read_time))
|
||||
else:
|
||||
raise ValueError("Unknown stat {}".format(stat))
|
||||
|
||||
def update_stats(self, stats):
|
||||
(characters, words, sentences, paragraphs, read_time) = stats
|
||||
self.characters = characters
|
||||
self.words = words
|
||||
self.sentences = sentences
|
||||
self.paragraphs = paragraphs
|
||||
self.read_time = read_time
|
||||
self.update_default_stat(False)
|
||||
|
||||
def update_default_stat(self, close_popover=True):
|
||||
stat = self.settings.get_enum("stat-default")
|
||||
text = self.get_text_for_stat(stat)
|
||||
self.stats_button.set_label(text)
|
||||
if close_popover and self.popover:
|
||||
self.popover.popdown()
|
||||
|
||||
def on_destroy(self, _widget):
|
||||
self.stats_counter.stop()
|
|
@ -1,487 +0,0 @@
|
|||
### BEGIN LICENSE
|
||||
# Copyright (C) 2012, Wolf Vollprecht <w.vollprecht@gmail.com>
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License version 3, as published
|
||||
# by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranties of
|
||||
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
|
||||
# PURPOSE. See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
### END LICENSE
|
||||
"""Module for the TextView widgth wich encapsulates management of TextBuffer
|
||||
and TextIter for common functionality, such as cut, copy, paste, undo, redo,
|
||||
and highlighting of text.
|
||||
|
||||
Using
|
||||
#create the TextEditor and set the text
|
||||
editor = TextEditor()
|
||||
editor.text = "Text to add to the editor"
|
||||
|
||||
#use cut, works the same for copy, paste, undo, and redo
|
||||
def __handle_on_cut(self, widget, data=None):
|
||||
self.editor.cut()
|
||||
|
||||
#add string to highlight
|
||||
self.editor.add_highlight("Ubuntu")
|
||||
self.editor.add_highlight("Quickly")
|
||||
|
||||
#remove highlights
|
||||
self.editor.clear_highlight("Ubuntu")
|
||||
self.editor.clear_all_highlight()
|
||||
|
||||
Configuring
|
||||
#Configure as a TextView
|
||||
self.editor.set_wrap_mode(Gtk.WRAP_CHAR)
|
||||
|
||||
#Access the Gtk.TextBuffer if needed
|
||||
buffer = self.editor.get_buffer()
|
||||
|
||||
Extending
|
||||
A TextEditor is Gtk.TextView
|
||||
|
||||
"""
|
||||
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk, Gdk, GObject
|
||||
from uberwriter.format_shortcuts import FormatShortcuts
|
||||
|
||||
import logging
|
||||
LOGGER = logging.getLogger('uberwriter')
|
||||
|
||||
|
||||
class UndoableInsert:
|
||||
"""something that has been inserted into our textbuffer"""
|
||||
def __init__(self, text_iter, text, length):
|
||||
self.offset = text_iter.get_offset()
|
||||
self.text = text
|
||||
self.length = length
|
||||
if self.length > 1 or self.text in ("\r", "\n", " "):
|
||||
self.mergeable = False
|
||||
else:
|
||||
self.mergeable = True
|
||||
|
||||
|
||||
class UndoableDelete:
|
||||
"""something that has ben deleted from our textbuffer"""
|
||||
def __init__(self, text_buffer, start_iter, end_iter):
|
||||
self.text = text_buffer.get_text(start_iter, end_iter, False)
|
||||
self.start = start_iter.get_offset()
|
||||
self.end = end_iter.get_offset()
|
||||
# need to find out if backspace or delete key has been used
|
||||
# so we don't mess up during redo
|
||||
insert_iter = text_buffer.get_iter_at_mark(text_buffer.get_insert())
|
||||
|
||||
self.delete_key_used = bool(insert_iter.get_offset() <= self.start)
|
||||
self.mergeable = not bool(self.end - self.start > 1
|
||||
or self.text in ("\r", "\n", " "))
|
||||
|
||||
|
||||
class TextEditor(Gtk.TextView):
|
||||
"""TextEditor encapsulates management of TextBuffer and TextIter for
|
||||
common functionality, such as cut, copy, paste, undo, redo, and
|
||||
highlighting of text.
|
||||
"""
|
||||
|
||||
__gsignals__ = {
|
||||
'insert-italic': (GObject.SignalFlags.ACTION, None, ()),
|
||||
'insert-bold': (GObject.SignalFlags.ACTION, None, ()),
|
||||
'insert-hrule': (GObject.SignalFlags.ACTION, None, ()),
|
||||
'insert-ulistitem': (GObject.SignalFlags.ACTION, None, ()),
|
||||
'insert-heading': (GObject.SignalFlags.ACTION, None, ()),
|
||||
'insert-strikeout': (GObject.SignalFlags.ACTION, None, ()),
|
||||
'undo': (GObject.SignalFlags.ACTION, None, ()),
|
||||
'redo': (GObject.SignalFlags.ACTION, None, ())
|
||||
}
|
||||
|
||||
def scroll_to_iter(self, iterable, *args):
|
||||
self.get_buffer().place_cursor(iterable)
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Create a TextEditor
|
||||
"""
|
||||
|
||||
Gtk.TextView.__init__(self)
|
||||
self.undo_max = None
|
||||
|
||||
self.insert_event = self.get_buffer().connect("insert-text",
|
||||
self.on_insert_text)
|
||||
self.delete_event = self.get_buffer().connect("delete-range",
|
||||
self.on_delete_range)
|
||||
display = self.get_display()
|
||||
self.clipboard = Gtk.Clipboard.get_for_display(display,
|
||||
Gdk.SELECTION_CLIPBOARD)
|
||||
|
||||
self.undo_stack = []
|
||||
self.redo_stack = []
|
||||
self.not_undoable_action = False
|
||||
self.undo_in_progress = False
|
||||
|
||||
self.can_delete = True
|
||||
self.connect('key-press-event', self.on_key_press_event)
|
||||
|
||||
self.format_shortcuts = FormatShortcuts(self.get_buffer(), self)
|
||||
|
||||
self.connect('insert-italic', self.set_italic)
|
||||
self.connect('insert-bold', self.set_bold)
|
||||
self.connect('insert-strikeout', self.set_strikeout)
|
||||
self.connect('insert-hrule', self.insert_horizontal_rule)
|
||||
self.connect('insert-ulistitem', self.insert_unordered_list_item)
|
||||
self.connect('insert-heading', self.insert_heading)
|
||||
self.connect('redo', self.redo)
|
||||
self.connect('undo', self.undo)
|
||||
|
||||
self.get_style_context().add_class("uberwriter-editor")
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
"""
|
||||
text - a string specifying all the text currently
|
||||
in the TextEditor's buffer.
|
||||
|
||||
This property is read/write.
|
||||
"""
|
||||
start_iter = self.get_buffer().get_iter_at_offset(0)
|
||||
end_iter = self.get_buffer().get_iter_at_offset(-1)
|
||||
return self.get_buffer().get_text(start_iter, end_iter, False)
|
||||
|
||||
@property
|
||||
def can_undo(self):
|
||||
return bool(self.undo_stack)
|
||||
|
||||
@property
|
||||
def can_redo(self):
|
||||
return bool(self.redo_stack)
|
||||
|
||||
@text.setter
|
||||
def text(self, text):
|
||||
self.get_buffer().set_text(text)
|
||||
|
||||
def append(self, text):
|
||||
"""append: appends text to the end of the textbuffer.
|
||||
|
||||
arguments:
|
||||
text - a string to add to the buffer. The text will be the
|
||||
last text in the buffer. The insertion cursor will not be moved.
|
||||
|
||||
"""
|
||||
|
||||
end_iter = self.get_buffer().get_iter_at_offset(-1)
|
||||
self.get_buffer().insert(end_iter, text)
|
||||
|
||||
def prepend(self, text):
|
||||
"""prepend: appends text to the start of the textbuffer.
|
||||
|
||||
arguments:
|
||||
text - a string to add to the buffer. The text will be the
|
||||
first text in the buffer. The insertion cursor will not be moved.
|
||||
|
||||
"""
|
||||
|
||||
start_iter = self.get_buffer().get_iter_at_offset(0)
|
||||
self.get_buffer().insert(start_iter, text)
|
||||
insert_iter = self.get_buffer().get_iter_at_offset(len(text)-1)
|
||||
self.get_buffer().place_cursor(insert_iter)
|
||||
|
||||
def cursor_to_end(self):
|
||||
"""cursor_to_end: moves the insertion curson to the last position
|
||||
in the buffer.
|
||||
|
||||
"""
|
||||
|
||||
end_iter = self.get_buffer().get_iter_at_offset(-1)
|
||||
self.get_buffer().place_cursor(end_iter)
|
||||
|
||||
def cursor_to_start(self):
|
||||
"""cursor_to_start: moves the insertion curson to the first position
|
||||
in the buffer.
|
||||
|
||||
"""
|
||||
|
||||
start_iter = self.get_buffer().get_iter_at_offset(0)
|
||||
self.get_buffer().place_cursor(start_iter)
|
||||
|
||||
def cut(self, _widget=None, _data=None):
|
||||
"""cut: cut currently selected text and put it on the clipboard.
|
||||
This function can be called as a function, or assigned as a signal
|
||||
handler.
|
||||
|
||||
"""
|
||||
|
||||
self.get_buffer().cut_clipboard(self.clipboard, True)
|
||||
|
||||
def copy(self, _widget=None, _data=None):
|
||||
"""copy: copy currently selected text to the clipboard.
|
||||
This function can be called as a function, or assigned as a signal
|
||||
handler.
|
||||
"""
|
||||
self.get_buffer().copy_clipboard(self.clipboard)
|
||||
|
||||
def paste(self, _widget=None, _data=None):
|
||||
"""paste: Insert any text currently on the clipboard into the
|
||||
buffer.
|
||||
This function can be called as a function, or assigned as a signal
|
||||
handler.
|
||||
|
||||
"""
|
||||
|
||||
self.get_buffer().paste_clipboard(self.clipboard, None, True)
|
||||
|
||||
def undo(self, _widget=None, _data=None):
|
||||
"""undo inserts or deletions
|
||||
undone actions are being moved to redo stack"""
|
||||
if not self.undo_stack:
|
||||
return
|
||||
self.begin_not_undoable_action()
|
||||
self.undo_in_progress = True
|
||||
undo_action = self.undo_stack.pop()
|
||||
self.redo_stack.append(undo_action)
|
||||
buf = self.get_buffer()
|
||||
if isinstance(undo_action, UndoableInsert):
|
||||
offset = undo_action.offset
|
||||
start = buf.get_iter_at_offset(offset)
|
||||
stop = buf.get_iter_at_offset(
|
||||
offset + undo_action.length
|
||||
)
|
||||
buf.place_cursor(start)
|
||||
buf.delete(start, stop)
|
||||
else:
|
||||
start = buf.get_iter_at_offset(undo_action.start)
|
||||
buf.insert(start, undo_action.text)
|
||||
if undo_action.delete_key_used:
|
||||
buf.place_cursor(start)
|
||||
else:
|
||||
stop = buf.get_iter_at_offset(undo_action.end)
|
||||
buf.place_cursor(stop)
|
||||
self.end_not_undoable_action()
|
||||
self.undo_in_progress = False
|
||||
|
||||
def redo(self, _widget=None, _data=None):
|
||||
"""redo inserts or deletions
|
||||
|
||||
redone actions are moved to undo stack"""
|
||||
if not self.redo_stack:
|
||||
return
|
||||
self.begin_not_undoable_action()
|
||||
self.undo_in_progress = True
|
||||
redo_action = self.redo_stack.pop()
|
||||
self.undo_stack.append(redo_action)
|
||||
buf = self.get_buffer()
|
||||
if isinstance(redo_action, UndoableInsert):
|
||||
start = buf.get_iter_at_offset(redo_action.offset)
|
||||
buf.insert(start, redo_action.text)
|
||||
new_cursor_pos = buf.get_iter_at_offset(
|
||||
redo_action.offset + redo_action.length
|
||||
)
|
||||
buf.place_cursor(new_cursor_pos)
|
||||
else:
|
||||
start = buf.get_iter_at_offset(redo_action.start)
|
||||
stop = buf.get_iter_at_offset(redo_action.end)
|
||||
buf.delete(start, stop)
|
||||
buf.place_cursor(start)
|
||||
self.end_not_undoable_action()
|
||||
self.undo_in_progress = False
|
||||
|
||||
def on_insert_text(self, _textbuffer, text_iter, text, _length):
|
||||
"""
|
||||
_on_insert: internal function to handle programatically inserted
|
||||
text. Do not call directly.
|
||||
"""
|
||||
def can_be_merged(prev, cur):
|
||||
"""see if we can merge multiple inserts here
|
||||
|
||||
will try to merge words or whitespace
|
||||
can't merge if prev and cur are not mergeable in the first place
|
||||
can't merge when user set the input bar somewhere else
|
||||
can't merge across word boundaries"""
|
||||
whitespace = (' ', '\t')
|
||||
if not cur.mergeable or not prev.mergeable:
|
||||
return False
|
||||
if cur.offset != (prev.offset + prev.length):
|
||||
return False
|
||||
if cur.text in whitespace and not prev.text in whitespace:
|
||||
return False
|
||||
if prev.text in whitespace and not cur.text in whitespace:
|
||||
return False
|
||||
return True
|
||||
|
||||
if not self.undo_in_progress:
|
||||
self.redo_stack = []
|
||||
if self.not_undoable_action:
|
||||
return
|
||||
|
||||
undo_action = UndoableInsert(text_iter, text, len(text))
|
||||
try:
|
||||
prev_insert = self.undo_stack.pop()
|
||||
except IndexError:
|
||||
self.undo_stack.append(undo_action)
|
||||
return
|
||||
if not isinstance(prev_insert, UndoableInsert):
|
||||
self.undo_stack.append(prev_insert)
|
||||
self.undo_stack.append(undo_action)
|
||||
return
|
||||
if can_be_merged(prev_insert, undo_action):
|
||||
prev_insert.length += undo_action.length
|
||||
prev_insert.text += undo_action.text
|
||||
self.undo_stack.append(prev_insert)
|
||||
else:
|
||||
self.undo_stack.append(prev_insert)
|
||||
self.undo_stack.append(undo_action)
|
||||
|
||||
def on_delete_range(self, text_buffer, start_iter, end_iter):
|
||||
"""On delete
|
||||
"""
|
||||
def can_be_merged(prev, cur):
|
||||
"""see if we can merge multiple deletions here
|
||||
|
||||
will try to merge words or whitespace
|
||||
can't merge if prev and cur are not mergeable in the first place
|
||||
can't merge if delete and backspace key were both used
|
||||
can't merge across word boundaries"""
|
||||
|
||||
whitespace = (' ', '\t')
|
||||
if not cur.mergeable or not prev.mergeable:
|
||||
return False
|
||||
if prev.delete_key_used != cur.delete_key_used:
|
||||
return False
|
||||
if prev.start != cur.start and prev.start != cur.end:
|
||||
return False
|
||||
if cur.text not in whitespace and \
|
||||
prev.text in whitespace:
|
||||
return False
|
||||
if cur.text in whitespace and \
|
||||
prev.text not in whitespace:
|
||||
return False
|
||||
return True
|
||||
|
||||
if not self.undo_in_progress:
|
||||
self.redo_stack = []
|
||||
if self.not_undoable_action:
|
||||
return
|
||||
undo_action = UndoableDelete(text_buffer, start_iter, end_iter)
|
||||
try:
|
||||
prev_delete = self.undo_stack.pop()
|
||||
except IndexError:
|
||||
self.undo_stack.append(undo_action)
|
||||
return
|
||||
if not isinstance(prev_delete, UndoableDelete):
|
||||
self.undo_stack.append(prev_delete)
|
||||
self.undo_stack.append(undo_action)
|
||||
return
|
||||
if can_be_merged(prev_delete, undo_action):
|
||||
if prev_delete.start == undo_action.start: # delete key used
|
||||
prev_delete.text += undo_action.text
|
||||
prev_delete.end += (undo_action.end - undo_action.start)
|
||||
else: # Backspace used
|
||||
prev_delete.text = "%s%s" % (undo_action.text,
|
||||
prev_delete.text)
|
||||
prev_delete.start = undo_action.start
|
||||
self.undo_stack.append(prev_delete)
|
||||
else:
|
||||
self.undo_stack.append(prev_delete)
|
||||
self.undo_stack.append(undo_action)
|
||||
|
||||
def begin_not_undoable_action(self):
|
||||
"""don't record the next actions
|
||||
toggles self.not_undoable_action"""
|
||||
self.not_undoable_action = True
|
||||
|
||||
def end_not_undoable_action(self):
|
||||
"""record next actions
|
||||
toggles self.not_undoable_action"""
|
||||
self.not_undoable_action = False
|
||||
|
||||
def on_key_press_event(self, widget, event):
|
||||
if widget == self and not self.can_delete:
|
||||
return event.keyval == Gdk.KEY_BackSpace or event.keyval == Gdk.KEY_Delete
|
||||
else:
|
||||
return False
|
||||
|
||||
def set_italic(self, _widget, _data=None):
|
||||
"""Ctrl + I"""
|
||||
self.format_shortcuts.italic()
|
||||
|
||||
def set_bold(self, _widget, _data=None):
|
||||
"""Ctrl + Shift + D"""
|
||||
self.format_shortcuts.bold()
|
||||
|
||||
def set_strikeout(self, _widget, _data=None):
|
||||
"""Ctrl + B"""
|
||||
self.format_shortcuts.strikeout()
|
||||
|
||||
def insert_horizontal_rule(self, _widget, _data=None):
|
||||
"""Ctrl + R"""
|
||||
self.format_shortcuts.rule()
|
||||
|
||||
def insert_unordered_list_item(self, _widget, _data=None):
|
||||
"""Ctrl + U"""
|
||||
self.format_shortcuts.unordered_list_item()
|
||||
|
||||
def insert_ordered_list(self, _widget, _data=None):
|
||||
"""CTRL + O"""
|
||||
self.format_shortcuts.ordered_list_item()
|
||||
|
||||
def insert_heading(self, _widget, _data=None):
|
||||
"""CTRL + H"""
|
||||
self.format_shortcuts.heading()
|
||||
|
||||
|
||||
class TestWindow(Gtk.Window):
|
||||
"""For testing and demonstrating AsycnTaskProgressBox.
|
||||
|
||||
"""
|
||||
def __init__(self):
|
||||
# create a window a VBox to hold the controls
|
||||
Gtk.Window.__init__(self)
|
||||
self.set_title("TextEditor Test Window")
|
||||
windowbox = Gtk.VBox(homogeneous=False, spacing=2)
|
||||
windowbox.show()
|
||||
self.add(windowbox)
|
||||
self.editor = TextEditor()
|
||||
self.editor.show()
|
||||
windowbox.pack_end(self.editor, True, True, 0)
|
||||
self.set_size_request(200, 200)
|
||||
self.show()
|
||||
self.maximize()
|
||||
|
||||
self.connect("destroy", Gtk.main_quit)
|
||||
self.editor.text = "this is some inserted text"
|
||||
self.editor.append("\nLine 3")
|
||||
self.editor.prepend("Line1\n")
|
||||
self.editor.cursor_to_end()
|
||||
self.editor.cursor_to_start()
|
||||
self.editor.undo_max = 100
|
||||
cut_button = Gtk.Button(label="Cut")
|
||||
cut_button.connect("clicked", self.editor.cut)
|
||||
cut_button.show()
|
||||
windowbox.pack_start(cut_button, False, False, 0)
|
||||
|
||||
copy_button = Gtk.Button(label="Copy")
|
||||
copy_button.connect("clicked", self.editor.copy)
|
||||
copy_button.show()
|
||||
windowbox.pack_start(copy_button, False, False, 0)
|
||||
|
||||
paste_button = Gtk.Button(label="Paste")
|
||||
paste_button.connect("clicked", self.editor.paste)
|
||||
paste_button.show()
|
||||
windowbox.pack_start(paste_button, False, False, 0)
|
||||
|
||||
undo_button = Gtk.Button(label="Undo")
|
||||
undo_button.connect("clicked", self.editor.undo)
|
||||
undo_button.show()
|
||||
windowbox.pack_start(undo_button, False, False, 0)
|
||||
|
||||
redo_button = Gtk.Button(label="Redo")
|
||||
redo_button.connect("clicked", self.editor.redo)
|
||||
redo_button.show()
|
||||
windowbox.pack_start(redo_button, False, False, 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
TEST = TestWindow()
|
||||
Gtk.main()
|
|
@ -0,0 +1,205 @@
|
|||
import gi
|
||||
|
||||
from uberwriter.inline_preview import InlinePreview
|
||||
from uberwriter.text_view_format_inserter import FormatInserter
|
||||
from uberwriter.text_view_markup_handler import MarkupHandler
|
||||
from uberwriter.text_view_undo_redo_handler import UndoRedoHandler
|
||||
from uberwriter.text_view_drag_drop_handler import DragDropHandler, TARGET_URI, TARGET_TEXT
|
||||
from uberwriter.scroller import Scroller
|
||||
|
||||
gi.require_version('Gtk', '3.0')
|
||||
gi.require_version('Gspell', '1')
|
||||
from gi.repository import Gtk, Gdk, GObject, GLib, Gspell
|
||||
|
||||
import logging
|
||||
LOGGER = logging.getLogger('uberwriter')
|
||||
|
||||
|
||||
class TextView(Gtk.TextView):
|
||||
"""UberwriterTextView encapsulates all the features around the editor.
|
||||
|
||||
It combines the following:
|
||||
- Undo / redo (via TextBufferUndoRedoHandler)
|
||||
- Format shortcuts (via TextBufferShortcutInserter)
|
||||
- Markup (via TextBufferMarkupHandler)
|
||||
- Preview popover (via TextBufferMarkupHandler)
|
||||
- Drag and drop (via TextViewDragDropHandler)
|
||||
- Scrolling (via TextViewScroller)
|
||||
- The various modes supported by UberWriter (eg. Focus Mode, Hemingway Mode)
|
||||
"""
|
||||
|
||||
__gsignals__ = {
|
||||
'insert-italic': (GObject.SignalFlags.ACTION, None, ()),
|
||||
'insert-bold': (GObject.SignalFlags.ACTION, None, ()),
|
||||
'insert-hrule': (GObject.SignalFlags.ACTION, None, ()),
|
||||
'insert-listitem': (GObject.SignalFlags.ACTION, None, ()),
|
||||
'insert-header': (GObject.SignalFlags.ACTION, None, ()),
|
||||
'insert-strikethrough': (GObject.SignalFlags.ACTION, None, ()),
|
||||
'undo': (GObject.SignalFlags.ACTION, None, ()),
|
||||
'redo': (GObject.SignalFlags.ACTION, None, ())
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
# Appearance
|
||||
self.set_wrap_mode(Gtk.WrapMode.WORD_CHAR)
|
||||
self.set_pixels_above_lines(4)
|
||||
self.set_pixels_below_lines(4)
|
||||
self.set_pixels_inside_wrap(8)
|
||||
self.get_style_context().add_class('uberwriter-editor')
|
||||
|
||||
# General behavior
|
||||
self.get_buffer().connect('changed', self.on_text_changed)
|
||||
self.connect('size-allocate', self.on_size_allocate)
|
||||
|
||||
# Spell checking
|
||||
self.gspell_view = Gspell.TextView.get_from_gtk_text_view(self)
|
||||
self.gspell_view.basic_setup()
|
||||
|
||||
# Undo / redo
|
||||
self.undo_redo = UndoRedoHandler()
|
||||
self.get_buffer().connect('insert-text', self.undo_redo.on_insert_text)
|
||||
self.get_buffer().connect('delete-range', self.undo_redo.on_delete_range)
|
||||
self.connect('undo', self.undo_redo.undo)
|
||||
self.connect('redo', self.undo_redo.redo)
|
||||
|
||||
# Format shortcuts
|
||||
self.shortcut = FormatInserter()
|
||||
self.connect('insert-italic', self.shortcut.insert_italic)
|
||||
self.connect('insert-bold', self.shortcut.insert_bold)
|
||||
self.connect('insert-strikethrough', self.shortcut.insert_strikethrough)
|
||||
self.connect('insert-hrule', self.shortcut.insert_horizontal_rule)
|
||||
self.connect('insert-listitem', self.shortcut.insert_list_item)
|
||||
self.connect('insert-header', self.shortcut.insert_header)
|
||||
|
||||
# Markup
|
||||
self.markup = MarkupHandler(self)
|
||||
self.connect('style-updated', self.markup.on_style_updated)
|
||||
|
||||
# Preview popover
|
||||
self.preview_popover = InlinePreview(self)
|
||||
|
||||
# Drag and drop
|
||||
self.drag_drop = DragDropHandler(self, TARGET_URI, TARGET_TEXT)
|
||||
|
||||
# Scrolling
|
||||
self.scroller = None
|
||||
self.get_buffer().connect('mark-set', self.on_mark_set)
|
||||
|
||||
# Focus mode
|
||||
self.focus_mode = False
|
||||
self.connect('button-release-event', self.on_button_release_event)
|
||||
|
||||
# Hemingway mode
|
||||
self.hemingway_mode = False
|
||||
self.connect('key-press-event', self.on_key_press_event)
|
||||
|
||||
def get_text(self):
|
||||
text_buffer = self.get_buffer()
|
||||
start_iter = text_buffer.get_start_iter()
|
||||
end_iter = text_buffer.get_end_iter()
|
||||
return text_buffer.get_text(start_iter, end_iter, False)
|
||||
|
||||
def set_text(self, text):
|
||||
text_buffer = self.get_buffer()
|
||||
text_buffer.set_text(text)
|
||||
|
||||
def on_text_changed(self, *_):
|
||||
self.markup.apply()
|
||||
GLib.idle_add(self.scroll_to)
|
||||
|
||||
def on_size_allocate(self, *_):
|
||||
self.update_vertical_margin()
|
||||
self.markup.update_margins_indents()
|
||||
|
||||
def set_focus_mode(self, focus_mode):
|
||||
"""Toggle focus mode.
|
||||
|
||||
When in focus mode, the cursor sits in the middle of the text view,
|
||||
and the surrounding text is greyed out."""
|
||||
|
||||
self.focus_mode = focus_mode
|
||||
self.gspell_view.set_inline_spell_checking(not focus_mode)
|
||||
self.update_vertical_margin()
|
||||
self.markup.apply()
|
||||
self.scroll_to()
|
||||
|
||||
def update_vertical_margin(self):
|
||||
if self.focus_mode:
|
||||
height = self.get_allocation().height
|
||||
self.props.top_margin = height / 2
|
||||
self.props.bottom_margin = height / 2
|
||||
else:
|
||||
self.props.top_margin = 80
|
||||
self.props.bottom_margin = 64
|
||||
|
||||
def on_button_release_event(self, _widget, _event):
|
||||
if self.focus_mode:
|
||||
self.markup.apply()
|
||||
return False
|
||||
|
||||
def set_hemingway_mode(self, hemingway_mode):
|
||||
"""Toggle hemingway mode.
|
||||
|
||||
When in hemingway mode, the backspace and delete keys are ignored."""
|
||||
|
||||
self.hemingway_mode = hemingway_mode
|
||||
|
||||
def on_key_press_event(self, _widget, event):
|
||||
if self.hemingway_mode:
|
||||
return event.keyval == Gdk.KEY_BackSpace or event.keyval == Gdk.KEY_Delete
|
||||
else:
|
||||
return False
|
||||
|
||||
def clear(self):
|
||||
"""Clear text and undo history"""
|
||||
|
||||
self.get_buffer().set_text('')
|
||||
self.undo_redo.clear()
|
||||
|
||||
def scroll_to(self, mark=None):
|
||||
"""Scrolls if needed to ensure mark is visible.
|
||||
|
||||
If mark is unspecified, the cursor is used."""
|
||||
|
||||
margin = 32
|
||||
scrolled_window = self.get_ancestor(Gtk.ScrolledWindow.__gtype__)
|
||||
if not scrolled_window:
|
||||
return
|
||||
va = scrolled_window.get_vadjustment()
|
||||
if va.props.page_size < margin * 2:
|
||||
return
|
||||
|
||||
text_buffer = self.get_buffer()
|
||||
if mark:
|
||||
mark_iter = text_buffer.get_iter_at_mark(mark)
|
||||
else:
|
||||
mark_iter = text_buffer.get_iter_at_mark(text_buffer.get_insert())
|
||||
mark_rect = self.get_iter_location(mark_iter)
|
||||
|
||||
pos_y = mark_rect.y + mark_rect.height + self.props.top_margin
|
||||
pos = pos_y - va.props.value
|
||||
target_pos = None
|
||||
if self.focus_mode:
|
||||
if pos != (va.props.page_size * 0.5):
|
||||
target_pos = pos_y - (va.props.page_size * 0.5)
|
||||
elif pos > va.props.page_size - margin:
|
||||
target_pos = pos_y - va.props.page_size + margin
|
||||
elif pos < margin:
|
||||
target_pos = pos_y - margin
|
||||
|
||||
if self.scroller and self.scroller.is_started:
|
||||
self.scroller.end()
|
||||
if target_pos:
|
||||
self.scroller = Scroller(scrolled_window, va.props.value, target_pos)
|
||||
self.scroller.start()
|
||||
|
||||
def on_mark_set(self, _text_buffer, _location, mark, _data=None):
|
||||
if mark.get_name() == 'insert':
|
||||
self.markup.apply()
|
||||
if self.focus_mode:
|
||||
self.scroll_to(mark)
|
||||
elif mark.get_name() == 'gtk_drag_target':
|
||||
self.scroll_to(mark)
|
||||
return True
|
|
@ -0,0 +1,62 @@
|
|||
import mimetypes
|
||||
import urllib
|
||||
|
||||
from gi.repository import Gtk
|
||||
|
||||
(TARGET_URI, TARGET_TEXT) = range(2)
|
||||
|
||||
|
||||
class DragDropHandler:
|
||||
TARGET_URI = None
|
||||
|
||||
def __init__(self, text_view, *targets):
|
||||
super().__init__()
|
||||
|
||||
self.target_list = Gtk.TargetList.new([])
|
||||
if TARGET_URI in targets:
|
||||
self.target_list.add_uri_targets(TARGET_URI)
|
||||
if TARGET_TEXT in targets:
|
||||
self.target_list.add_text_targets(TARGET_TEXT)
|
||||
|
||||
text_view.drag_dest_set_target_list(self.target_list)
|
||||
text_view.connect_after('drag-data-received', self.on_drag_data_received)
|
||||
|
||||
def on_drag_data_received(self, text_view, drag_context, _x, _y, data, info, time):
|
||||
"""Handle drag and drop events"""
|
||||
|
||||
text_buffer = text_view.get_buffer()
|
||||
if info == TARGET_URI:
|
||||
uris = data.get_uris()
|
||||
for uri in uris:
|
||||
uri = urllib.parse.unquote_plus(uri)
|
||||
mime = mimetypes.guess_type(uri)
|
||||
|
||||
if mime[0] is not None and mime[0].startswith('image'):
|
||||
if uri.startswith("file://"):
|
||||
uri = uri[7:]
|
||||
text = "![Image caption](%s)" % uri
|
||||
limit_left = 2
|
||||
limit_right = 23
|
||||
else:
|
||||
text = "[Link description](%s)" % uri
|
||||
limit_left = 1
|
||||
limit_right = 22
|
||||
text_buffer.place_cursor(text_buffer.get_iter_at_mark(
|
||||
text_buffer.get_mark('gtk_drag_target')))
|
||||
text_buffer.insert_at_cursor(text)
|
||||
insert_mark = text_buffer.get_insert()
|
||||
selection_bound = text_buffer.get_selection_bound()
|
||||
cursor_iter = text_buffer.get_iter_at_mark(insert_mark)
|
||||
cursor_iter.backward_chars(len(text) - limit_left)
|
||||
text_buffer.move_mark(insert_mark, cursor_iter)
|
||||
cursor_iter.forward_chars(limit_right)
|
||||
text_buffer.move_mark(selection_bound, cursor_iter)
|
||||
|
||||
elif info == TARGET_TEXT:
|
||||
text_buffer.place_cursor(text_buffer.get_iter_at_mark(
|
||||
text_buffer.get_mark('gtk_drag_target')))
|
||||
text_buffer.insert_at_cursor(data.get_text())
|
||||
|
||||
Gtk.drag_finish(drag_context, True, True, time)
|
||||
text_view.get_window().present_with_time(time)
|
||||
return False
|
|
@ -0,0 +1,154 @@
|
|||
from gettext import gettext as _
|
||||
|
||||
|
||||
class FormatInserter:
|
||||
"""Manages insertion of formatting.
|
||||
|
||||
Methods can be called directly, as well as be used as signal callbacks."""
|
||||
|
||||
def insert_italic(self, text_view, _data=None):
|
||||
"""Insert italic or mark a selection as bold"""
|
||||
|
||||
self.__wrap(text_view, "_", _("italic text"))
|
||||
|
||||
def insert_bold(self, text_view, _data=None):
|
||||
"""Insert bold or mark a selection as bold"""
|
||||
|
||||
self.__wrap(text_view, "**", _("bold text"))
|
||||
|
||||
def insert_strikethrough(self, text_view, _data=None):
|
||||
"""Insert strikethrough or mark a selection as strikethrough"""
|
||||
|
||||
self.__wrap(text_view, "~~", _("strikethrough text"))
|
||||
|
||||
def insert_horizontal_rule(self, text_view, _data=None):
|
||||
"""Insert horizontal rule"""
|
||||
|
||||
text_buffer = text_view.get_buffer()
|
||||
text_buffer.insert_at_cursor("\n\n---\n")
|
||||
text_view.scroll_mark_onscreen(text_buffer.get_insert())
|
||||
|
||||
def insert_list_item(self, text_view, _data=None):
|
||||
"""Insert list item or mark a selection as list item"""
|
||||
|
||||
text_buffer = text_view.get_buffer()
|
||||
if text_buffer.get_has_selection():
|
||||
(start, end) = text_buffer.get_selection_bounds()
|
||||
if start.starts_line():
|
||||
text = text_buffer.get_text(start, end, False)
|
||||
if text.startswith(("- ", "* ", "+ ")):
|
||||
delete_end = start.forward_chars(2)
|
||||
text_buffer.delete(start, delete_end)
|
||||
else:
|
||||
text_buffer.insert(start, "- ")
|
||||
else:
|
||||
helptext = _("Item")
|
||||
text_length = len(helptext)
|
||||
|
||||
cursor_mark = text_buffer.get_insert()
|
||||
cursor_iter = text_buffer.get_iter_at_mark(cursor_mark)
|
||||
|
||||
start_ext = cursor_iter.copy()
|
||||
start_ext.backward_lines(3)
|
||||
text = text_buffer.get_text(cursor_iter, start_ext, False)
|
||||
lines = text.splitlines()
|
||||
|
||||
for line in reversed(lines):
|
||||
if line and line.startswith(("- ", "* ", "+ ")):
|
||||
if cursor_iter.starts_line():
|
||||
text_buffer.insert_at_cursor(line[:2] + helptext)
|
||||
else:
|
||||
text_buffer.insert_at_cursor(
|
||||
"\n" + line[:2] + helptext)
|
||||
break
|
||||
else:
|
||||
if not lines[-1] and not lines[-2]:
|
||||
text_buffer.insert_at_cursor("- " + helptext)
|
||||
elif not lines[-1]:
|
||||
if cursor_iter.starts_line():
|
||||
text_buffer.insert_at_cursor("- " + helptext)
|
||||
else:
|
||||
text_buffer.insert_at_cursor("\n- " + helptext)
|
||||
else:
|
||||
text_buffer.insert_at_cursor("\n\n- " + helptext)
|
||||
break
|
||||
|
||||
self.__select_text(text_view, 0, text_length)
|
||||
|
||||
def insert_ordered_list_item(self, _text_view, _data=None):
|
||||
# TODO: implement ordered lists
|
||||
pass
|
||||
|
||||
def insert_header(self, text_view, _data=None):
|
||||
"""Insert header or mark a selection as a list header"""
|
||||
|
||||
text_buffer = text_view.get_buffer()
|
||||
if text_buffer.get_has_selection():
|
||||
(start, end) = text_buffer.get_selection_bounds()
|
||||
text = text_buffer.get_text(start, end, False)
|
||||
text_buffer.delete(start, end)
|
||||
else:
|
||||
text = _("Header")
|
||||
|
||||
text_buffer.insert_at_cursor("#" + " " + text)
|
||||
self.__select_text(text_view, 0, len(text))
|
||||
|
||||
@staticmethod
|
||||
def __wrap(text_view, wrap, helptext=""):
|
||||
"""Inserts wrap format to the selected text (helper text when nothing selected)"""
|
||||
text_buffer = text_view.get_buffer()
|
||||
if text_buffer.get_has_selection():
|
||||
# Find current highlighting
|
||||
(start, end) = text_buffer.get_selection_bounds()
|
||||
moved = False
|
||||
if (start.get_offset() >= len(wrap) and
|
||||
end.get_offset() <= text_buffer.get_char_count() - len(wrap)):
|
||||
moved = True
|
||||
ext_start = start.copy()
|
||||
ext_start.backward_chars(len(wrap))
|
||||
ext_end = end.copy()
|
||||
ext_end.forward_chars(len(wrap))
|
||||
text = text_buffer.get_text(ext_start, ext_end, True)
|
||||
else:
|
||||
text = text_buffer.get_text(start, end, True)
|
||||
|
||||
if moved and text.startswith(wrap) and text.endswith(wrap):
|
||||
text = text[len(wrap):-len(wrap)]
|
||||
new_text = text
|
||||
text_buffer.delete(ext_start, ext_end)
|
||||
move_back = 0
|
||||
else:
|
||||
if moved:
|
||||
text = text[len(wrap):-len(wrap)]
|
||||
new_text = text.lstrip().rstrip()
|
||||
text = text.replace(new_text, wrap + new_text + wrap)
|
||||
|
||||
text_buffer.delete(start, end)
|
||||
move_back = len(wrap)
|
||||
|
||||
text_buffer.insert_at_cursor(text)
|
||||
text_length = len(new_text)
|
||||
|
||||
else:
|
||||
text_buffer.insert_at_cursor(wrap + helptext + wrap)
|
||||
text_length = len(helptext)
|
||||
move_back = len(wrap)
|
||||
|
||||
cursor_mark = text_buffer.get_insert()
|
||||
cursor_iter = text_buffer.get_iter_at_mark(cursor_mark)
|
||||
cursor_iter.backward_chars(move_back)
|
||||
text_buffer.move_mark_by_name('selection_bound', cursor_iter)
|
||||
cursor_iter.backward_chars(text_length)
|
||||
text_buffer.move_mark_by_name('insert', cursor_iter)
|
||||
|
||||
@staticmethod
|
||||
def __select_text(text_view, offset, length):
|
||||
"""Selects text starting at the current cursor minus offset, length characters."""
|
||||
|
||||
text_buffer = text_view.get_buffer()
|
||||
cursor_mark = text_buffer.get_insert()
|
||||
cursor_iter = text_buffer.get_iter_at_mark(cursor_mark)
|
||||
cursor_iter.backward_chars(offset)
|
||||
text_buffer.move_mark_by_name('selection_bound', cursor_iter)
|
||||
cursor_iter.backward_chars(length)
|
||||
text_buffer.move_mark_by_name('insert', cursor_iter)
|
|
@ -0,0 +1,307 @@
|
|||
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
|
||||
### BEGIN LICENSE
|
||||
# Copyright (C) 2012, Wolf Vollprecht <w.vollprecht@gmail.com>
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License version 3, as published
|
||||
# by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranties of
|
||||
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
|
||||
# PURPOSE. See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
### END LICENSE
|
||||
|
||||
import re
|
||||
|
||||
import gi
|
||||
from gi.overrides import GLib
|
||||
|
||||
from uberwriter import helpers
|
||||
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Pango
|
||||
|
||||
|
||||
class MarkupHandler:
|
||||
# Maximum number of characters for which to markup synchronously.
|
||||
max_char_sync = 100000
|
||||
|
||||
# Regular expressions for various markdown constructs.
|
||||
regex = {
|
||||
"ITALIC": re.compile(r"(\*|_)(.+?)\1"),
|
||||
"BOLD": re.compile(r"(\*\*|__)(.+?)\1"),
|
||||
"BOLDITALIC": re.compile(r"(\*\*\*|___)(.+?)\1"),
|
||||
"STRIKETHROUGH": re.compile(r"~~.+?~~"),
|
||||
"LINK": re.compile(r"(\[).*(\]\(.+?\))"),
|
||||
"HORIZONTALRULE": re.compile(r"\n\n([ ]{0,3}[*\-_]{3,}[ ]*)\n", re.MULTILINE),
|
||||
"LIST": re.compile(r"^((?:\t|[ ]{4})*)[\-*+] .+", re.MULTILINE),
|
||||
"NUMERICLIST": re.compile(r"^((\d|[a-z]|#)+[.)]) ", re.MULTILINE),
|
||||
"NUMBEREDLIST": re.compile(r"^((?:\t|[ ]{4})*)((?:\d|[a-z])+[.)]) .+", re.MULTILINE),
|
||||
"BLOCKQUOTE": re.compile(r"^[ ]{0,3}(?:>|(?:> )+).+", re.MULTILINE),
|
||||
"HEADER": re.compile(r"^[ ]{0,3}(#{1,6}) [^\n]+", re.MULTILINE),
|
||||
"HEADER_UNDER": re.compile(r"^[ ]{0,3}\w.+\n[ ]{0,3}[=\-]{3,}", re.MULTILINE),
|
||||
"CODE": re.compile(r"(?:^|\n)[ ]{0,3}(([`~]{3}).+?[ ]{0,3}\2)(?:\n|$)", re.DOTALL),
|
||||
"TABLE": re.compile(r"^[\-+]{5,}\n(.+?)\n[\-+]{5,}\n", re.DOTALL),
|
||||
"MATH": re.compile(r"[$]{1,2}([^` ].+?[^`\\ ])[$]{1,2}"),
|
||||
}
|
||||
|
||||
def __init__(self, text_view):
|
||||
self.text_view = text_view
|
||||
self.text_buffer = text_view.get_buffer()
|
||||
|
||||
# Styles
|
||||
buffer = self.text_buffer
|
||||
|
||||
self.italic = buffer.create_tag('italic',
|
||||
weight=Pango.Weight.NORMAL,
|
||||
style=Pango.Style.ITALIC)
|
||||
|
||||
self.bold = buffer.create_tag('bold',
|
||||
weight=Pango.Weight.BOLD,
|
||||
style=Pango.Style.NORMAL)
|
||||
|
||||
self.bolditalic = buffer.create_tag('bolditalic',
|
||||
weight=Pango.Weight.BOLD,
|
||||
style=Pango.Style.ITALIC)
|
||||
|
||||
self.strikethrough = buffer.create_tag('strikethrough', strikethrough=True)
|
||||
|
||||
self.horizontalrule = buffer.create_tag('centertext',
|
||||
justification=Gtk.Justification.CENTER)
|
||||
|
||||
self.plaintext = buffer.create_tag('plaintext',
|
||||
weight=Pango.Weight.NORMAL,
|
||||
style=Pango.Style.NORMAL,
|
||||
strikethrough=False,
|
||||
justification=Gtk.Justification.LEFT)
|
||||
|
||||
self.table = buffer.create_tag('table')
|
||||
self.table.set_property('wrap-mode', Gtk.WrapMode.NONE)
|
||||
self.table.set_property('pixels-above-lines', 0)
|
||||
self.table.set_property('pixels-below-lines', 0)
|
||||
|
||||
self.mathtext = buffer.create_tag('mathtext')
|
||||
|
||||
self.graytext = buffer.create_tag('graytext',
|
||||
foreground='gray',
|
||||
weight=Pango.Weight.NORMAL,
|
||||
style=Pango.Style.NORMAL)
|
||||
|
||||
# Margin and indents
|
||||
# A baseline margin is set to allow negative offsets for formatting headers, lists, etc
|
||||
self.margins_indents = {}
|
||||
self.update_margins_indents()
|
||||
|
||||
# Style
|
||||
self.on_style_updated()
|
||||
|
||||
self.version = 0
|
||||
|
||||
def on_style_updated(self, *_):
|
||||
(found, color) = self.text_view.get_style_context().lookup_color('math_text_color')
|
||||
if not found:
|
||||
(_, color) = self.text_view.get_style_context().lookup_color('foreground_color')
|
||||
self.mathtext.set_property("foreground", color.to_string())
|
||||
|
||||
def apply(self):
|
||||
self.version = self.version + 1
|
||||
if self.text_buffer.get_char_count() < self.max_char_sync:
|
||||
self.do_apply()
|
||||
else:
|
||||
GLib.idle_add(self.do_apply, self.version)
|
||||
|
||||
def do_apply(self, version=None):
|
||||
if version is not None and version != self.version:
|
||||
return
|
||||
|
||||
buffer = self.text_buffer
|
||||
start = buffer.get_start_iter()
|
||||
end = buffer.get_end_iter()
|
||||
offset = 0
|
||||
|
||||
text = buffer.get_slice(start, end, False)
|
||||
|
||||
# Remove tags
|
||||
buffer.remove_tag(self.italic, start, end)
|
||||
buffer.remove_tag(self.bold, start, end)
|
||||
buffer.remove_tag(self.bolditalic, start, end)
|
||||
buffer.remove_tag(self.strikethrough, start, end)
|
||||
buffer.remove_tag(self.horizontalrule, start, end)
|
||||
buffer.remove_tag(self.plaintext, start, end)
|
||||
buffer.remove_tag(self.table, start, end)
|
||||
buffer.remove_tag(self.mathtext, start, end)
|
||||
for tag in self.margins_indents.values():
|
||||
buffer.remove_tag(tag, start, end)
|
||||
buffer.remove_tag(self.graytext, start, end)
|
||||
buffer.remove_tag(self.graytext, start, end)
|
||||
|
||||
# Apply "_italic_" tag (italic)
|
||||
matches = re.finditer(self.regex["ITALIC"], text)
|
||||
for match in matches:
|
||||
start_iter = buffer.get_iter_at_offset(offset + match.start())
|
||||
end_iter = buffer.get_iter_at_offset(offset + match.end())
|
||||
buffer.apply_tag(self.italic, start_iter, end_iter)
|
||||
|
||||
# Apply "**bold**" tag (bold)
|
||||
matches = re.finditer(self.regex["BOLD"], text)
|
||||
for match in matches:
|
||||
start_iter = buffer.get_iter_at_offset(offset + match.start())
|
||||
end_iter = buffer.get_iter_at_offset(offset + match.end())
|
||||
buffer.apply_tag(self.bold, start_iter, end_iter)
|
||||
|
||||
# Apply "***bolditalic***" tag (bold/italic)
|
||||
matches = re.finditer(self.regex["BOLDITALIC"], text)
|
||||
for match in matches:
|
||||
start_iter = buffer.get_iter_at_offset(offset + match.start())
|
||||
end_iter = buffer.get_iter_at_offset(offset + match.end())
|
||||
buffer.apply_tag(self.bolditalic, start_iter, end_iter)
|
||||
|
||||
# Apply "~~strikethrough~~" tag (strikethrough)
|
||||
matches = re.finditer(self.regex["STRIKETHROUGH"], text)
|
||||
for match in matches:
|
||||
start_iter = buffer.get_iter_at_offset(offset + match.start())
|
||||
end_iter = buffer.get_iter_at_offset(offset + match.end())
|
||||
buffer.apply_tag(self.strikethrough, start_iter, end_iter)
|
||||
|
||||
matches = re.finditer(self.regex["LINK"], text)
|
||||
for match in matches:
|
||||
start_iter = buffer.get_iter_at_offset(offset + match.start(1))
|
||||
end_iter = buffer.get_iter_at_offset(offset + match.end(1))
|
||||
buffer.apply_tag(self.graytext, start_iter, end_iter)
|
||||
start_iter = buffer.get_iter_at_offset(offset + match.start(2))
|
||||
end_iter = buffer.get_iter_at_offset(offset + match.end(2))
|
||||
buffer.apply_tag(self.graytext, start_iter, end_iter)
|
||||
|
||||
# Apply "---" horizontal rule tag (center)
|
||||
matches = re.finditer(self.regex["HORIZONTALRULE"], text)
|
||||
for match in matches:
|
||||
start_iter = buffer.get_iter_at_offset(offset + match.start(1))
|
||||
end_iter = buffer.get_iter_at_offset(offset + match.end(1))
|
||||
buffer.apply_tag(self.horizontalrule, start_iter, end_iter)
|
||||
|
||||
# Apply "* list" tag (offset)
|
||||
matches = re.finditer(self.regex["LIST"], text)
|
||||
for match in matches:
|
||||
start_iter = buffer.get_iter_at_offset(offset + match.start())
|
||||
end_iter = buffer.get_iter_at_offset(offset + match.end())
|
||||
# Lists use character+space (eg. "* ")
|
||||
length = 2
|
||||
nest = len(match.group(1).replace(" ", "\t"))
|
||||
margin = -length - 2 * nest
|
||||
indent = -length - 2 * length * nest
|
||||
buffer.apply_tag(self.get_margin_indent_tag(margin, indent), start_iter, end_iter)
|
||||
|
||||
# Apply "1. numbered list" tag (offset)
|
||||
matches = re.finditer(self.regex["NUMBEREDLIST"], text)
|
||||
for match in matches:
|
||||
start_iter = buffer.get_iter_at_offset(offset + match.start())
|
||||
end_iter = buffer.get_iter_at_offset(offset + match.end())
|
||||
# Numeric lists use numbers/letters+dot/parens+space (eg. "123. ")
|
||||
length = len(match.group(2)) + 1
|
||||
nest = len(match.group(1).replace(" ", "\t"))
|
||||
margin = -length - 2 * nest
|
||||
indent = -length - 2 * length * nest
|
||||
buffer.apply_tag(self.get_margin_indent_tag(margin, indent), start_iter, end_iter)
|
||||
|
||||
# Apply "> blockquote" tag (offset)
|
||||
matches = re.finditer(self.regex["BLOCKQUOTE"], text)
|
||||
for match in matches:
|
||||
start_iter = buffer.get_iter_at_offset(offset + match.start())
|
||||
end_iter = buffer.get_iter_at_offset(offset + match.end())
|
||||
buffer.apply_tag(self.get_margin_indent_tag(2, -2), start_iter, end_iter)
|
||||
|
||||
# Apply "#" tag (offset + bold)
|
||||
matches = re.finditer(self.regex["HEADER"], text)
|
||||
for match in matches:
|
||||
start_iter = buffer.get_iter_at_offset(offset + match.start())
|
||||
end_iter = buffer.get_iter_at_offset(offset + match.end())
|
||||
margin = -len(match.group(1)) - 1
|
||||
buffer.apply_tag(self.get_margin_indent_tag(margin, 0), start_iter, end_iter)
|
||||
buffer.apply_tag(self.bold, start_iter, end_iter)
|
||||
|
||||
# Apply "======" header underline tag (bold)
|
||||
matches = re.finditer(self.regex["HEADER_UNDER"], text)
|
||||
for match in matches:
|
||||
start_iter = buffer.get_iter_at_offset(offset + match.start())
|
||||
end_iter = buffer.get_iter_at_offset(offset + match.end())
|
||||
buffer.apply_tag(self.bold, start_iter, end_iter)
|
||||
|
||||
# Apply "```" code tag (offset)
|
||||
matches = re.finditer(self.regex["CODE"], text)
|
||||
for match in matches:
|
||||
start_iter = buffer.get_iter_at_offset(offset + match.start(1))
|
||||
end_iter = buffer.get_iter_at_offset(offset + match.end(1))
|
||||
buffer.apply_tag(self.get_margin_indent_tag(0, 2), start_iter, end_iter)
|
||||
buffer.apply_tag(self.plaintext, start_iter, end_iter)
|
||||
|
||||
# Apply "---" table tag (wrap/pixels)
|
||||
matches = re.finditer(self.regex["TABLE"], text)
|
||||
for match in matches:
|
||||
start_iter = buffer.get_iter_at_offset(offset + match.start())
|
||||
end_iter = buffer.get_iter_at_offset(offset + match.end())
|
||||
buffer.apply_tag(self.table, start_iter, end_iter)
|
||||
|
||||
# Apply "$math$" tag (colorize)
|
||||
matches = re.finditer(self.regex["MATH"], text)
|
||||
for match in matches:
|
||||
start_iter = buffer.get_iter_at_offset(offset + match.start())
|
||||
end_iter = buffer.get_iter_at_offset(offset + match.end())
|
||||
buffer.apply_tag(self.mathtext, start_iter, end_iter)
|
||||
|
||||
# Apply focus mode tag (grey out before/after current sentence)
|
||||
if self.text_view.focus_mode:
|
||||
cursor_iter = buffer.get_iter_at_mark(buffer.get_insert())
|
||||
start_sentence = cursor_iter.copy()
|
||||
start_sentence.backward_sentence_start()
|
||||
end_sentence = cursor_iter.copy()
|
||||
end_sentence.forward_sentence_end()
|
||||
if start.compare(start_sentence) <= 0:
|
||||
buffer.apply_tag(self.graytext, start, start_sentence)
|
||||
if end.compare(end_sentence) >= 0:
|
||||
buffer.apply_tag(self.graytext, end_sentence, end)
|
||||
|
||||
# Margin and indent are cumulative. They differ in two ways:
|
||||
# * Margin is always in the beginning, which means it effectively only affects the first line
|
||||
# of multi-line text. Indent is applied to every line.
|
||||
# * Margin level can be negative, as a baseline margin exists from which it can be subtracted.
|
||||
# Indent is always positive, or 0.
|
||||
def get_margin_indent_tag(self, margin_level, indent_level):
|
||||
level = (margin_level, indent_level)
|
||||
if level not in self.margins_indents:
|
||||
tag = self.text_buffer.create_tag(
|
||||
"margin_indent_" + str(margin_level) + "_" + str(indent_level))
|
||||
margin, indent = self.get_margin_indent(margin_level, indent_level)
|
||||
tag.set_property("left-margin", margin)
|
||||
tag.set_property("indent", indent)
|
||||
self.margins_indents[level] = tag
|
||||
return tag
|
||||
else:
|
||||
return self.margins_indents[level]
|
||||
|
||||
def get_margin_indent(self, margin_level, indent_level, baseline_margin=None, char_width=None):
|
||||
if baseline_margin is None:
|
||||
baseline_margin = self.text_view.get_left_margin()
|
||||
if char_width is None:
|
||||
char_width = helpers.get_char_width(self.text_view)
|
||||
margin = max(baseline_margin + char_width * margin_level, 0)
|
||||
indent = char_width * indent_level
|
||||
return margin, indent
|
||||
|
||||
def update_margins_indents(self):
|
||||
baseline_margin = self.text_view.get_left_margin()
|
||||
char_width = helpers.get_char_width(self.text_view)
|
||||
|
||||
# Adjust tab size, as character width can change
|
||||
tab_array = Pango.TabArray.new(1, True)
|
||||
tab_array.set_tab(0, Pango.TabAlign.LEFT, 4 * char_width)
|
||||
self.text_view.set_tabs(tab_array)
|
||||
|
||||
# Adjust margins and indents, as character width can change
|
||||
for level, tag in self.margins_indents.items():
|
||||
margin, indent = self.get_margin_indent(*level, baseline_margin, char_width)
|
||||
tag.set_property("left-margin", margin)
|
||||
tag.set_property("indent", indent)
|
|
@ -0,0 +1,204 @@
|
|||
class UndoableInsert:
|
||||
"""Something has been inserted into text_buffer"""
|
||||
|
||||
def __init__(self, text_iter, text, length):
|
||||
self.offset = text_iter.get_offset()
|
||||
self.text = text
|
||||
self.length = length
|
||||
self.mergeable = not bool(self.length > 1 or self.text in ("\r", "\n", " "))
|
||||
|
||||
|
||||
class UndoableDelete:
|
||||
"""Something has been deleted from text_buffer"""
|
||||
|
||||
def __init__(self, text_buffer, start_iter, end_iter):
|
||||
self.text = text_buffer.get_text(start_iter, end_iter, False)
|
||||
self.start = start_iter.get_offset()
|
||||
self.end = end_iter.get_offset()
|
||||
# Find out if backspace or delete were used to not mess up redo
|
||||
insert_iter = text_buffer.get_iter_at_mark(text_buffer.get_insert())
|
||||
self.delete_key_used = bool(insert_iter.get_offset() <= self.start)
|
||||
self.mergeable = not bool(self.end - self.start > 1 or self.text in ("\r", "\n", " "))
|
||||
|
||||
|
||||
class UndoRedoHandler:
|
||||
"""Manages undo/redo for a given text_buffer.
|
||||
|
||||
Methods can be called directly, as well as be used as signal callbacks."""
|
||||
|
||||
def __init__(self):
|
||||
self.undo_stack = []
|
||||
self.redo_stack = []
|
||||
self.not_undoable_action = False
|
||||
self.undo_in_progress = False
|
||||
|
||||
def undo(self, text_view, _data=None):
|
||||
"""Undo insertions or deletions. Undone actions are moved to redo stack.
|
||||
|
||||
This method can be registered to a custom undo signal, or used independently."""
|
||||
|
||||
if not self.undo_stack:
|
||||
return
|
||||
self.__begin_not_undoable_action()
|
||||
self.undo_in_progress = True
|
||||
undo_action = self.undo_stack.pop()
|
||||
self.redo_stack.append(undo_action)
|
||||
text_buffer = text_view.get_buffer()
|
||||
if isinstance(undo_action, UndoableInsert):
|
||||
offset = undo_action.offset
|
||||
start = text_buffer.get_iter_at_offset(offset)
|
||||
stop = text_buffer.get_iter_at_offset(
|
||||
offset + undo_action.length
|
||||
)
|
||||
text_buffer.place_cursor(start)
|
||||
text_buffer.delete(start, stop)
|
||||
else:
|
||||
start = text_buffer.get_iter_at_offset(undo_action.start)
|
||||
text_buffer.insert(start, undo_action.text)
|
||||
if undo_action.delete_key_used:
|
||||
text_buffer.place_cursor(start)
|
||||
else:
|
||||
stop = text_buffer.get_iter_at_offset(undo_action.end)
|
||||
text_buffer.place_cursor(stop)
|
||||
self.__end_not_undoable_action()
|
||||
self.undo_in_progress = False
|
||||
|
||||
def redo(self, text_view, _data=None):
|
||||
"""Redo insertions or deletions. Redone actions are moved to undo stack
|
||||
|
||||
This method can be registered to a custom redo signal, or used independently."""
|
||||
|
||||
if not self.redo_stack:
|
||||
return
|
||||
self.__begin_not_undoable_action()
|
||||
self.undo_in_progress = True
|
||||
redo_action = self.redo_stack.pop()
|
||||
self.undo_stack.append(redo_action)
|
||||
text_buffer = text_view.get_buffer()
|
||||
if isinstance(redo_action, UndoableInsert):
|
||||
start = text_buffer.get_iter_at_offset(redo_action.offset)
|
||||
text_buffer.insert(start, redo_action.text)
|
||||
new_cursor_pos = text_buffer.get_iter_at_offset(
|
||||
redo_action.offset + redo_action.length)
|
||||
text_buffer.place_cursor(new_cursor_pos)
|
||||
else:
|
||||
start = text_buffer.get_iter_at_offset(redo_action.start)
|
||||
stop = text_buffer.get_iter_at_offset(redo_action.end)
|
||||
text_buffer.delete(start, stop)
|
||||
text_buffer.place_cursor(start)
|
||||
self.__end_not_undoable_action()
|
||||
self.undo_in_progress = False
|
||||
|
||||
def clear(self):
|
||||
self.undo_stack = []
|
||||
self.redo_stack = []
|
||||
|
||||
def on_insert_text(self, _text_buffer, text_iter, text, _length):
|
||||
"""Registers a text insert. Refer to TextBuffer's "insert-text" signal.
|
||||
|
||||
This method must be registered to TextBuffer's "insert-text" signal, or called manually."""
|
||||
|
||||
def can_be_merged(prev, cur):
|
||||
"""Check if multiple insertions can be merged
|
||||
|
||||
can't merge if prev and cur are not mergeable in the first place
|
||||
can't merge when user set the input bar somewhere else
|
||||
can't merge across word boundaries"""
|
||||
|
||||
whitespace = (' ', '\t')
|
||||
if not cur.mergeable or not prev.mergeable:
|
||||
return False
|
||||
if cur.offset != (prev.offset + prev.length):
|
||||
return False
|
||||
if cur.text in whitespace and prev.text not in whitespace:
|
||||
return False
|
||||
if prev.text in whitespace and cur.text not in whitespace:
|
||||
return False
|
||||
return True
|
||||
|
||||
if not self.undo_in_progress:
|
||||
self.redo_stack = []
|
||||
if self.not_undoable_action:
|
||||
return
|
||||
|
||||
undo_action = UndoableInsert(text_iter, text, len(text))
|
||||
try:
|
||||
prev_insert = self.undo_stack.pop()
|
||||
except IndexError:
|
||||
self.undo_stack.append(undo_action)
|
||||
return
|
||||
if not isinstance(prev_insert, UndoableInsert):
|
||||
self.undo_stack.append(prev_insert)
|
||||
self.undo_stack.append(undo_action)
|
||||
return
|
||||
if can_be_merged(prev_insert, undo_action):
|
||||
prev_insert.length += undo_action.length
|
||||
prev_insert.text += undo_action.text
|
||||
self.undo_stack.append(prev_insert)
|
||||
else:
|
||||
self.undo_stack.append(prev_insert)
|
||||
self.undo_stack.append(undo_action)
|
||||
|
||||
def on_delete_range(self, text_buffer, start_iter, end_iter):
|
||||
"""Registers a range deletion. Refer to TextBuffer's "delete-range" signal.
|
||||
|
||||
This method must be registered to TextBuffer's "delete-range" signal, or called manually."""
|
||||
|
||||
def can_be_merged(prev, cur):
|
||||
"""Check if multiple deletions can be merged
|
||||
|
||||
can't merge if prev and cur are not mergeable in the first place
|
||||
can't merge if delete and backspace key were both used
|
||||
can't merge across word boundaries"""
|
||||
|
||||
whitespace = (' ', '\t')
|
||||
if not cur.mergeable or not prev.mergeable:
|
||||
return False
|
||||
if prev.delete_key_used != cur.delete_key_used:
|
||||
return False
|
||||
if prev.start != cur.start and prev.start != cur.end:
|
||||
return False
|
||||
if cur.text not in whitespace and \
|
||||
prev.text in whitespace:
|
||||
return False
|
||||
if cur.text in whitespace and \
|
||||
prev.text not in whitespace:
|
||||
return False
|
||||
return True
|
||||
|
||||
if not self.undo_in_progress:
|
||||
self.redo_stack = []
|
||||
if self.not_undoable_action:
|
||||
return
|
||||
undo_action = UndoableDelete(text_buffer, start_iter, end_iter)
|
||||
try:
|
||||
prev_delete = self.undo_stack.pop()
|
||||
except IndexError:
|
||||
self.undo_stack.append(undo_action)
|
||||
return
|
||||
if not isinstance(prev_delete, UndoableDelete):
|
||||
self.undo_stack.append(prev_delete)
|
||||
self.undo_stack.append(undo_action)
|
||||
return
|
||||
if can_be_merged(prev_delete, undo_action):
|
||||
if prev_delete.start == undo_action.start: # delete key used
|
||||
prev_delete.text += undo_action.text
|
||||
prev_delete.end += (undo_action.end - undo_action.start)
|
||||
else: # Backspace used
|
||||
prev_delete.text = "%s%s" % (undo_action.text,
|
||||
prev_delete.text)
|
||||
prev_delete.start = undo_action.start
|
||||
self.undo_stack.append(prev_delete)
|
||||
else:
|
||||
self.undo_stack.append(prev_delete)
|
||||
self.undo_stack.append(undo_action)
|
||||
|
||||
def __begin_not_undoable_action(self):
|
||||
"""Toggle to stop recording actions"""
|
||||
|
||||
self.not_undoable_action = True
|
||||
|
||||
def __end_not_undoable_action(self):
|
||||
"""Toggle to start recording actions"""
|
||||
|
||||
self.not_undoable_action = False
|
|
@ -11,11 +11,11 @@ class Theme:
|
|||
The light variant is listed first, followed by the dark variant, if any.
|
||||
"""
|
||||
|
||||
previous = None
|
||||
settings = Settings.new()
|
||||
|
||||
def __init__(self, name, gtk_css_path, web_css_path, is_dark, inverse_name):
|
||||
def __init__(self, name, web_css_path, is_dark, inverse_name):
|
||||
self.name = name
|
||||
self.gtk_css_path = gtk_css_path
|
||||
self.web_css_path = web_css_path
|
||||
self.is_dark = is_dark
|
||||
self.inverse_name = inverse_name
|
||||
|
@ -29,33 +29,39 @@ class Theme:
|
|||
return current_theme
|
||||
|
||||
@classmethod
|
||||
def get_current(cls):
|
||||
def get_current_changed(cls):
|
||||
theme_name = Gtk.Settings.get_default().get_property('gtk-theme-name')
|
||||
dark_mode_auto = cls.settings.get_value('dark-mode-auto').get_boolean()
|
||||
dark_mode = cls.settings.get_value('dark-mode').get_boolean()
|
||||
current_theme = cls.get_for_name(theme_name)
|
||||
# Technically, we could very easily allow the user to force the light ui on a dark theme.
|
||||
# However, as there is no inverse of "gtk-application-prefer-dark-theme", we shouldn't do that.
|
||||
if dark_mode and not current_theme.is_dark and current_theme.inverse_name:
|
||||
if not dark_mode_auto and dark_mode != current_theme.is_dark and current_theme.inverse_name:
|
||||
current_theme = cls.get_for_name(current_theme.inverse_name, current_theme.name)
|
||||
changed = current_theme != cls.previous
|
||||
cls.previous = current_theme
|
||||
return current_theme, changed
|
||||
|
||||
@classmethod
|
||||
def get_current(cls):
|
||||
current_theme, _ = cls.get_current_changed()
|
||||
return current_theme
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, self.__class__) and \
|
||||
self.name == other.name and \
|
||||
self.web_css_path == other.web_css_path and \
|
||||
self.is_dark == other.is_dark and \
|
||||
self.inverse_name == other.inverse_name
|
||||
|
||||
|
||||
defaultThemes = [
|
||||
# https://gitlab.gnome.org/GNOME/gtk/tree/master/gtk/theme/Adwaita
|
||||
Theme('Adwaita', get_css_path('gtk_adwaita.css'),
|
||||
get_css_path('web_adwaita.css'), False, 'Adwaita-dark'),
|
||||
Theme('Adwaita-dark', get_css_path('gtk_adwaita_dark.css'),
|
||||
get_css_path('web_adwaita_dark.css'), True, 'Adwaita'),
|
||||
Theme('Adwaita', get_css_path('web/adwaita.css'), False, 'Adwaita-dark'),
|
||||
Theme('Adwaita-dark', get_css_path('web/adwaita_dark.css'), True, 'Adwaita'),
|
||||
# https://github.com/NicoHood/arc-theme/tree/master/common/gtk-3.0/3.20/sass
|
||||
Theme('Arc', get_css_path('gtk_arc.css'),
|
||||
get_css_path('web_arc.css'), False, 'Arc-Dark'),
|
||||
Theme('Arc-Darker', get_css_path('gtk_arc_darker.css'),
|
||||
get_css_path('web_arc_darker.css'), False, 'Arc-Dark'),
|
||||
Theme('Arc-Dark', get_css_path('gtk_arc_dark.css'),
|
||||
get_css_path('web_arc_dark.css'), True, 'Arc'),
|
||||
Theme('Arc', get_css_path('web/arc.css'), False, 'Arc-Dark'),
|
||||
Theme('Arc-Darker', get_css_path('web/arc_darker.css'), False, 'Arc-Dark'),
|
||||
Theme('Arc-Dark', get_css_path('web/arc_dark.css'), True, 'Arc'),
|
||||
# https://gitlab.gnome.org/GNOME/gtk/tree/master/gtk/theme/HighContrast
|
||||
Theme('HighContrast', get_css_path('gtk_high_contrast.css'),
|
||||
get_css_path('web_high_contrast.css'), False, 'HighContrastInverse'),
|
||||
Theme('HighContrastInverse', get_css_path('gtk_high_contrast_inverse.css'),
|
||||
get_css_path('web_high_contrast_inverse.css'), True, 'HighContrast'),
|
||||
Theme('HighContrast', get_css_path('web/highcontrast.css'), False, 'HighContrastInverse'),
|
||||
Theme('HighContrastInverse', get_css_path('web/highcontrast_inverse.css'), True, 'HighContrast')
|
||||
]
|
||||
|
|
|
@ -101,7 +101,6 @@ class PyGTKBrowser:
|
|||
if options.delay:
|
||||
print("--delay is only supported on Mac OS X (for now). Sorry!")
|
||||
|
||||
gobject.threads_init()
|
||||
window = gtk.Window()
|
||||
window.resize(int(options.initWidth),int(options.initHeight))
|
||||
self.view = webkit.WebView()
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue