apostrophe/uberwriter/UberwriterWindow.py

1322 lines
49 KiB
Python
Raw Normal View History

2014-07-06 20:35:24 +00:00
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
# BEGIN LICENSE
2014-07-06 20:35:24 +00:00
# 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
2014-07-06 20:35:24 +00:00
2018-04-11 15:30:54 +00:00
import locale
2014-07-06 20:35:24 +00:00
import subprocess
import os
import codecs
import webbrowser
import urllib
import logging
2014-07-06 20:35:24 +00:00
import mimetypes
2018-06-28 17:38:43 +00:00
import re
from gettext import gettext as _
2014-07-06 20:35:24 +00:00
import gi
gi.require_version('WebKit2', '4.0') # pylint: disable=wrong-import-position
from gi.repository import Gtk, Gdk, GObject, GLib # pylint: disable=E0611
2017-12-07 06:56:39 +00:00
from gi.repository import WebKit2 as WebKit
2014-07-06 20:35:24 +00:00
from gi.repository import Pango # pylint: disable=E0611
import cairo
# import cairo.Pattern, cairo.SolidPattern
2014-07-06 20:35:24 +00:00
2018-06-28 17:38:43 +00:00
from uberwriter_lib import helpers
from uberwriter_lib.helpers import get_builder
from uberwriter_lib.AppWindow import Window
from uberwriter_lib.gtkspellcheck import SpellChecker
2018-04-11 17:03:23 +00:00
2014-07-06 20:35:24 +00:00
from .MarkupBuffer import MarkupBuffer
from .UberwriterTextEditor import TextEditor
from .UberwriterInlinePreview import UberwriterInlinePreview
from .UberwriterSidebar import UberwriterSidebar
from .UberwriterSearchAndReplace import UberwriterSearchAndReplace
2018-04-15 00:33:02 +00:00
from .Settings import Settings
2017-12-07 06:56:39 +00:00
# from .UberwriterAutoCorrect import UberwriterAutoCorrect
2014-07-06 20:35:24 +00:00
2018-07-17 10:47:47 +00:00
from .UberwriterExportDialog import Export
2017-12-07 06:56:39 +00:00
# from .plugins.bibtex import BibTex
2014-07-06 20:35:24 +00:00
# Some Globals
# TODO move them somewhere for better
# accesibility from other files
LOGGER = logging.getLogger('uberwriter')
2014-07-06 20:35:24 +00:00
CONFIG_PATH = os.path.expanduser("~/.config/uberwriter/")
# See texteditor_lib.Window.py for more details about how this class works
class UberwriterWindow(Window):
2014-07-06 20:35:24 +00:00
#__gtype_name__ = "UberwriterWindow"
2018-06-28 17:38:43 +00:00
__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, ())
}
2014-07-06 20:35:24 +00:00
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
2014-07-06 20:35:24 +00:00
def paste_done(self, *_):
self.markup_buffer.markup_buffer(0)
2014-07-06 20:35:24 +00:00
def init_typewriter(self):
"""put the cursor at the center of the screen by setting top and
bottom margins to height/2
"""
self.editor_height = self.text_editor.get_allocation().height
self.text_editor.props.top_margin = self.editor_height / 2
self.text_editor.props.bottom_margin = self.editor_height / 2
2014-07-06 20:35:24 +00:00
self.typewriter_initiated = True
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)
2014-07-06 20:35:24 +00:00
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)
2014-07-06 20:35:24 +00:00
2014-09-11 16:37:50 +00:00
WORDCOUNT = re.compile(r"(?!\-\w)[\s#*\+\-]+", re.UNICODE)
2014-07-06 20:35:24 +00:00
def update_line_and_char_count(self):
"""it... it updates line and characters count
"""
2018-06-28 17:38:43 +00:00
if self.status_bar_visible is False:
2014-07-06 20:35:24 +00:00
return
self.char_count.set_text(str(self.text_buffer.get_char_count()))
2014-07-06 20:35:24 +00:00
text = self.get_text()
words = re.split(self.WORDCOUNT, text)
length = len(words)
# Last word a "space"
if not words[-1]:
2014-07-06 20:35:24 +00:00
length = length - 1
# First word a "space" (happens in focus mode...)
if not words[0]:
2014-07-06 20:35:24 +00:00
length = length - 1
if length == -1:
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
2014-07-06 20:35:24 +00:00
def 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
"""
2018-06-28 17:38:43 +00:00
if self.did_change is False:
2014-07-06 20:35:24 +00:00
self.did_change = True
title = self.get_title()
2014-08-08 11:25:57 +00:00
self.set_headerbar_title("* " + title)
2014-07-06 20:35:24 +00:00
self.markup_buffer.markup_buffer(1)
2014-07-06 20:35:24 +00:00
self.textchange = True
self.buffer_modified_for_status_bar = True
self.update_line_and_char_count()
self.check_scroll(self.text_buffer.get_insert())
2014-07-06 20:35:24 +00:00
def toggle_fullscreen(self, state):
"""Puts the application in fullscreen mode and show/hides
the poller for motion in the top border
Arguments:
state {almost bool} -- The desired fullscreen state of the window
"""
if state.get_boolean():
2014-07-06 20:35:24 +00:00
self.fullscreen()
self.fullscr_events.show()
2018-06-24 15:54:31 +00:00
2014-07-06 20:35:24 +00:00
else:
self.unfullscreen()
self.fullscr_events.hide()
2014-07-06 20:35:24 +00:00
self.text_editor.grab_focus()
2014-07-06 20:35:24 +00:00
def set_focusmode(self, state):
"""toggle focusmode
"""
if state.get_boolean():
2014-07-06 20:35:24 +00:00
self.init_typewriter()
self.markup_buffer.focusmode_highlight()
2014-07-06 20:35:24 +00:00
self.focusmode = True
self.text_editor.grab_focus()
self.check_scroll(self.text_buffer.get_insert())
if self.spellcheck:
self.spell_checker._misspelled.set_property('underline', 0)
self.click_event = self.text_editor.connect("button-release-event",
self.on_focusmode_click)
2014-07-06 20:35:24 +00:00
else:
self.remove_typewriter()
self.focusmode = False
self.text_buffer.remove_tag(self.markup_buffer.grayfont,
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()
2014-07-06 20:35:24 +00:00
self.update_line_and_char_count()
self.check_scroll()
if self.spellcheck:
2014-07-06 20:35:24 +00:00
self.SpellChecker._misspelled.set_property('underline', 4)
_click_event = self.text_editor.disconnect(self.click_event)
def on_focusmode_click(self, *_args):
"""call MarkupBuffer to mark as bold the line where the cursor is
"""
self.markup_buffer.markup_buffer(1)
2014-07-06 20:35:24 +00:00
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
2018-06-28 17:38:43 +00:00
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']
2018-06-28 17:38:43 +00:00
if now < self.smooth_scroll_endtime:
time = float(now - self.smooth_scroll_starttime) / float(
2018-06-30 00:11:47 +00:00
self.smooth_scroll_endtime - self.smooth_scroll_starttime)
else:
time = 1
2018-06-28 17:38:43 +00:00
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)
2018-06-28 17:38:43 +00:00
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
2018-06-28 17:38:43 +00:00
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,
2018-06-28 17:38:43 +00:00
'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):
"""set paddings dependant of the window size
"""
2014-07-06 20:35:24 +00:00
# To calc padding top / bottom
2017-12-22 15:01:37 +00:00
self.window_height = widget.get_allocation().height
w_width = widget.get_allocation().width
2014-07-06 20:35:24 +00:00
# Calculate left / right margin
width_request = 600
2018-06-28 17:38:43 +00:00
if w_width < 900:
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")
self.get_style_context().add_class("small")
2014-07-06 20:35:24 +00:00
2018-06-28 17:38:43 +00:00
elif w_width < 1400:
self.markup_buffer.set_multiplier(10)
width_request = 800
self.current_font_size = 15
self.alignment_padding = 40
lm = 7 * 10
self.get_style_context().remove_class("small")
self.get_style_context().remove_class("large")
self.get_style_context().add_class("medium")
2014-07-06 20:35:24 +00:00
else:
self.markup_buffer.set_multiplier(13)
self.current_font_size = 17
width_request = 1000
self.alignment_padding = 60
lm = 7 * 13
self.get_style_context().remove_class("medium")
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)
2014-07-06 20:35:24 +00:00
self.markup_buffer.recalculate(lm)
2014-07-06 20:35:24 +00:00
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
self.scrolled_window.props.width_request = width_request
alloc = self.text_editor.get_allocation()
alloc.width = width_request
self.text_editor.size_allocate(alloc)
2014-07-06 20:35:24 +00:00
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):
"""provide to the user a filechooser and save the document
where he wants. Call set_headbar_title after that
"""
2014-07-06 20:35:24 +00:00
if self.filename:
LOGGER.info("saving")
2014-07-06 20:35:24 +00:00
filename = self.filename
file_to_save = codecs.open(filename, encoding="utf-8", mode='w')
file_to_save.write(self.get_text())
file_to_save.close()
2014-07-06 20:35:24 +00:00
if self.did_change:
self.did_change = False
title = self.get_title()
2014-08-08 11:25:57 +00:00
self.set_headerbar_title(title[2:])
2014-07-06 20:35:24 +00:00
return Gtk.ResponseType.OK
filefilter = Gtk.FileFilter.new()
filefilter.add_mime_type('text/x-markdown')
filefilter.add_mime_type('text/plain')
filefilter.set_name('MarkDown (.md)')
filechooser = Gtk.FileChooserDialog(
_("Save your File"),
self,
Gtk.FileChooserAction.SAVE,
("_Cancel", Gtk.ResponseType.CANCEL,
"_Save", Gtk.ResponseType.OK)
)
2018-06-28 17:38:43 +00:00
filechooser.set_do_overwrite_confirmation(True)
filechooser.add_filter(filefilter)
response = filechooser.run()
if response == Gtk.ResponseType.OK:
filename = filechooser.get_filename()
if filename[-3:] != ".md":
filename = filename + ".md"
try:
self.recent_manager.add_item("file:/ " + filename)
except:
pass
2018-06-28 17:38:43 +00:00
file_to_save = codecs.open(filename, encoding="utf-8", mode='w')
file_to_save.write(self.get_text())
file_to_save.close()
2014-07-06 20:35:24 +00:00
self.set_filename(filename)
self.set_headerbar_title(
os.path.basename(filename) + self.title_end)
self.did_change = False
filechooser.destroy()
return response
filechooser.destroy()
return Gtk.ResponseType.CANCEL
2014-07-06 20:35:24 +00:00
def save_document_as(self, _widget=None, _data=None):
"""provide to the user a filechooser and save the document
where he wants. Call set_headbar_title after that
"""
2014-07-06 20:35:24 +00:00
filechooser = Gtk.FileChooserDialog(
"Save your File",
self,
Gtk.FileChooserAction.SAVE,
("_Cancel", Gtk.ResponseType.CANCEL,
"_Save", Gtk.ResponseType.OK)
)
2014-07-06 20:35:24 +00:00
filechooser.set_do_overwrite_confirmation(True)
if self.filename:
filechooser.set_filename(self.filename)
response = filechooser.run()
if response == Gtk.ResponseType.OK:
filename = filechooser.get_filename()
if filename[-3:] != ".md":
filename = filename + ".md"
try:
2018-06-28 17:38:43 +00:00
self.recent_manager.remove_item("file:/" + filename)
2014-07-06 20:35:24 +00:00
self.recent_manager.add_item("file:/ " + filename)
except:
pass
file_to_save = codecs.open(filename, encoding="utf-8", mode='w')
file_to_save.write(self.get_text())
file_to_save.close()
2018-06-28 17:38:43 +00:00
2018-05-06 18:00:14 +00:00
self.set_filename(filename)
self.set_headerbar_title(
os.path.basename(filename) + self.title_end)
2014-07-06 20:35:24 +00:00
try:
self.recent_manager.add_item(filename)
except:
pass
2018-06-28 17:38:43 +00:00
2014-07-06 20:35:24 +00:00
filechooser.destroy()
self.did_change = False
else:
2014-07-06 20:35:24 +00:00
filechooser.destroy()
return Gtk.ResponseType.CANCEL
2014-07-06 20:35:24 +00:00
def copy_html_to_clipboard(self, _widget=None, _date=None):
"""Copies only html without headers etc. to Clipboard
"""
2014-07-06 20:35:24 +00:00
2017-12-07 13:17:41 +00:00
args = ['pandoc', '--from=markdown', '-smart', '-thtml']
proc = subprocess.Popen(args, stdin=subprocess.PIPE,
stdout=subprocess.PIPE)
2014-07-06 20:35:24 +00:00
text = bytes(self.get_text(), "utf-8")
output = proc.communicate(text)[0]
clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
clipboard.set_text(output.decode("utf-8"), -1)
clipboard.store()
def open_document(self, _widget=None):
"""open the desired file
"""
2014-07-06 20:35:24 +00:00
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'))
filechooser = Gtk.FileChooserDialog(
_("Open a .md-File"),
self,
Gtk.FileChooserAction.OPEN,
("_Cancel", Gtk.ResponseType.CANCEL,
"_Open", Gtk.ResponseType.OK)
)
2014-07-06 20:35:24 +00:00
filechooser.add_filter(filefilter)
response = filechooser.run()
if response == Gtk.ResponseType.OK:
filename = filechooser.get_filename()
self.load_file(filename)
filechooser.destroy()
elif response == Gtk.ResponseType.CANCEL:
filechooser.destroy()
def check_change(self):
"""Show dialog to prevent loss of unsaved changes
"""
if self.did_change and self.get_text():
2014-07-06 20:35:24 +00:00
dialog = Gtk.MessageDialog(self,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
Gtk.MessageType.WARNING,
Gtk.ButtonsType.NONE,
_("You have not saved your changes.")
)
2014-07-06 20:35:24 +00:00
dialog.add_button(_("Close without Saving"), Gtk.ResponseType.NO)
dialog.add_button(_("Cancel"), Gtk.ResponseType.CANCEL)
dialog.add_button(_("Save now"), Gtk.ResponseType.YES)
2014-07-06 20:35:24 +00:00
dialog.set_title(_('Unsaved changes'))
dialog.set_default_size(200, 150)
dialog.set_default_response(Gtk.ResponseType.YES)
2014-07-06 20:35:24 +00:00
response = dialog.run()
2014-07-06 20:35:24 +00:00
if response == Gtk.ResponseType.YES:
if self.save_document() == Gtk.ResponseType.CANCEL:
2014-07-06 20:35:24 +00:00
dialog.destroy()
return self.check_change()
2014-07-06 20:35:24 +00:00
dialog.destroy()
return response
if response == Gtk.ResponseType.NO:
dialog.destroy()
return response
dialog.destroy()
return Gtk.ResponseType.CANCEL
def new_document(self, _widget=None):
"""create new document
"""
2014-07-06 20:35:24 +00:00
if self.check_change() == Gtk.ResponseType.CANCEL:
return
self.text_buffer.set_text('')
self.text_editor.undos = []
self.text_editor.redos = []
2014-07-06 20:35:24 +00:00
self.did_change = False
self.set_filename()
self.set_headerbar_title(_("New File") + self.title_end)
2014-07-06 20:35:24 +00:00
def menu_toggle_sidebar(self, _widget=None):
"""WIP
"""
2014-08-08 11:25:57 +00:00
self.sidebar.toggle_sidebar()
2018-06-23 19:34:53 +00:00
def toggle_spellcheck(self, status):
"""Enable/disable the autospellchecking
Arguments:
status {gtk bool} -- Desired status of the spellchecking
"""
2018-06-23 19:34:53 +00:00
if self.spellcheck:
if status.get_boolean():
self.spell_checker.enable()
2018-06-23 19:34:53 +00:00
else:
self.spell_checker.disable()
2018-06-23 19:34:53 +00:00
elif status.get_boolean():
self.spell_checker = SpellChecker(
self.text_editor, self, locale.getdefaultlocale()[0],
2018-06-30 00:11:47 +00:00
collapse=False)
2018-06-23 19:34:53 +00:00
if self.auto_correct:
self.auto_correct.set_language(self.spell_checker.language)
self.spell_checker.connect_language_change( # pylint: disable=no-member
self.auto_correct.set_language)
2018-06-23 19:34:53 +00:00
try:
self.spellcheck = True
except:
self.spell_checker = None
2018-06-23 19:34:53 +00:00
self.spellcheck = False
dialog = Gtk.MessageDialog(self,
Gtk.DialogFlags.MODAL \
| Gtk.DialogFlags.DESTROY_WITH_PARENT,
Gtk.MessageType.INFO,
Gtk.ButtonsType.NONE,
_("You can not enable the Spell Checker.")
)
dialog.format_secondary_text(
_("Please install 'hunspell' or 'aspell' dictionarys"
+ " for your language from the software center."))
_response = dialog.run()
2018-06-23 19:34:53 +00:00
return
2014-07-06 20:35:24 +00:00
return
def on_drag_data_received(self, _widget, drag_context, _x, _y,
2014-07-06 20:35:24 +00:00
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)
if mime[0] is not None and mime[0].startswith('image'):
if uri.startswith("file://"):
uri = uri[7:]
2014-07-06 20:35:24 +00:00
text = "![Insert image title here](%s)" % uri
limit_left = 2
limit_right = 23
2014-07-06 20:35:24 +00:00
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)
2014-07-06 20:35:24 +00:00
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)
2014-07-06 20:35:24 +00:00
self.present()
return False
2014-07-06 20:35:24 +00:00
2018-06-23 19:34:53 +00:00
def toggle_preview(self, state):
"""Toggle the preview mode
Arguments:
state {gtk bool} -- Desired state of the preview mode (enabled/disabled)
"""
2018-06-23 19:34:53 +00:00
if state.get_boolean():
2014-07-06 20:35:24 +00:00
# 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
2018-05-04 10:39:57 +00:00
# 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 + '/'
# Set the styles according the color theme
if self.settings.get_value("dark-mode"):
stylesheet = helpers.get_media_path('uberwriter_dark.css')
else:
stylesheet = helpers.get_media_path('uberwriter.css')
2014-07-06 20:35:24 +00:00
args = ['pandoc',
'-s',
2014-07-06 20:35:24 +00:00
'--from=markdown',
'--to=html5',
2014-07-06 20:35:24 +00:00
'--mathjax',
'--css=' + stylesheet,
'--lua-filter=' +
helpers.get_script_path('relative_to_absolute.lua'),
'--lua-filter=' + helpers.get_script_path('task-list.lua')]
2014-07-06 20:35:24 +00:00
proc = subprocess.Popen(
args, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
2014-07-06 20:35:24 +00:00
text = bytes(self.get_text(), "utf-8")
output = proc.communicate(text)[0]
2014-07-06 20:35:24 +00:00
# Load in Webview and scroll to #ID
self.webview = WebKit.WebView()
self.webview_settings = self.webview.get_settings()
self.webview_settings.set_allow_universal_access_from_file_urls(
True)
2017-12-07 13:17:41 +00:00
self.webview.load_html(output.decode("utf-8"), 'file://localhost/')
2014-07-06 20:35:24 +00:00
# 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)
self.scrolled_window.remove(self.text_editor)
self.scrolled_window.add(self.webview)
2014-07-06 20:35:24 +00:00
self.webview.show()
# This saying that all links will be opened in default browser, \
# but local files are opened in appropriate apps:
self.webview.connect("decide-policy", self.on_click_link)
2014-07-06 20:35:24 +00:00
else:
self.scrolled_window.remove(self.webview)
2014-07-06 20:35:24 +00:00
self.webview.destroy()
self.scrolled_window.add(self.text_editor)
self.text_editor.show()
2014-07-06 20:35:24 +00:00
self.queue_draw()
return True
2014-07-06 20:35:24 +00:00
def toggle_dark_mode(self, state):
"""Toggle the dark mode, both for the window and for the CSD
Arguments:
state {gtk bool} -- Desired state of the dark mode (enabled/disabled)
"""
2014-07-06 20:35:24 +00:00
# Save state for saving settings later
self.dark_mode = state
2014-07-06 20:35:24 +00:00
if self.dark_mode:
# Dark Mode is on
2018-04-15 00:33:02 +00:00
# self.gtk_settings.set_property('gtk-application-prefer-dark-theme', True)
# self.settings.set_value("dark-mode", GLib.Variant("b", True))
self.get_style_context().add_class("dark_mode")
2017-12-11 15:45:54 +00:00
self.hb_container.get_style_context().add_class("dark_mode")
self.markup_buffer.dark_mode(True)
2014-07-06 20:35:24 +00:00
else:
# Dark mode off
2018-04-15 00:33:02 +00:00
# self.gtk_settings.set_property('gtk-application-prefer-dark-theme', False)
# self.settings.set_value("dark-mode", GLib.Variant("b", False))
self.get_style_context().remove_class("dark_mode")
2017-12-11 15:45:54 +00:00
self.hb_container.get_style_context().remove_class("dark_mode")
self.markup_buffer.dark_mode(False)
2014-07-06 20:35:24 +00:00
# Redraw contents of window (self)
self.queue_draw()
def load_file(self, filename=None):
"""Open File from command line or open / open recent etc."""
if filename:
if filename.startswith('file://'):
filename = filename[7:]
filename = urllib.parse.unquote_plus(filename)
try:
if not os.path.exists(filename):
self.text_buffer.set_text("")
else:
current_file = codecs.open(filename, encoding="utf-8", mode='r')
self.text_buffer.set_text(current_file.read())
current_file.close()
self.markup_buffer.markup_buffer(0)
2014-10-04 22:18:17 +00:00
self.set_headerbar_title(
os.path.basename(filename) + self.title_end)
self.text_editor.undo_stack = []
self.text_editor.redo_stack = []
2018-05-06 18:00:14 +00:00
self.set_filename(filename)
2014-07-06 20:35:24 +00:00
except Exception:
LOGGER.warning("Error Reading File: %r" % Exception)
2014-07-06 20:35:24 +00:00
self.did_change = False
else:
LOGGER.warning("No File arg")
2014-07-06 20:35:24 +00:00
def open_uberwriter_markdown(self, _widget=None, _data=None):
"""open a markdown mini tutorial
"""
2014-07-06 20:35:24 +00:00
self.load_file(helpers.get_media_file('uberwriter_markdown.md'))
2018-06-23 19:34:53 +00:00
def open_search_and_replace(self):
"""toggle the search box
"""
2014-07-06 20:35:24 +00:00
self.searchreplace.toggle_search()
def open_advanced_export(self, _widget=None, _data=None):
"""open the export and advanced export dialog
"""
self.export = Export(self.filename)
self.export.dialog.set_transient_for(self)
2018-07-17 10:47:47 +00:00
response = self.export.dialog.run()
if response == 1:
self.export.export(bytes(self.get_text(), "utf-8"))
self.export.dialog.destroy()
def open_recent(self, _widget, data=None):
"""open the given recent document
"""
2018-07-17 10:47:47 +00:00
2014-07-06 20:35:24 +00:00
if data:
if self.check_change() == Gtk.ResponseType.CANCEL:
return
self.load_file(data)
2014-07-06 20:35:24 +00:00
2018-07-18 14:24:35 +00:00
def generate_recent_files_menu(self):
"""Generate a menu of recent opened .md files
Returns:
GtkMenu -- the menu of recent items
"""
2014-07-06 20:35:24 +00:00
# Recent file filter
self.recent_manager = Gtk.RecentManager.get_default()
self.recent_files_menu = Gtk.RecentChooserMenu.new_for_manager(
self.recent_manager)
2014-07-06 20:35:24 +00:00
self.recent_files_menu.set_sort_type(Gtk.RecentSortType.MRU)
recent_filter = Gtk.RecentFilter.new()
recent_filter.add_mime_type('text/x-markdown')
self.recent_files_menu.set_filter(recent_filter)
menu = Gtk.Menu.new()
for entry in self.recent_files_menu.get_items():
if entry.exists():
item = Gtk.MenuItem.new_with_label(entry.get_display_name())
item.connect('activate', self.open_recent, entry.get_uri())
menu.append(item)
item.show()
menu.show()
2018-07-18 14:24:35 +00:00
return menu
# menu.attach_to_widget(widget)
# parent_menu.show()
2014-07-06 20:35:24 +00:00
def poll_for_motion(self):
"""check if the user has moved the cursor to show the headerbar
Returns:
True -- Gtk things
"""
if (self.was_motion is False
2014-07-06 20:35:24 +00:00
and self.status_bar_visible
and self.buffer_modified_for_status_bar
and self.text_editor.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.hb_revealer.set_reveal_child(False)
2014-07-06 20:35:24 +00:00
self.status_bar_visible = False
self.buffer_modified_for_status_bar = False
self.was_motion = False
return True
def on_motion_notify(self, _widget, event, _data=None):
"""check the motion of the mouse to fade in the headerbar
"""
now = event.get_time()
if now - self.timestamp_last_mouse_motion > 150:
# filter out accidental motions
self.timestamp_last_mouse_motion = now
return
if now - self.timestamp_last_mouse_motion < 100:
# filter out accidental motion
return
if now - self.timestamp_last_mouse_motion > 100:
# react on motion by fading in headerbar and statusbar
if self.status_bar_visible is False:
self.statusbar_revealer.set_reveal_child(True)
self.hb_revealer.set_reveal_child(True)
self.hb.props.opacity = 1
self.status_bar_visible = True
self.buffer_modified_for_status_bar = False
self.update_line_and_char_count()
# self.status_bar.set_state_flags(Gtk.StateFlags.NORMAL, True)
self.was_motion = True
def show_fs_hb(self, _widget, _data=None):
"""show headerbar of the fullscreen mode
"""
self.fullscr_hb_revealer.set_reveal_child(True)
def hide_fs_hb(self, _widget, _data=None):
"""hide headerbar of the fullscreen mode
"""
2018-06-30 00:11:47 +00:00
if self.fs_btn_menu.get_active():
pass
else:
self.fullscr_hb_revealer.set_reveal_child(False)
def focus_out(self, _widget, _data=None):
"""events called when the window losses focus
"""
if self.status_bar_visible is False:
self.statusbar_revealer.set_reveal_child(True)
self.hb_revealer.set_reveal_child(True)
self.hb.props.opacity = 1
self.status_bar_visible = True
self.buffer_modified_for_status_bar = False
self.update_line_and_char_count()
def draw_gradient(self, _widget, cr):
"""draw fading gradient over the top and the bottom of the
TextWindow
"""
bg_color = self.get_style_context().get_background_color(Gtk.StateFlags.ACTIVE)
lg_top = cairo.LinearGradient(0, 0, 0, 35) #pylint: disable=no-member
lg_top.add_color_stop_rgba(
0, bg_color.red, bg_color.green, bg_color.blue, 1)
lg_top.add_color_stop_rgba(
1, bg_color.red, bg_color.green, bg_color.blue, 0)
width = self.scrolled_window.get_allocation().width
height = self.scrolled_window.get_allocation().height
cr.rectangle(0, 0, width, 35)
cr.set_source(lg_top)
cr.fill()
cr.rectangle(0, height - 35, width, height)
lg_btm = cairo.LinearGradient(0, height - 35, 0, height) # pylint: disable=no-member
lg_btm.add_color_stop_rgba(
1, bg_color.red, bg_color.green, bg_color.blue, 1)
lg_btm.add_color_stop_rgba(
0, bg_color.red, bg_color.green, bg_color.blue, 0)
cr.set_source(lg_btm)
cr.fill()
2014-07-06 20:35:24 +00:00
def use_experimental_features(self, _val):
"""use experimental features
"""
pass
# try:
# self.auto_correct = UberwriterAutoCorrect(
# self.text_editor, self.text_buffer)
# except:
# LOGGER.debug("Couldn't install autocorrect.")
2014-07-06 20:35:24 +00:00
def finish_initializing(self, builder): # pylint: disable=E1002
"""Set up the main window"""
2014-07-06 20:35:24 +00:00
super(UberwriterWindow, self).finish_initializing(builder)
2018-04-15 00:33:02 +00:00
# preferences
self.settings = Settings.new()
2014-07-06 20:35:24 +00:00
self.builder = builder
self.connect('save-file', self.save_document)
self.connect('save-file-as', self.save_document_as)
self.connect('new-file', self.new_document)
self.connect('open-file', self.open_document)
self.connect('close-window', self.on_mnu_close_activate)
self.scroll_adjusted = False
2014-07-06 20:35:24 +00:00
# Code for other initialization actions should be added here.
# Texlive checker
self.texlive_installed = False
self.set_name('UberwriterWindow')
2018-06-28 00:03:48 +00:00
# Headerbars
self.hb_container = Gtk.Frame(name='titlebar_container')
self.hb_container.set_shadow_type(Gtk.ShadowType.NONE)
self.hb_revealer = Gtk.Revealer(name='titlebar_revealer')
self.hb = Gtk.HeaderBar()
self.hb_revealer.add(self.hb)
self.hb_revealer.props.transition_duration = 1000
self.hb_revealer.props.transition_type = Gtk.RevealerTransitionType.CROSSFADE
self.hb.props.show_close_button = True
self.hb.get_style_context().add_class("titlebar")
self.hb_container.add(self.hb_revealer)
self.hb_container.show()
self.set_titlebar(self.hb_container)
self.hb_revealer.show()
self.hb_revealer.set_reveal_child(True)
self.hb.show()
2018-07-01 01:14:29 +00:00
btn_new = Gtk.Button().new_with_label(_("New"))
btn_open = Gtk.Button().new_with_label(_("Open"))
2018-07-18 14:24:35 +00:00
btn_recent = Gtk.MenuButton().new()
btn_recent.set_image(Gtk.Image.new_from_icon_name("go-down-symbolic",
Gtk.IconSize.BUTTON))
2018-07-01 01:14:29 +00:00
btn_recent.set_tooltip_text(_("Open Recent"))
2018-07-18 14:24:35 +00:00
btn_recent.set_popup(self.generate_recent_files_menu())
2018-07-01 01:14:29 +00:00
btn_save = Gtk.Button().new_with_label(_("Save"))
2018-06-30 00:11:47 +00:00
btn_search = Gtk.Button().new_from_icon_name("system-search-symbolic",
Gtk.IconSize.BUTTON)
2018-07-01 01:14:29 +00:00
btn_search.set_tooltip_text(_("Search and replace"))
2018-06-30 00:11:47 +00:00
btn_menu = Gtk.MenuButton().new()
2018-07-01 01:14:29 +00:00
btn_menu.set_tooltip_text(_("Menu"))
2018-06-30 00:11:47 +00:00
btn_menu.set_image(Gtk.Image.new_from_icon_name("open-menu-symbolic",
Gtk.IconSize.BUTTON))
btn_new.set_action_name("app.new")
btn_open.set_action_name("app.open")
btn_recent.set_action_name("app.open_recent")
btn_save.set_action_name("app.save")
btn_search.set_action_name("app.search")
btn_menu.set_use_popover(True)
self.builder_window_menu = get_builder('Menu')
self.model = self.builder_window_menu.get_object("Menu")
btn_menu.set_menu_model(self.model)
self.hb.pack_start(btn_new)
self.hb.pack_start(btn_open)
self.hb.pack_start(btn_recent)
self.hb.pack_end(btn_menu)
self.hb.pack_end(btn_search)
self.hb.pack_end(btn_save)
2018-06-28 00:03:48 +00:00
self.hb.show_all()
# same for fullscreen headerbar
2018-06-30 00:11:47 +00:00
# TODO: Refactorice: this is duplicated code!
2018-06-28 00:03:48 +00:00
self.fullscr_events = self.builder.get_object("FullscreenEventbox")
self.fullscr_hb_revealer = self.builder.get_object(
"FullscreenHbPlaceholder")
2018-06-28 00:03:48 +00:00
self.fullscr_hb = self.builder.get_object("FullscreenHeaderbar")
self.fullscr_hb.get_style_context().add_class("titlebar")
self.fullscr_hb_revealer.show()
self.fullscr_hb_revealer.set_reveal_child(False)
self.fullscr_hb.show()
self.fullscr_events.hide()
2018-07-01 01:14:29 +00:00
fs_btn_new = Gtk.Button().new_with_label(_("New"))
fs_btn_open = Gtk.Button().new_with_label(_("Open"))
2018-07-18 14:24:35 +00:00
self.fs_btn_recent = Gtk.MenuButton().new()
self.fs_btn_recent.set_tooltip_text(_("Open Recent"))
self.fs_btn_recent.set_image(Gtk.Image.new_from_icon_name("go-down-symbolic",
Gtk.IconSize.BUTTON))
2018-07-18 14:24:35 +00:00
self.fs_btn_recent.set_tooltip_text(_("Open Recent"))
self.fs_btn_recent.set_popup(self.generate_recent_files_menu())
2018-07-01 01:14:29 +00:00
fs_btn_save = Gtk.Button().new_with_label(_("Save"))
2018-06-30 00:11:47 +00:00
fs_btn_search = Gtk.Button().new_from_icon_name("system-search-symbolic",
Gtk.IconSize.BUTTON)
2018-07-01 01:14:29 +00:00
fs_btn_search.set_tooltip_text(_("Search and replace"))
2018-06-30 00:11:47 +00:00
self.fs_btn_menu = Gtk.MenuButton().new()
2018-07-01 01:14:29 +00:00
self.fs_btn_menu.set_tooltip_text(_("Menu"))
2018-06-30 00:11:47 +00:00
self.fs_btn_menu.set_image(Gtk.Image.new_from_icon_name("open-menu-symbolic",
Gtk.IconSize.BUTTON))
fs_btn_exit = Gtk.Button().new_from_icon_name("view-restore-symbolic",
2018-06-30 00:11:47 +00:00
Gtk.IconSize.BUTTON)
2018-07-01 01:14:29 +00:00
fs_btn_exit.set_tooltip_text(_("Exit Fullscreen"))
2018-06-30 00:11:47 +00:00
fs_btn_new.set_action_name("app.new")
fs_btn_open.set_action_name("app.open")
2018-07-18 14:24:35 +00:00
self.fs_btn_recent.set_action_name("app.open_recent")
2018-06-30 00:11:47 +00:00
fs_btn_save.set_action_name("app.save")
fs_btn_search.set_action_name("app.search")
fs_btn_exit.set_action_name("app.fullscreen")
self.fs_btn_menu.set_use_popover(True)
self.builder_window_menu = get_builder('Menu')
self.model = self.builder_window_menu.get_object("Menu")
self.fs_btn_menu.set_menu_model(self.model)
self.fullscr_hb.pack_start(fs_btn_new)
self.fullscr_hb.pack_start(fs_btn_open)
2018-07-18 14:24:35 +00:00
self.fullscr_hb.pack_start(self.fs_btn_recent)
2018-06-30 00:11:47 +00:00
self.fullscr_hb.pack_end(fs_btn_exit)
self.fullscr_hb.pack_end(self.fs_btn_menu)
self.fullscr_hb.pack_end(fs_btn_search)
self.fullscr_hb.pack_end(fs_btn_save)
2018-06-28 00:03:48 +00:00
self.fullscr_hb.show_all()
# this is a little tricky
# we show hb when the cursor enters an area of 1 px at the top of the window
# as the hb is shown the height of the eventbox grows to accomodate it
self.fullscr_events.connect('enter_notify_event', self.show_fs_hb)
self.fullscr_events.connect('leave_notify_event', self.hide_fs_hb)
2018-06-30 00:11:47 +00:00
self.fs_btn_menu.get_popover().connect('closed', self.hide_fs_hb)
2014-07-06 20:35:24 +00:00
self.title_end = " UberWriter"
2014-08-08 11:25:57 +00:00
self.set_headerbar_title("New File" + self.title_end)
2014-07-06 20:35:24 +00:00
self.focusmode = False
self.word_count = builder.get_object('word_count')
self.char_count = builder.get_object('char_count')
2014-07-06 20:35:24 +00:00
# Setup status bar hide after 3 seconds
self.status_bar = builder.get_object('status_bar_box')
self.statusbar_revealer = builder.get_object('status_bar_revealer')
2015-05-19 20:32:55 +00:00
self.status_bar.get_style_context().add_class('status_bar_box')
2014-07-06 20:35:24 +00:00
self.status_bar_visible = True
self.was_motion = True
self.buffer_modified_for_status_bar = False
self.connect("motion-notify-event", self.on_motion_notify)
GObject.timeout_add(3000, self.poll_for_motion)
self.accel_group = Gtk.AccelGroup()
self.add_accel_group(self.accel_group)
# Setup light background
self.text_editor = TextEditor()
self.text_editor.set_name('UberwriterEditor')
2015-05-19 20:32:55 +00:00
self.get_style_context().add_class('uberwriter_window')
2014-07-06 20:35:24 +00:00
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(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.TextEditor.install_style_property_parser
2014-07-06 20:35:24 +00:00
self.text_editor.show()
self.text_editor.grab_focus()
2014-07-06 20:35:24 +00:00
self.editor_alignment = builder.get_object('editor_alignment')
self.scrolled_window = builder.get_object('editor_scrolledwindow')
self.scrolled_window.props.width_request = 600
self.scrolled_window.add(self.text_editor)
self.alignment_padding = 40
self.editor_viewport = builder.get_object('editor_viewport')
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
2014-08-08 11:25:57 +00:00
self.preview_pane = builder.get_object('preview_scrolledwindow')
2014-07-06 20:35:24 +00:00
self.text_editor.set_top_margin(80)
self.text_editor.set_bottom_margin(16)
2014-07-06 20:35:24 +00:00
self.text_editor.set_pixels_above_lines(4)
self.text_editor.set_pixels_below_lines(4)
self.text_editor.set_pixels_inside_wrap(8)
2014-07-06 20:35:24 +00:00
tab_array = Pango.TabArray.new(1, True)
tab_array.set_tab(0, Pango.TabAlign.LEFT, 20)
self.text_editor.set_tabs(tab_array)
2014-07-06 20:35:24 +00:00
self.text_buffer = self.text_editor.get_buffer()
self.text_buffer.set_text('')
2014-07-06 20:35:24 +00:00
# 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)
2014-07-06 20:35:24 +00:00
# Init file name with None
2018-05-06 18:00:14 +00:00
self.set_filename()
2014-07-06 20:35:24 +00:00
self.style_provider = Gtk.CssProvider()
self.style_provider.load_from_path(helpers.get_media_path('style.css'))
2014-07-06 20:35:24 +00:00
Gtk.StyleContext.add_provider_for_screen(
Gdk.Screen.get_default(), self.style_provider,
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
)
# Markup and Shortcuts for the TextBuffer
self.markup_buffer = MarkupBuffer(
self, self.text_buffer, base_leftmargin)
self.markup_buffer.markup_buffer()
2014-07-06 20:35:24 +00:00
2018-04-18 13:00:06 +00:00
# Setup dark mode if so
if self.settings.get_value("dark-mode"):
self.toggle_dark_mode(True)
2018-04-18 13:00:06 +00:00
2014-07-06 20:35:24 +00:00
# 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)
2014-07-06 20:35:24 +00:00
# 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)
2014-07-06 20:35:24 +00:00
self.text_buffer.connect('paste-done', self.paste_done)
2014-07-06 20:35:24 +00:00
# self.connect('key-press-event', self.alt_mod)
# Events for Typewriter mode
# Setting up inline preview
self.inline_preview = UberwriterInlinePreview(
self.text_editor, self.text_buffer)
2014-07-06 20:35:24 +00:00
# Vertical scrolling
self.vadjustment = self.scrolled_window.get_vadjustment()
2014-07-06 20:35:24 +00:00
self.vadjustment.connect('value-changed', self.scrolled)
# Setting up spellcheck
self.auto_correct = None
2014-07-06 20:35:24 +00:00
try:
self.spell_checker = SpellChecker(
self.text_editor, locale.getdefaultlocale()[0],
2018-06-30 00:11:47 +00:00
collapse=False)
if self.auto_correct:
self.auto_correct.set_language(self.spell_checker.language)
self.spell_checker.connect_language_change( #pylint: disable=no-member
self.auto_correct.set_language)
2014-07-06 20:35:24 +00:00
self.spellcheck = True
except:
self.spell_checker = None
2014-07-06 20:35:24 +00:00
self.spellcheck = False
if self.spellcheck:
self.spell_checker.append_filter('[#*]+', SpellChecker.FILTER_WORD)
2014-07-06 20:35:24 +00:00
self.did_change = False
###
# Sidebar initialization test
###
self.paned_window = builder.get_object("main_pained")
self.sidebar_box = builder.get_object("sidebar_box")
self.sidebar = UberwriterSidebar(self)
2014-08-08 11:25:57 +00:00
self.sidebar_box.hide()
2014-07-06 20:35:24 +00:00
###
# Search and replace initialization
# Same interface as Sidebar ;)
###
self.searchreplace = UberwriterSearchAndReplace(self)
# Window resize
self.window_resize(self)
2014-07-06 20:35:24 +00:00
self.connect("configure-event", self.window_resize)
self.connect("delete-event", self.on_delete_called)
2017-12-07 06:56:39 +00:00
# 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
2014-07-06 20:35:24 +00:00
def on_delete_called(self, _widget, _data=None):
"""Called when the TexteditorWindow is closed.
"""
LOGGER.info('delete called')
2014-07-06 20:35:24 +00:00
if self.check_change() == Gtk.ResponseType.CANCEL:
return True
return False
def on_mnu_close_activate(self, _widget, _data=None):
"""Signal handler for closing the UberwriterWindow.
Overriden from parent Window Class
2014-07-06 20:35:24 +00:00
"""
if self.on_delete_called(self): # Really destroy?
return
self.destroy()
2014-07-06 20:35:24 +00:00
return
def on_destroy(self, _widget, _data=None):
"""Called when the TexteditorWindow is closed.
"""
2014-07-06 20:35:24 +00:00
# Clean up code for saving application state should be added here.
Gtk.main_quit()
2014-08-08 11:25:57 +00:00
def set_headerbar_title(self, title):
"""set the desired headerbar title
"""
2018-06-28 00:03:48 +00:00
self.hb.props.title = title
self.fullscr_hb.props.title = title
self.set_title(title)
2014-07-06 20:35:24 +00:00
2018-05-06 18:00:14 +00:00
def set_filename(self, filename=None):
"""set filename
"""
2018-05-06 18:00:14 +00:00
if filename:
self.filename = filename
base_path = os.path.dirname(self.filename)
else:
self.filename = None
base_path = "/"
self.settings.set_value("open-file-path", GLib.Variant("s", base_path))
def on_click_link(self, web_view, decision, _decision_type):
"""provide ability for self.webview to open links in default browser
"""
if web_view.get_uri().startswith(("http://", "https://", "www.")):
webbrowser.open(web_view.get_uri())
decision.ignore()
return True # Don't let the event "bubble up"
def open_translation(self):
"""open poeditor project web
"""
webbrowser.open("https://poeditor.com/join/project/gxVzFyXb2x")
def open_donation(self):
"""open donations web
"""
webbrowser.open("https://liberapay.com/UberWriter/donate")
def open_pandoc_markdown(self, _widget, _data=None):
"""open pandoc markdown web
"""
webbrowser.open(
"http://johnmacfarlane.net/pandoc/README.html#pandocs-markdown")