forked from Mirrors/apostrophe
Merge pull request #143 from goncalossilva/various_improvements
This PR includes various fixes and improvements, most of which I discussed with @somas95 a couple of weeks ago. It also includes some refactoring, mainly to improve encapsulation. Fixes #17 Fixes #75 Fixes #90 Fixes #120 Fixes #122 Some considerations/questions follow. **Commit messages** I recommend reviewing each commit message carefully, as they explain the thinking behind each change. Not everything is small or linear, and hopefully the commit message explains the background and reasoning properly. If anything is missing, I'm happy to try to explain. **Translations** There are a few capitalization/copy fixes. I am unsure how this affects translations, or if there's any work I can to make the transition easier. For instance, how are the po files updated? **Styling** Styling is subjective, and although I claim to address #90, it's important to note that I'm a fan of UberWriter's simplistic style, reminiscent of iA Writer's approach. As such, I didn't introduce any sizing nor color changes, as I don't personally agree with this direction. Instead, the changes are subtle: * For links, the link/syntax part is greyed out, without any further changes * For code blocks, they are indented similarly to quotations, without any further changes **UI changes** There are (very minor) UI changes within the places that were touched, mainly preferences and the export dialog. Most of these are around improving small things, such as spacing (eg. multiples of 8), slightly broken alignments, etc. The only exception would be the removal of ODT as one of the export options in the top bar. As before, subjective, but I find that a sub-window's top bar with 6 horizontal buttons is a little overwhelming, and considering that ODT is still available within the advanced tab, I propose that we remove it as a default option and have a more balanced top bar. I am assuming that PDF/HTML are the most used, but please let me know if that's not the case. **Further work** I have a few more tasks planned for the upcoming weeks, hopefully built on top of this codebase as it makes some of them much easier. [Here's a screenshot of my Todoist.](https://cl.ly/4160755d210d/gnome-shell-screenshot-3MMO0Z.png) Hopefully this helps knowing what to expect. 😊ft.font-size v2.2.0-beta1
commit
f5d7f518fa
|
@ -4,35 +4,47 @@
|
|||
|
||||
<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'>
|
||||
|
|
|
@ -8,42 +8,39 @@
|
|||
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>u" { "insert-listitem" () };
|
||||
bind "<ctl>h" { "insert-header" () };
|
||||
bind "<ctl>z" { "undo" () };
|
||||
bind "<ctl>y" { "redo" () };
|
||||
bind "<ctl><shift>d" { "insert-strikeout" () };
|
||||
bind "<ctl><shift>d" { "insert-strikethrough" () };
|
||||
/*bind "<ctl>t" { "insert-at-cursor" ('[ ] ') };*/
|
||||
bind "<ctl><shift>z" { "redo" () };
|
||||
}
|
||||
|
||||
/* Main window and text colors */
|
||||
|
||||
.uberwriter_window {
|
||||
.uberwriter-window {
|
||||
/*border-radius: 7px 7px 3px 3px;*/
|
||||
background: @background_color;
|
||||
caret-color: @foreground_color;
|
||||
}
|
||||
|
||||
.uberwriter_window.small .uberwriter-editor {
|
||||
.uberwriter-window .uberwriter-editor {
|
||||
font-family: 'Fira Mono', monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
.uberwriter_window grid {
|
||||
background-color: @background_color;
|
||||
font-size: 16px;
|
||||
padding-top: 80px;
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
|
||||
#UberwriterWindow.medium .uberwriter-editor {
|
||||
font-family: 'Fira Mono', monospace;
|
||||
font-size: 15px;
|
||||
.uberwriter-window.small .uberwriter-editor {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#UberwriterWindow.large .uberwriter-editor {
|
||||
font-family: 'Fira Mono', monospace;
|
||||
.uberwriter-window.large .uberwriter-editor {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
#titlebar_revealer {
|
||||
#titlebar-revealer {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
|
@ -52,7 +49,7 @@
|
|||
background: transparent;
|
||||
}
|
||||
|
||||
#titlebar_container {
|
||||
#titlebar-container {
|
||||
background: @background_color;
|
||||
}
|
||||
|
||||
|
@ -89,11 +86,11 @@
|
|||
}
|
||||
|
||||
|
||||
.status_bar_box label {
|
||||
.status-bar-box label {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.status_bar_box button {
|
||||
.status-bar-box button {
|
||||
/* finding reset */
|
||||
background-color: @background_color;
|
||||
text-shadow: inherit;
|
||||
|
@ -118,26 +115,26 @@
|
|||
transition: 100ms ease-in;
|
||||
}
|
||||
|
||||
.status_bar_box button:hover,
|
||||
.status_bar_box button:checked {
|
||||
.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 {
|
||||
.status-bar-box button:hover label,
|
||||
.status-bar-box button:checked label {
|
||||
color: @background_color;
|
||||
}
|
||||
|
||||
.status_bar_box button:active {
|
||||
.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 {
|
||||
.status-bar-box separator {
|
||||
border-color: #999;
|
||||
border-right: none;
|
||||
}
|
||||
|
@ -150,7 +147,7 @@
|
|||
background: #FFF;
|
||||
}
|
||||
|
||||
#UberwriterWindow treeview {
|
||||
.uberwriter-window treeview {
|
||||
padding: 3px 3px 3px 3px;
|
||||
}
|
||||
|
||||
|
@ -165,7 +162,7 @@
|
|||
padding: 5px;
|
||||
}
|
||||
|
||||
/* .QuickPreviewPopup {
|
||||
/* .quick-preview-popup {
|
||||
padding: 5px;
|
||||
margin: 5px;
|
||||
border: 1px solid #333;
|
||||
|
@ -183,8 +180,7 @@
|
|||
border: 5px solid @background_color;
|
||||
}
|
||||
|
||||
#LexikonBubble .lexikon_heading {
|
||||
/*font: serif 12;*/
|
||||
#LexikonBubble .lexikon-heading {
|
||||
font-family: serif;
|
||||
font-size: 12px;
|
||||
padding-bottom: 5px;
|
||||
|
@ -193,21 +189,21 @@
|
|||
padding-left: 10px;
|
||||
}
|
||||
|
||||
#LexikonBubble .lexikon_num {
|
||||
#LexikonBubble .lexikon-num {
|
||||
padding-right: 5px;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.QuickPreviewPopup {
|
||||
.quick-preview-popup {
|
||||
background-color: @background_color;
|
||||
}
|
||||
|
||||
.QuickPreviewPopup grid {
|
||||
.quick-preview-popup grid {
|
||||
background-color: @background_color;
|
||||
color: @foreground_color;
|
||||
border-color: @background_color;
|
||||
}
|
||||
|
||||
.QuickPreviewPopup label {
|
||||
.quick-preview-popup label {
|
||||
color: @foreground_color;
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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">
|
||||
|
@ -186,23 +184,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 +281,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 +327,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 +340,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>
|
|
@ -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,4 @@
|
|||
regex
|
||||
enchant
|
||||
python-gtkspellcheck
|
||||
pandoc
|
||||
pypandoc==1.4
|
||||
|
|
|
@ -15,20 +15,14 @@
|
|||
### 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():
|
||||
|
@ -38,13 +32,4 @@ def main():
|
|||
# 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,7 @@ class Application(Gtk.Application):
|
|||
|
||||
Gtk.Application.do_startup(self)
|
||||
|
||||
# Actions
|
||||
|
||||
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
|
||||
self.settings.connect("changed", self.on_settings_changed)
|
||||
|
||||
action = Gio.SimpleAction.new("new", None)
|
||||
action.connect("activate", self.on_new)
|
||||
|
@ -122,14 +52,34 @@ 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)
|
||||
|
||||
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,14 +88,30 @@ 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)
|
||||
|
||||
# Shortcuts
|
||||
|
||||
# TODO: be aware that a couple of shortcuts are defined in _gtk_base.css
|
||||
|
@ -163,8 +129,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 +138,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 +149,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 +157,15 @@ 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()
|
||||
|
||||
def on_new(self, _action, _value):
|
||||
self.window.new_document()
|
||||
|
@ -290,26 +176,58 @@ 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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
"""
|
||||
|
||||
|
@ -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.
|
||||
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"])
|
||||
|
||||
Returns a fully instantiated PreferencesDialog object.
|
||||
"""
|
||||
builder = get_builder('Preferences')
|
||||
new_object = builder.get_object("PreferencesWindow")
|
||||
return new_object
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -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,201 @@
|
|||
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')
|
||||
from gi.repository import Gtk, Gdk, GObject, GLib
|
||||
|
||||
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)
|
||||
|
||||
# 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.original_top_margin = self.props.top_margin
|
||||
self.original_bottom_margin = self.props.bottom_margin
|
||||
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.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 = self.original_top_margin
|
||||
self.props.bottom_margin = self.original_bottom_margin
|
||||
|
||||
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 = 80
|
||||
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,6 +11,7 @@ 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):
|
||||
|
@ -29,16 +30,30 @@ 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.gtk_css_path == other.gtk_css_path 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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -17,43 +17,35 @@
|
|||
import codecs
|
||||
import locale
|
||||
import logging
|
||||
import mimetypes
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import urllib
|
||||
import webbrowser
|
||||
from gettext import gettext as _
|
||||
|
||||
import gi
|
||||
from gi.repository.GObject import param_spec_string
|
||||
|
||||
from uberwriter.export_dialog import Export
|
||||
from uberwriter.text_view import TextView
|
||||
|
||||
gi.require_version('Gtk', '3.0')
|
||||
gi.require_version('WebKit2', '4.0') # pylint: disable=wrong-import-position
|
||||
from gi.repository import Gtk, Gdk, GObject, GLib, Gio
|
||||
from gi.repository import WebKit2 as WebKit
|
||||
from gi.repository import Pango # pylint: disable=E0611
|
||||
|
||||
import cairo
|
||||
# import cairo.Pattern, cairo.SolidPattern
|
||||
|
||||
from uberwriter import headerbars
|
||||
from uberwriter import helpers
|
||||
from uberwriter.theme import Theme
|
||||
from uberwriter.helpers import get_builder
|
||||
from uberwriter.gtkspellcheck import SpellChecker
|
||||
|
||||
from uberwriter.markup_buffer import MarkupBuffer
|
||||
from uberwriter.text_editor import TextEditor
|
||||
from uberwriter.inline_preview import InlinePreview
|
||||
from uberwriter.sidebar import Sidebar
|
||||
from uberwriter.search_and_replace import SearchAndReplace
|
||||
from uberwriter.settings import Settings
|
||||
# from .auto_correct import AutoCorrect
|
||||
|
||||
from uberwriter.export_dialog import Export
|
||||
# from .plugins.bibtex import BibTex
|
||||
from . import headerbars
|
||||
|
||||
# Some Globals
|
||||
# TODO move them somewhere for better
|
||||
# accesibility from other files
|
||||
|
@ -62,10 +54,17 @@ LOGGER = logging.getLogger('uberwriter')
|
|||
|
||||
CONFIG_PATH = os.path.expanduser("~/.config/uberwriter/")
|
||||
|
||||
# See texteditor_lib.Window.py for more details about how this class works
|
||||
|
||||
|
||||
class Window(Gtk.ApplicationWindow):
|
||||
__gsignals__ = {
|
||||
'save-file': (GObject.SIGNAL_ACTION, None, ()),
|
||||
'open-file': (GObject.SIGNAL_ACTION, None, ()),
|
||||
'save-file-as': (GObject.SIGNAL_ACTION, None, ()),
|
||||
'new-file': (GObject.SIGNAL_ACTION, None, ()),
|
||||
'toggle-bibtex': (GObject.SIGNAL_ACTION, None, ()),
|
||||
'toggle-preview': (GObject.SIGNAL_ACTION, None, ()),
|
||||
'close-window': (GObject.SIGNAL_ACTION, None, ())
|
||||
}
|
||||
|
||||
WORDCOUNT = re.compile(r"(?!\-\w)[\s#*\+\-]+", re.UNICODE)
|
||||
|
||||
|
@ -76,38 +75,37 @@ class Window(Gtk.ApplicationWindow):
|
|||
application=Gio.Application.get_default(),
|
||||
title="Uberwriter")
|
||||
|
||||
self.builder = get_builder('UberwriterWindow')
|
||||
self.add(self.builder.get_object("FullscreenOverlay"))
|
||||
# Set UI
|
||||
self.builder = get_builder('Window')
|
||||
root = self.builder.get_object("FullscreenOverlay")
|
||||
root.connect('style-updated', self.apply_current_theme)
|
||||
self.add(root)
|
||||
|
||||
self.set_default_size(850, 500)
|
||||
self.set_default_size(900, 500)
|
||||
|
||||
# preferences
|
||||
# Preferences
|
||||
self.settings = Settings.new()
|
||||
|
||||
self.set_name('UberwriterWindow')
|
||||
|
||||
# Headerbars
|
||||
self.headerbar = headerbars.MainHeaderbar(app)
|
||||
self.set_titlebar(self.headerbar.hb_container)
|
||||
self.fs_headerbar = headerbars.FsHeaderbar(self.builder, app)
|
||||
self.fs_headerbar = headerbars.FullscreenHeaderbar(self.builder, app)
|
||||
|
||||
self.title_end = " – UberWriter"
|
||||
self.set_headerbar_title("New File" + self.title_end)
|
||||
|
||||
self.focusmode = False
|
||||
|
||||
self.word_count = self.builder.get_object('word_count')
|
||||
self.char_count = self.builder.get_object('char_count')
|
||||
|
||||
# Setup status bar hide after 3 seconds
|
||||
|
||||
self.status_bar = self.builder.get_object('status_bar_box')
|
||||
self.statusbar_revealer = self.builder.get_object('status_bar_revealer')
|
||||
self.status_bar.get_style_context().add_class('status_bar_box')
|
||||
self.status_bar.get_style_context().add_class('status-bar-box')
|
||||
self.status_bar_visible = True
|
||||
self.was_motion = True
|
||||
self.buffer_modified_for_status_bar = False
|
||||
|
||||
self.timestamp_last_mouse_motion = 0
|
||||
if self.settings.get_value("poll-motion"):
|
||||
self.connect("motion-notify-event", self.on_motion_notify)
|
||||
GObject.timeout_add(3000, self.poll_for_motion)
|
||||
|
@ -116,116 +114,30 @@ class Window(Gtk.ApplicationWindow):
|
|||
self.add_accel_group(self.accel_group)
|
||||
|
||||
# Setup text editor
|
||||
self.text_editor = TextEditor()
|
||||
self.text_editor.set_name('UberwriterEditor')
|
||||
self.get_style_context().add_class('uberwriter_window')
|
||||
self.text_view = TextView()
|
||||
self.text_view.props.halign = Gtk.Align.CENTER
|
||||
self.text_view.connect('focus-out-event', self.focus_out)
|
||||
self.text_view.show()
|
||||
self.text_view.grab_focus()
|
||||
|
||||
base_leftmargin = 100
|
||||
self.text_editor.set_left_margin(base_leftmargin)
|
||||
self.text_editor.set_left_margin(40)
|
||||
self.text_editor.set_top_margin(80)
|
||||
self.text_editor.props.width_request = 600
|
||||
self.text_editor.props.halign = Gtk.Align.CENTER
|
||||
self.text_editor.set_vadjustment(self.builder.get_object('vadjustment1'))
|
||||
self.text_editor.set_wrap_mode(Gtk.WrapMode.WORD_CHAR)
|
||||
self.text_editor.connect('focus-out-event', self.focus_out)
|
||||
self.text_editor.get_style_context().connect('changed', self.style_changed)
|
||||
|
||||
self.text_editor.set_top_margin(80)
|
||||
self.text_editor.set_bottom_margin(16)
|
||||
|
||||
self.text_editor.set_pixels_above_lines(4)
|
||||
self.text_editor.set_pixels_below_lines(4)
|
||||
self.text_editor.set_pixels_inside_wrap(8)
|
||||
|
||||
tab_array = Pango.TabArray.new(1, True)
|
||||
tab_array.set_tab(0, Pango.TabAlign.LEFT, 20)
|
||||
self.text_editor.set_tabs(tab_array)
|
||||
|
||||
self.text_editor.show()
|
||||
self.text_editor.grab_focus()
|
||||
self.text_view.get_buffer().connect('changed', self.on_text_changed)
|
||||
|
||||
# Setup preview webview
|
||||
self.preview_webview = None
|
||||
|
||||
self.editor_alignment = self.builder.get_object('editor_alignment')
|
||||
self.scrolled_window = self.builder.get_object('editor_scrolledwindow')
|
||||
self.scrolled_window.props.width_request = 600
|
||||
self.scrolled_window.add(self.text_editor)
|
||||
self.alignment_padding = 40
|
||||
self.scrolled_window.get_style_context().add_class('uberwriter-scrolled-window')
|
||||
self.scrolled_window.add(self.text_view)
|
||||
self.editor_viewport = self.builder.get_object('editor_viewport')
|
||||
|
||||
# some people seems to have performance problems with the overlay.
|
||||
# Let them disable it
|
||||
|
||||
if self.settings.get_value("gradient-overlay"):
|
||||
self.overlay = self.scrolled_window.connect_after("draw", self.draw_gradient)
|
||||
|
||||
self.smooth_scroll_starttime = 0
|
||||
self.smooth_scroll_endtime = 0
|
||||
self.smooth_scroll_acttarget = 0
|
||||
self.smooth_scroll_data = {
|
||||
'target_pos': -1,
|
||||
'source_pos': -1,
|
||||
'duration': 0
|
||||
}
|
||||
self.smooth_scroll_tickid = -1
|
||||
|
||||
self.text_buffer = self.text_editor.get_buffer()
|
||||
self.text_buffer.set_text('')
|
||||
|
||||
# Init Window height for top/bottom padding
|
||||
self.window_height = self.get_size()[1]
|
||||
|
||||
self.text_change_event = self.text_buffer.connect(
|
||||
'changed', self.text_changed)
|
||||
self.overlay_id = None
|
||||
self.toggle_gradient_overlay(self.settings.get_value("gradient-overlay"))
|
||||
|
||||
# Init file name with None
|
||||
self.set_filename()
|
||||
|
||||
# Markup and Shortcuts for the TextBuffer
|
||||
self.markup_buffer = MarkupBuffer(
|
||||
self, self.text_buffer, base_leftmargin)
|
||||
self.markup_buffer.markup_buffer()
|
||||
|
||||
# Set current theme
|
||||
self.apply_current_theme()
|
||||
|
||||
# Scrolling -> Dark or not?
|
||||
self.textchange = False
|
||||
self.scroll_count = 0
|
||||
self.timestamp_last_mouse_motion = 0
|
||||
self.text_buffer.connect_after('mark-set', self.mark_set)
|
||||
|
||||
# Drag and drop
|
||||
|
||||
# self.TextEditor.drag_dest_unset()
|
||||
# self.TextEditor.drag_dest_set(Gtk.DestDefaults.ALL, [], Gdk.DragAction.COPY)
|
||||
self.target_list = Gtk.TargetList.new([])
|
||||
self.target_list.add_uri_targets(1)
|
||||
self.target_list.add_text_targets(2)
|
||||
|
||||
self.text_editor.drag_dest_set_target_list(self.target_list)
|
||||
self.text_editor.connect_after(
|
||||
'drag-data-received', self.on_drag_data_received)
|
||||
|
||||
def on_drop(_widget, *_args):
|
||||
print("drop")
|
||||
self.text_editor.connect('drag-drop', on_drop)
|
||||
|
||||
self.text_buffer.connect('paste-done', self.paste_done)
|
||||
# self.connect('key-press-event', self.alt_mod)
|
||||
|
||||
# Events for Typewriter mode
|
||||
|
||||
# Setting up inline preview
|
||||
self.inline_preview = InlinePreview(
|
||||
self.text_editor, self.text_buffer)
|
||||
|
||||
# Vertical scrolling
|
||||
self.vadjustment = self.scrolled_window.get_vadjustment()
|
||||
self.vadjustment.connect('value-changed', self.scrolled)
|
||||
|
||||
# Setting up spellcheck
|
||||
self.auto_correct = None
|
||||
self.toggle_spellcheck(self.settings.get_value("spellcheck"))
|
||||
|
@ -243,87 +155,49 @@ class Window(Gtk.ApplicationWindow):
|
|||
# Search and replace initialization
|
||||
# Same interface as Sidebar ;)
|
||||
###
|
||||
self.searchreplace = SearchAndReplace(self)
|
||||
self.searchreplace = SearchAndReplace(self, self.text_view)
|
||||
|
||||
# Window resize
|
||||
self.window_resize(self)
|
||||
self.connect("configure-event", self.window_resize)
|
||||
self.connect("delete-event", self.on_delete_called)
|
||||
|
||||
__gsignals__ = {
|
||||
'save-file': (GObject.SIGNAL_ACTION, None, ()),
|
||||
'open-file': (GObject.SIGNAL_ACTION, None, ()),
|
||||
'save-file-as': (GObject.SIGNAL_ACTION, None, ()),
|
||||
'new-file': (GObject.SIGNAL_ACTION, None, ()),
|
||||
'toggle-bibtex': (GObject.SIGNAL_ACTION, None, ()),
|
||||
'toggle-preview': (GObject.SIGNAL_ACTION, None, ()),
|
||||
'close-window': (GObject.SIGNAL_ACTION, None, ())
|
||||
}
|
||||
# Set current theme
|
||||
self.apply_current_theme()
|
||||
self.get_style_context().add_class('uberwriter-window')
|
||||
|
||||
def apply_current_theme(self):
|
||||
"""Adjusts both the window and the CSD for the current theme.
|
||||
def apply_current_theme(self, *_):
|
||||
"""Adjusts the window, CSD and preview for the current theme.
|
||||
"""
|
||||
# Get current theme
|
||||
theme, changed = Theme.get_current_changed()
|
||||
if changed:
|
||||
# Set theme variant (dark/light)
|
||||
Gtk.Settings.get_default().set_property(
|
||||
"gtk-application-prefer-dark-theme",
|
||||
GLib.Variant("b", theme.is_dark))
|
||||
|
||||
self.markup_buffer.update_style()
|
||||
# Set theme css
|
||||
style_provider = Gtk.CssProvider()
|
||||
style_provider.load_from_path(theme.gtk_css_path)
|
||||
Gtk.StyleContext.add_provider_for_screen(
|
||||
self.get_screen(), style_provider,
|
||||
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
|
||||
|
||||
# Reload preview if it exists, otherwise redraw contents of window (self)
|
||||
if self.preview_webview:
|
||||
self.show_preview()
|
||||
else:
|
||||
# Reload preview if it exists
|
||||
self.reload_preview()
|
||||
|
||||
# Redraw contents of window
|
||||
self.queue_draw()
|
||||
|
||||
def scrolled(self, widget):
|
||||
"""if window scrolled + focusmode make font black again"""
|
||||
# if self.focusmode:
|
||||
# if self.textchange == False:
|
||||
# if self.scroll_count >= 4:
|
||||
# self.TextBuffer.apply_tag(
|
||||
# self.MarkupBuffer.blackfont,
|
||||
# self.TextBuffer.get_start_iter(),
|
||||
# self.TextBuffer.get_end_iter())
|
||||
# else:
|
||||
# self.scroll_count += 1
|
||||
# else:
|
||||
# self.scroll_count = 0
|
||||
# self.textchange = False
|
||||
|
||||
def paste_done(self, *_):
|
||||
self.markup_buffer.markup_buffer(0)
|
||||
|
||||
def init_typewriter(self):
|
||||
"""put the cursor at the center of the screen by setting top and
|
||||
bottom margins to height/2
|
||||
"""
|
||||
|
||||
editor_height = self.text_editor.get_allocation().height
|
||||
self.text_editor.props.top_margin = editor_height / 2
|
||||
self.text_editor.props.bottom_margin = editor_height / 2
|
||||
|
||||
def remove_typewriter(self):
|
||||
"""set margins to default values
|
||||
"""
|
||||
|
||||
self.text_editor.props.top_margin = 80
|
||||
self.text_editor.props.bottom_margin = 16
|
||||
self.text_change_event = self.text_buffer.connect(
|
||||
'changed', self.text_changed)
|
||||
|
||||
def get_text(self):
|
||||
"""get text from self.text_buffer
|
||||
"""
|
||||
|
||||
start_iter = self.text_buffer.get_start_iter()
|
||||
end_iter = self.text_buffer.get_end_iter()
|
||||
return self.text_buffer.get_text(start_iter, end_iter, False)
|
||||
|
||||
def update_line_and_char_count(self):
|
||||
"""it... it updates line and characters count
|
||||
"""
|
||||
|
||||
if self.status_bar_visible is False:
|
||||
return
|
||||
self.char_count.set_text(str(self.text_buffer.get_char_count()))
|
||||
text = self.get_text()
|
||||
text = self.text_view.get_text()
|
||||
self.char_count.set_text(str(len(text)))
|
||||
words = re.split(self.WORDCOUNT, text)
|
||||
length = len(words)
|
||||
# Last word a "space"
|
||||
|
@ -336,12 +210,7 @@ class Window(Gtk.ApplicationWindow):
|
|||
length = 0
|
||||
self.word_count.set_text(str(length))
|
||||
|
||||
def mark_set(self, _buffer, _location, mark, _data=None):
|
||||
if mark.get_name() in ['insert', 'gtk_drag_target']:
|
||||
self.check_scroll(mark)
|
||||
return True
|
||||
|
||||
def text_changed(self, *_args):
|
||||
def on_text_changed(self, *_args):
|
||||
"""called when the text changes, sets the self.did_change to true and
|
||||
updates the title and the counters to reflect that
|
||||
"""
|
||||
|
@ -351,12 +220,8 @@ class Window(Gtk.ApplicationWindow):
|
|||
title = self.get_title()
|
||||
self.set_headerbar_title("* " + title)
|
||||
|
||||
self.markup_buffer.markup_buffer(1)
|
||||
self.textchange = True
|
||||
|
||||
self.buffer_modified_for_status_bar = True
|
||||
self.update_line_and_char_count()
|
||||
self.check_scroll(self.text_buffer.get_insert())
|
||||
|
||||
def set_fullscreen(self, state):
|
||||
"""Puts the application in fullscreen mode and show/hides
|
||||
|
@ -374,184 +239,61 @@ class Window(Gtk.ApplicationWindow):
|
|||
self.unfullscreen()
|
||||
self.fs_headerbar.events.hide()
|
||||
|
||||
self.text_editor.grab_focus()
|
||||
self.text_view.grab_focus()
|
||||
|
||||
def set_focus_mode(self, state):
|
||||
"""toggle focusmode
|
||||
"""
|
||||
|
||||
if state.get_boolean():
|
||||
self.init_typewriter()
|
||||
self.markup_buffer.focusmode_highlight()
|
||||
self.focusmode = True
|
||||
self.text_editor.grab_focus()
|
||||
self.check_scroll(self.text_buffer.get_insert())
|
||||
if self.spell_checker:
|
||||
self.spell_checker._misspelled.set_property('underline', 0)
|
||||
self.click_event = self.text_editor.connect("button-release-event",
|
||||
self.on_focusmode_click)
|
||||
else:
|
||||
self.remove_typewriter()
|
||||
self.focusmode = False
|
||||
self.text_buffer.remove_tag(self.markup_buffer.unfocused_text,
|
||||
self.text_buffer.get_start_iter(),
|
||||
self.text_buffer.get_end_iter())
|
||||
self.text_buffer.remove_tag(self.markup_buffer.blackfont,
|
||||
self.text_buffer.get_start_iter(),
|
||||
self.text_buffer.get_end_iter())
|
||||
|
||||
self.markup_buffer.markup_buffer(1)
|
||||
self.text_editor.grab_focus()
|
||||
self.update_line_and_char_count()
|
||||
self.check_scroll()
|
||||
if self.spell_checker:
|
||||
self.spell_checker._misspelled.set_property('underline', 4)
|
||||
_click_event = self.text_editor.disconnect(self.click_event)
|
||||
focus_mode = state.get_boolean()
|
||||
self.text_view.set_focus_mode(focus_mode)
|
||||
if self.spell_checker:
|
||||
self.spell_checker._misspelled.set_property('underline', 0 if focus_mode else 4)
|
||||
self.text_view.grab_focus()
|
||||
|
||||
def set_hemingway_mode(self, state):
|
||||
"""toggle hemingwaymode
|
||||
"""
|
||||
self.text_editor.can_delete = not state.get_boolean()
|
||||
self.text_editor.grab_focus()
|
||||
|
||||
def on_focusmode_click(self, *_args):
|
||||
"""call MarkupBuffer to mark as bold the line where the cursor is
|
||||
"""
|
||||
self.text_view.set_hemingway_mode(state.get_boolean())
|
||||
self.text_view.grab_focus()
|
||||
|
||||
self.markup_buffer.markup_buffer(1)
|
||||
|
||||
def scroll_smoothly(self, widget, frame_clock, _data=None):
|
||||
if self.smooth_scroll_data['target_pos'] == -1:
|
||||
return True
|
||||
|
||||
def ease_out_cubic(time):
|
||||
time = time - 1
|
||||
return pow(time, 3) + 1
|
||||
|
||||
now = frame_clock.get_frame_time()
|
||||
if self.smooth_scroll_acttarget != self.smooth_scroll_data['target_pos']:
|
||||
self.smooth_scroll_starttime = now
|
||||
self.smooth_scroll_endtime = now + \
|
||||
self.smooth_scroll_data['duration'] * 100
|
||||
self.smooth_scroll_acttarget = self.smooth_scroll_data['target_pos']
|
||||
|
||||
if now < self.smooth_scroll_endtime:
|
||||
time = float(now - self.smooth_scroll_starttime) / float(
|
||||
self.smooth_scroll_endtime - self.smooth_scroll_starttime)
|
||||
else:
|
||||
time = 1
|
||||
pos = self.smooth_scroll_data['source_pos'] \
|
||||
+ (time * (self.smooth_scroll_data['target_pos']
|
||||
- self.smooth_scroll_data['source_pos']))
|
||||
widget.get_vadjustment().props.value = pos
|
||||
self.smooth_scroll_data['target_pos'] = -1
|
||||
return True
|
||||
|
||||
time = ease_out_cubic(time)
|
||||
pos = self.smooth_scroll_data['source_pos'] \
|
||||
+ (time * (self.smooth_scroll_data['target_pos']
|
||||
- self.smooth_scroll_data['source_pos']))
|
||||
widget.get_vadjustment().props.value = pos
|
||||
return True # continue ticking
|
||||
|
||||
def check_scroll(self, mark=None):
|
||||
gradient_offset = 80
|
||||
buf = self.text_editor.get_buffer()
|
||||
if mark:
|
||||
ins_it = buf.get_iter_at_mark(mark)
|
||||
else:
|
||||
ins_it = buf.get_iter_at_mark(buf.get_insert())
|
||||
loc_rect = self.text_editor.get_iter_location(ins_it)
|
||||
|
||||
# alignment offset added from top
|
||||
pos_y = loc_rect.y + loc_rect.height + self.text_editor.props.top_margin # pylint: disable=no-member
|
||||
|
||||
ha = self.scrolled_window.get_vadjustment()
|
||||
if ha.props.page_size < gradient_offset:
|
||||
return
|
||||
pos = pos_y - ha.props.value
|
||||
# print("pos: %i, pos_y %i, page_sz: %i, val: %i" % (pos, pos_y, ha.props.page_size
|
||||
# - gradient_offset, ha.props.value))
|
||||
# global t, amount, initvadjustment
|
||||
target_pos = -1
|
||||
if self.focusmode:
|
||||
# print("pos: %i > %i" % (pos, ha.props.page_size * 0.5))
|
||||
if pos != (ha.props.page_size * 0.5):
|
||||
target_pos = pos_y - (ha.props.page_size * 0.5)
|
||||
elif pos > ha.props.page_size - gradient_offset - 60:
|
||||
target_pos = pos_y - ha.props.page_size + gradient_offset + 40
|
||||
elif pos < gradient_offset:
|
||||
target_pos = pos_y - gradient_offset
|
||||
self.smooth_scroll_data = {
|
||||
'target_pos': target_pos,
|
||||
'source_pos': ha.props.value,
|
||||
'duration': 2000
|
||||
}
|
||||
if self.smooth_scroll_tickid == -1:
|
||||
self.smooth_scroll_tickid = self.scrolled_window.add_tick_callback(
|
||||
self.scroll_smoothly)
|
||||
|
||||
def window_resize(self, widget, _data=None):
|
||||
def window_resize(self, window, event=None):
|
||||
"""set paddings dependant of the window size
|
||||
"""
|
||||
|
||||
# To calc padding top / bottom
|
||||
self.window_height = widget.get_allocation().height
|
||||
w_width = widget.get_allocation().width
|
||||
# Calculate left / right margin
|
||||
# Adjust text editor width depending on window width, so that:
|
||||
# - The number of characters per line is adequate (http://webtypography.net/2.1.2)
|
||||
# - The number of characters stays constant while resizing the window / font
|
||||
# - There is enough text margin for MarkupBuffer to apply indents / negative margins
|
||||
#
|
||||
# TODO: Avoid hard-coding. Font size is clearer than unclear dimensions, but not ideal.
|
||||
w_width = event.width if event else window.get_allocation().width
|
||||
if w_width < 900:
|
||||
width_request = 600
|
||||
self.markup_buffer.set_multiplier(8)
|
||||
self.current_font_size = 12
|
||||
self.alignment_padding = 30
|
||||
lm = 7 * 8
|
||||
self.get_style_context().remove_class("medium")
|
||||
self.get_style_context().remove_class("large")
|
||||
font_size = 14
|
||||
self.get_style_context().add_class("small")
|
||||
self.get_style_context().remove_class("large")
|
||||
|
||||
elif w_width < 1400:
|
||||
width_request = 800
|
||||
self.markup_buffer.set_multiplier(10)
|
||||
self.current_font_size = 15
|
||||
self.alignment_padding = 40
|
||||
lm = 7 * 10
|
||||
elif w_width < 1280:
|
||||
font_size = 16
|
||||
self.get_style_context().remove_class("small")
|
||||
self.get_style_context().remove_class("large")
|
||||
self.get_style_context().add_class("medium")
|
||||
|
||||
else:
|
||||
width_request = 1000
|
||||
self.markup_buffer.set_multiplier(13)
|
||||
self.current_font_size = 17
|
||||
self.alignment_padding = 60
|
||||
lm = 7 * 13
|
||||
self.get_style_context().remove_class("medium")
|
||||
font_size = 18
|
||||
self.get_style_context().remove_class("small")
|
||||
self.get_style_context().add_class("large")
|
||||
|
||||
self.editor_alignment.props.margin_bottom = 0
|
||||
self.editor_alignment.props.margin_top = 0
|
||||
self.text_editor.set_left_margin(lm)
|
||||
self.text_editor.set_right_margin(lm)
|
||||
font_width = int(font_size * 1/1.6) # Ratio specific to Fira Mono
|
||||
width = 67 * font_width - 1 # 66 characters
|
||||
horizontal_margin = 8 * font_width # 8 characters
|
||||
width_request = width + horizontal_margin * 2
|
||||
|
||||
self.markup_buffer.recalculate(lm)
|
||||
|
||||
if self.focusmode:
|
||||
self.remove_typewriter()
|
||||
self.init_typewriter()
|
||||
|
||||
if self.text_editor.props.width_request != width_request: # pylint: disable=no-member
|
||||
self.text_editor.props.width_request = width_request
|
||||
if self.text_view.props.width_request != width_request:
|
||||
self.text_view.props.width_request = width_request
|
||||
self.text_view.set_left_margin(horizontal_margin)
|
||||
self.text_view.set_right_margin(horizontal_margin)
|
||||
self.scrolled_window.props.width_request = width_request
|
||||
alloc = self.text_editor.get_allocation()
|
||||
alloc.width = width_request
|
||||
self.text_editor.size_allocate(alloc)
|
||||
|
||||
def style_changed(self, _widget, _data=None):
|
||||
pgc = self.text_editor.get_pango_context()
|
||||
mets = pgc.get_metrics()
|
||||
self.markup_buffer.set_multiplier(
|
||||
Pango.units_to_double(mets.get_approximate_char_width()) + 1)
|
||||
|
||||
# TODO: refactorizable
|
||||
def save_document(self, _widget=None, _data=None):
|
||||
|
@ -563,7 +305,7 @@ class Window(Gtk.ApplicationWindow):
|
|||
LOGGER.info("saving")
|
||||
filename = self.filename
|
||||
file_to_save = codecs.open(filename, encoding="utf-8", mode='w')
|
||||
file_to_save.write(self.get_text())
|
||||
file_to_save.write(self.text_view.get_text())
|
||||
file_to_save.close()
|
||||
if self.did_change:
|
||||
self.did_change = False
|
||||
|
@ -574,7 +316,7 @@ class Window(Gtk.ApplicationWindow):
|
|||
filefilter = Gtk.FileFilter.new()
|
||||
filefilter.add_mime_type('text/x-markdown')
|
||||
filefilter.add_mime_type('text/plain')
|
||||
filefilter.set_name('MarkDown (.md)')
|
||||
filefilter.set_name('Markdown (.md)')
|
||||
filechooser = Gtk.FileChooserDialog(
|
||||
_("Save your File"),
|
||||
self,
|
||||
|
@ -597,7 +339,7 @@ class Window(Gtk.ApplicationWindow):
|
|||
pass
|
||||
|
||||
file_to_save = codecs.open(filename, encoding="utf-8", mode='w')
|
||||
file_to_save.write(self.get_text())
|
||||
file_to_save.write(self.text_view.get_text())
|
||||
file_to_save.close()
|
||||
|
||||
self.set_filename(filename)
|
||||
|
@ -638,7 +380,7 @@ class Window(Gtk.ApplicationWindow):
|
|||
pass
|
||||
|
||||
file_to_save = codecs.open(filename, encoding="utf-8", mode='w')
|
||||
file_to_save.write(self.get_text())
|
||||
file_to_save.write(self.text_view.get_text())
|
||||
file_to_save.close()
|
||||
|
||||
self.set_filename(filename)
|
||||
|
@ -661,15 +403,9 @@ class Window(Gtk.ApplicationWindow):
|
|||
"""Copies only html without headers etc. to Clipboard
|
||||
"""
|
||||
|
||||
args = ['pandoc', '--from=markdown', '--to=html5']
|
||||
proc = subprocess.Popen(args, stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE)
|
||||
|
||||
text = bytes(self.get_text(), "utf-8")
|
||||
output = proc.communicate(text)[0]
|
||||
|
||||
output = helpers.pandoc_convert(self.text_view.get_text())
|
||||
clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
|
||||
clipboard.set_text(output.decode("utf-8"), -1)
|
||||
clipboard.set_text(output, -1)
|
||||
clipboard.store()
|
||||
|
||||
def open_document(self, _widget=None):
|
||||
|
@ -679,19 +415,24 @@ class Window(Gtk.ApplicationWindow):
|
|||
if self.check_change() == Gtk.ResponseType.CANCEL:
|
||||
return
|
||||
|
||||
filefilter = Gtk.FileFilter.new()
|
||||
filefilter.add_mime_type('text/x-markdown')
|
||||
filefilter.add_mime_type('text/plain')
|
||||
filefilter.set_name(_('MarkDown or Plain Text'))
|
||||
markdown_filter = Gtk.FileFilter.new()
|
||||
markdown_filter.add_mime_type('text/markdown')
|
||||
markdown_filter.add_mime_type('text/x-markdown')
|
||||
markdown_filter.set_name(_('Markdown Files'))
|
||||
|
||||
plaintext_filter = Gtk.FileFilter.new()
|
||||
plaintext_filter.add_mime_type('text/plain')
|
||||
plaintext_filter.set_name(_('Plain Text Files'))
|
||||
|
||||
filechooser = Gtk.FileChooserDialog(
|
||||
_("Open a .md-File"),
|
||||
_("Open a .md file"),
|
||||
self,
|
||||
Gtk.FileChooserAction.OPEN,
|
||||
("_Cancel", Gtk.ResponseType.CANCEL,
|
||||
"_Open", Gtk.ResponseType.OK)
|
||||
)
|
||||
filechooser.add_filter(filefilter)
|
||||
filechooser.add_filter(markdown_filter)
|
||||
filechooser.add_filter(plaintext_filter)
|
||||
response = filechooser.run()
|
||||
if response == Gtk.ResponseType.OK:
|
||||
filename = filechooser.get_filename()
|
||||
|
@ -705,14 +446,14 @@ class Window(Gtk.ApplicationWindow):
|
|||
"""Show dialog to prevent loss of unsaved changes
|
||||
"""
|
||||
|
||||
if self.did_change and self.get_text():
|
||||
if self.did_change and self.text_view.get_text():
|
||||
dialog = Gtk.MessageDialog(self,
|
||||
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
|
||||
Gtk.MessageType.WARNING,
|
||||
Gtk.ButtonsType.NONE,
|
||||
_("You have not saved your changes.")
|
||||
)
|
||||
dialog.add_button(_("Close without Saving"), Gtk.ResponseType.NO)
|
||||
dialog.add_button(_("Close without saving"), Gtk.ResponseType.NO)
|
||||
dialog.add_button(_("Cancel"), Gtk.ResponseType.CANCEL)
|
||||
dialog.add_button(_("Save now"), Gtk.ResponseType.YES)
|
||||
# dialog.set_default_size(200, 60)
|
||||
|
@ -739,9 +480,7 @@ class Window(Gtk.ApplicationWindow):
|
|||
|
||||
if self.check_change() == Gtk.ResponseType.CANCEL:
|
||||
return
|
||||
self.text_buffer.set_text('')
|
||||
self.text_editor.undos = []
|
||||
self.text_editor.redos = []
|
||||
self.text_view.clear()
|
||||
|
||||
self.did_change = False
|
||||
self.set_filename()
|
||||
|
@ -752,20 +491,20 @@ class Window(Gtk.ApplicationWindow):
|
|||
"""
|
||||
self.sidebar.toggle_sidebar()
|
||||
|
||||
def toggle_spellcheck(self, status):
|
||||
def toggle_spellcheck(self, state):
|
||||
"""Enable/disable the autospellchecking
|
||||
|
||||
Arguments:
|
||||
status {gtk bool} -- Desired status of the spellchecking
|
||||
"""
|
||||
|
||||
if status.get_boolean():
|
||||
if state.get_boolean():
|
||||
try:
|
||||
self.spell_checker.enable()
|
||||
except:
|
||||
try:
|
||||
self.spell_checker = SpellChecker(
|
||||
self.text_editor, locale.getdefaultlocale()[0],
|
||||
self.text_view, locale.getdefaultlocale()[0],
|
||||
collapse=False)
|
||||
if self.auto_correct:
|
||||
self.auto_correct.set_language(self.spell_checker.language)
|
||||
|
@ -781,7 +520,7 @@ class Window(Gtk.ApplicationWindow):
|
|||
_("You can not enable the Spell Checker.")
|
||||
)
|
||||
dialog.format_secondary_text(
|
||||
_("Please install 'hunspell' or 'aspell' dictionarys"
|
||||
_("Please install 'hunspell' or 'aspell' dictionaries"
|
||||
+ " for your language from the software center."))
|
||||
_response = dialog.run()
|
||||
return
|
||||
|
@ -793,45 +532,17 @@ class Window(Gtk.ApplicationWindow):
|
|||
pass
|
||||
return
|
||||
|
||||
def on_drag_data_received(self, _widget, drag_context, _x, _y,
|
||||
data, info, time):
|
||||
"""Handle drag and drop events"""
|
||||
if info == 1:
|
||||
# uri target
|
||||
uris = data.get_uris()
|
||||
for uri in uris:
|
||||
uri = urllib.parse.unquote_plus(uri)
|
||||
mime = mimetypes.guess_type(uri)
|
||||
def toggle_gradient_overlay(self, state):
|
||||
"""Toggle the gradient overlay
|
||||
|
||||
if mime[0] is not None and mime[0].startswith('image'):
|
||||
if uri.startswith("file://"):
|
||||
uri = uri[7:]
|
||||
text = "![Insert image title here](%s)" % uri
|
||||
limit_left = 2
|
||||
limit_right = 23
|
||||
else:
|
||||
text = "[Insert link title here](%s)" % uri
|
||||
limit_left = 1
|
||||
limit_right = 22
|
||||
self.text_buffer.place_cursor(self.text_buffer.get_iter_at_mark(
|
||||
self.text_buffer.get_mark('gtk_drag_target')))
|
||||
self.text_buffer.insert_at_cursor(text)
|
||||
insert_mark = self.text_buffer.get_insert()
|
||||
selection_bound = self.text_buffer.get_selection_bound()
|
||||
cursor_iter = self.text_buffer.get_iter_at_mark(insert_mark)
|
||||
cursor_iter.backward_chars(len(text) - limit_left)
|
||||
self.text_buffer.move_mark(insert_mark, cursor_iter)
|
||||
cursor_iter.forward_chars(limit_right)
|
||||
self.text_buffer.move_mark(selection_bound, cursor_iter)
|
||||
Arguments:
|
||||
state {gtk bool} -- Desired state of the gradient overlay (enabled/disabled)
|
||||
"""
|
||||
|
||||
elif info == 2:
|
||||
# Text target
|
||||
self.text_buffer.place_cursor(self.text_buffer.get_iter_at_mark(
|
||||
self.text_buffer.get_mark('gtk_drag_target')))
|
||||
self.text_buffer.insert_at_cursor(data.get_text())
|
||||
Gtk.drag_finish(drag_context, True, True, time)
|
||||
self.present()
|
||||
return False
|
||||
if state.get_boolean():
|
||||
self.overlay_id = self.scrolled_window.connect_after("draw", self.draw_gradient)
|
||||
elif self.overlay_id:
|
||||
self.scrolled_window.disconnect(self.overlay_id)
|
||||
|
||||
def toggle_preview(self, state):
|
||||
"""Toggle the preview mode
|
||||
|
@ -849,8 +560,8 @@ class Window(Gtk.ApplicationWindow):
|
|||
|
||||
def show_text_editor(self):
|
||||
self.scrolled_window.remove(self.scrolled_window.get_child())
|
||||
self.scrolled_window.add(self.text_editor)
|
||||
self.text_editor.show()
|
||||
self.scrolled_window.add(self.text_view)
|
||||
self.text_view.show()
|
||||
self.preview_webview.destroy()
|
||||
self.preview_webview = None
|
||||
self.queue_draw()
|
||||
|
@ -862,49 +573,17 @@ class Window(Gtk.ApplicationWindow):
|
|||
self.preview_webview.show()
|
||||
self.queue_draw()
|
||||
else:
|
||||
# Insert a tag with ID to scroll to
|
||||
# self.TextBuffer.insert_at_cursor('<span id="scroll_mark"></span>')
|
||||
# TODO
|
||||
# Find a way to find the next header, scroll to the next header.
|
||||
# TODO: provide a local version of mathjax
|
||||
|
||||
# We need to convert relative routes to absolute ones
|
||||
# For that first we need to know if the file is saved:
|
||||
if self.filename:
|
||||
base_path = os.path.dirname(self.filename)
|
||||
else:
|
||||
base_path = ''
|
||||
os.environ['PANDOC_PREFIX'] = base_path + '/'
|
||||
|
||||
args = ['pandoc',
|
||||
'-s',
|
||||
'--from=markdown',
|
||||
'--to=html5',
|
||||
args = ['--standalone',
|
||||
'--mathjax',
|
||||
'--css=' + Theme.get_current().web_css_path,
|
||||
'--quiet',
|
||||
'--lua-filter=' + helpers.get_script_path('relative_to_absolute.lua'),
|
||||
'--lua-filter=' + helpers.get_script_path('task-list.lua')]
|
||||
|
||||
# TODO: find a way to pass something like this instead of the quiet arg
|
||||
#'--metadata pagetitle="test"',
|
||||
|
||||
proc = subprocess.Popen(
|
||||
args, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
||||
|
||||
text = bytes(self.get_text(), "utf-8")
|
||||
output = proc.communicate(text)[0]
|
||||
output = helpers.pandoc_convert(self.text_view.get_text(), to="html5", args=args)
|
||||
|
||||
if self.preview_webview is None:
|
||||
self.preview_webview = WebKit.WebView()
|
||||
self.preview_webview.get_settings().set_allow_universal_access_from_file_urls(True)
|
||||
|
||||
# Delete the cursor-scroll mark again
|
||||
# cursor_iter = self.TextBuffer.get_iter_at_mark(self.TextBuffer.get_insert())
|
||||
# begin_del = cursor_iter.copy()
|
||||
# begin_del.backward_chars(30)
|
||||
# self.TextBuffer.delete(begin_del, cursor_iter)
|
||||
|
||||
# Show preview once the load is finished
|
||||
self.preview_webview.connect("load-changed", self.on_preview_load_change)
|
||||
|
||||
|
@ -912,7 +591,11 @@ class Window(Gtk.ApplicationWindow):
|
|||
# but local files are opened in appropriate apps:
|
||||
self.preview_webview.connect("decide-policy", self.on_click_link)
|
||||
|
||||
self.preview_webview.load_html(output.decode("utf-8"), 'file://localhost/')
|
||||
self.preview_webview.load_html(output, 'file://localhost/')
|
||||
|
||||
def reload_preview(self):
|
||||
if self.preview_webview:
|
||||
self.show_preview()
|
||||
|
||||
def load_file(self, filename=None):
|
||||
"""Open File from command line or open / open recent etc."""
|
||||
|
@ -923,19 +606,14 @@ class Window(Gtk.ApplicationWindow):
|
|||
if filename.startswith('file://'):
|
||||
filename = filename[7:]
|
||||
filename = urllib.parse.unquote_plus(filename)
|
||||
self.text_view.clear()
|
||||
try:
|
||||
if not os.path.exists(filename):
|
||||
self.text_buffer.set_text("")
|
||||
else:
|
||||
if os.path.exists(filename):
|
||||
current_file = codecs.open(filename, encoding="utf-8", mode='r')
|
||||
self.text_buffer.set_text(current_file.read())
|
||||
self.text_view.set_text(current_file.read())
|
||||
current_file.close()
|
||||
self.markup_buffer.markup_buffer(0)
|
||||
|
||||
self.set_headerbar_title(
|
||||
os.path.basename(filename) + self.title_end)
|
||||
self.text_editor.undo_stack = []
|
||||
self.text_editor.redo_stack = []
|
||||
self.set_headerbar_title(os.path.basename(filename) + self.title_end)
|
||||
self.set_filename(filename)
|
||||
|
||||
except Exception:
|
||||
|
@ -967,7 +645,7 @@ class Window(Gtk.ApplicationWindow):
|
|||
|
||||
response = self.export.dialog.run()
|
||||
if response == 1:
|
||||
self.export.export(bytes(self.get_text(), "utf-8"))
|
||||
self.export.export(bytes(self.text_view.get_text(), "utf-8"))
|
||||
|
||||
self.export.dialog.destroy()
|
||||
|
||||
|
@ -991,7 +669,7 @@ class Window(Gtk.ApplicationWindow):
|
|||
if (self.was_motion is False
|
||||
and self.status_bar_visible
|
||||
and self.buffer_modified_for_status_bar
|
||||
and self.text_editor.props.has_focus): # pylint: disable=no-member
|
||||
and self.text_view.props.has_focus): # pylint: disable=no-member
|
||||
# self.status_bar.set_state_flags(Gtk.StateFlags.INSENSITIVE, True)
|
||||
self.statusbar_revealer.set_reveal_child(False)
|
||||
self.headerbar.hb_revealer.set_reveal_child(False)
|
||||
|
@ -1064,24 +742,6 @@ class Window(Gtk.ApplicationWindow):
|
|||
cr.set_source(lg_btm)
|
||||
cr.fill()
|
||||
|
||||
def use_experimental_features(self, _val):
|
||||
"""use experimental features
|
||||
"""
|
||||
pass
|
||||
# try:
|
||||
# self.auto_correct = AutoCorrect(
|
||||
# self.text_editor, self.text_buffer)
|
||||
# except:
|
||||
# LOGGER.debug("Couldn't install autocorrect.")
|
||||
|
||||
# self.plugins = [BibTex(self)]
|
||||
|
||||
# def alt_mod(self, _widget, event, _data=None):
|
||||
# # TODO: Click and open when alt is pressed
|
||||
# if event.state & Gdk.ModifierType.MOD2_MASK:
|
||||
# LOGGER.info("Alt pressed")
|
||||
# return
|
||||
|
||||
def on_delete_called(self, _widget, _data=None):
|
||||
"""Called when the TexteditorWindow is closed.
|
||||
"""
|
||||
|
@ -1091,7 +751,7 @@ class Window(Gtk.ApplicationWindow):
|
|||
return False
|
||||
|
||||
def on_mnu_close_activate(self, _widget, _data=None):
|
||||
"""Signal handler for closing the UberwriterWindow.
|
||||
"""Signal handler for closing the Window.
|
||||
Overriden from parent Window Class
|
||||
"""
|
||||
if self.on_delete_called(self): # Really destroy?
|
||||
|
|
Loading…
Reference in New Issue