Exemple #1
0
    def __init__(self, app):
        """Set up the main window"""

        Gtk.ApplicationWindow.__init__(self,
                                       application=Gio.Application.get_default(),
                                       title="Uberwriter")

        self.builder = get_builder('UberwriterWindow')
        self.add(self.builder.get_object("FullscreenOverlay"))

        self.set_default_size(850, 500)

        # 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.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_visible = True
        self.was_motion = True
        self.buffer_modified_for_status_bar = False

        if self.settings.get_value("poll-motion"):
            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 text editor
        self.text_editor = TextEditor()
        self.text_editor.set_name('UberwriterEditor')
        self.get_style_context().add_class('uberwriter_window')

        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()

        # 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.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)

        # 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"))
        self.did_change = False

        ###
        #   Sidebar initialization test
        ###
        self.paned_window = self.builder.get_object("main_pained")
        self.sidebar_box = self.builder.get_object("sidebar_box")
        self.sidebar = Sidebar(self)
        self.sidebar_box.hide()

        ###
        #   Search and replace initialization
        #   Same interface as Sidebar ;)
        ###
        self.searchreplace = SearchAndReplace(self)

        # Window resize
        self.window_resize(self)
        self.connect("configure-event", self.window_resize)
        self.connect("delete-event", self.on_delete_called)
Exemple #2
0
    def __init__(self, app):
        """Set up the main window"""

        super().__init__(application=Gio.Application.get_default(),
                         title="Uberwriter")

        self.get_style_context().add_class('uberwriter-window')

        # Set UI
        builder = Gtk.Builder()
        builder.add_from_resource("/de/wolfvollprecht/UberWriter/ui/Window.ui")
        root = builder.get_object("FullscreenOverlay")
        self.connect("delete-event", self.on_delete_called)
        self.add(root)

        self.set_default_size(1000, 600)

        # Preferences
        self.settings = Settings.new()

        # Headerbars
        self.headerbar = headerbars.MainHeaderbar(app)
        self.set_titlebar(self.headerbar.hb_container)
        self.fs_headerbar = headerbars.FullscreenHeaderbar(builder, app)

        self.title_end = "  –  UberWriter"
        self.set_headerbar_title("New File" + self.title_end)

        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)

        self.accel_group = Gtk.AccelGroup()
        self.add_accel_group(self.accel_group)

        self.scrolled_window = builder.get_object('editor_scrolledwindow')

        # Setup text editor
        self.text_view = TextView(self.settings.get_int("characters-per-line"))
        self.text_view.connect('focus-out-event', self.focus_out)
        self.text_view.get_buffer().connect('changed', self.on_text_changed)
        self.text_view.show()
        self.text_view.grab_focus()
        self.scrolled_window.add(self.text_view)

        # Setup stats counter
        self.stats_revealer = builder.get_object('editor_stats_revealer')
        self.stats_button = builder.get_object('editor_stats_button')
        self.stats_handler = StatsHandler(self.stats_button, self.text_view)

        # Setup preview
        content = builder.get_object('content')
        editor = builder.get_object('editor')
        self.preview_handler = PreviewHandler(self, content, editor,
                                              self.text_view)

        # Setup header/stats bar hide after 3 seconds
        self.top_bottom_bars_visible = True
        self.was_motion = True
        self.buffer_modified_for_status_bar = False

        # some people seems to have performance problems with the overlay.
        # Let them disable it
        self.overlay_id = None
        self.toggle_gradient_overlay(
            self.settings.get_value("gradient-overlay"))

        # Init file name with None
        self.set_filename()

        # Setting up spellcheck
        self.auto_correct = None
        self.toggle_spellcheck(self.settings.get_value("spellcheck"))
        self.did_change = False

        ###
        #   Sidebar initialization test
        ###
        self.paned_window = builder.get_object("main_paned")
        self.sidebar_box = builder.get_object("sidebar_box")
        self.sidebar = Sidebar(self)
        self.sidebar_box.hide()

        ###
        #   Search and replace initialization
        #   Same interface as Sidebar ;)
        ###
        self.searchreplace = SearchAndReplace(self, self.text_view, builder)
Exemple #3
0
class Window(Gtk.ApplicationWindow):

    WORDCOUNT = re.compile(r"(?!\-\w)[\s#*\+\-]+", re.UNICODE)

    def __init__(self, app):
        """Set up the main window"""

        Gtk.ApplicationWindow.__init__(self,
                                       application=Gio.Application.get_default(),
                                       title="Uberwriter")

        self.builder = get_builder('UberwriterWindow')
        self.add(self.builder.get_object("FullscreenOverlay"))

        self.set_default_size(850, 500)

        # 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.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_visible = True
        self.was_motion = True
        self.buffer_modified_for_status_bar = False

        if self.settings.get_value("poll-motion"):
            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 text editor
        self.text_editor = TextEditor()
        self.text_editor.set_name('UberwriterEditor')
        self.get_style_context().add_class('uberwriter_window')

        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()

        # 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.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)

        # 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"))
        self.did_change = False

        ###
        #   Sidebar initialization test
        ###
        self.paned_window = self.builder.get_object("main_pained")
        self.sidebar_box = self.builder.get_object("sidebar_box")
        self.sidebar = Sidebar(self)
        self.sidebar_box.hide()

        ###
        #   Search and replace initialization
        #   Same interface as Sidebar ;)
        ###
        self.searchreplace = SearchAndReplace(self)

        # 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, ())
    }

    def apply_current_theme(self):
        """Adjusts both the window and the CSD for the current theme.
        """

        self.markup_buffer.update_style()

        # Reload preview if it exists, otherwise redraw contents of window (self)
        if self.preview_webview:
            self.show_preview()
        else:
            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()
        words = re.split(self.WORDCOUNT, text)
        length = len(words)
        # Last word a "space"
        if not words[-1]:
            length = length - 1
        # First word a "space" (happens in focus mode...)
        if not words[0]:
            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

    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
        """

        if self.did_change is False:
            self.did_change = True
            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
        the poller for motion in the top border

        Arguments:
            state {almost bool} -- The desired fullscreen state of the window
        """

        if state.get_boolean():
            self.fullscreen()
            self.fs_headerbar.events.show()

        else:
            self.unfullscreen()
            self.fs_headerbar.events.hide()

        self.text_editor.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)

    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.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):
        """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
        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")
            self.get_style_context().add_class("small")

        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
            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")
            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)

        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
            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):
        """provide to the user a filechooser and save the document
           where he wants. Call set_headbar_title after that
        """

        if self.filename:
            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.close()
            if self.did_change:
                self.did_change = False
                title = self.get_title()
                self.set_headerbar_title(title[2:])
            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)
        )

        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

            file_to_save = codecs.open(filename, encoding="utf-8", mode='w')
            file_to_save.write(self.get_text())
            file_to_save.close()

            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

    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
        """
        filechooser = Gtk.FileChooserDialog(
            "Save your File",
            self,
            Gtk.FileChooserAction.SAVE,
            ("_Cancel", Gtk.ResponseType.CANCEL,
             "_Save", Gtk.ResponseType.OK)
        )
        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:
                    self.recent_manager.remove_item("file:/" + filename)
                    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()

            self.set_filename(filename)
            self.set_headerbar_title(
                os.path.basename(filename) + self.title_end)

            try:
                self.recent_manager.add_item(filename)
            except:
                pass

            filechooser.destroy()
            self.did_change = False

        else:
            filechooser.destroy()
            return Gtk.ResponseType.CANCEL

    def copy_html_to_clipboard(self, _widget=None, _date=None):
        """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]

        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
        """

        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)
        )
        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():
            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(_("Cancel"), Gtk.ResponseType.CANCEL)
            dialog.add_button(_("Save now"), Gtk.ResponseType.YES)
            # dialog.set_default_size(200, 60)
            dialog.set_default_response(Gtk.ResponseType.YES)
            response = dialog.run()

            if response == Gtk.ResponseType.YES:
                if self.save_document() == Gtk.ResponseType.CANCEL:
                    dialog.destroy()
                    return self.check_change()

                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
        """

        if self.check_change() == Gtk.ResponseType.CANCEL:
            return
        self.text_buffer.set_text('')
        self.text_editor.undos = []
        self.text_editor.redos = []

        self.did_change = False
        self.set_filename()
        self.set_headerbar_title(_("New File") + self.title_end)

    def menu_toggle_sidebar(self, _widget=None):
        """WIP
        """
        self.sidebar.toggle_sidebar()

    def toggle_spellcheck(self, status):
        """Enable/disable the autospellchecking

        Arguments:
            status {gtk bool} -- Desired status of the spellchecking
        """

        if status.get_boolean():
            try:
                self.spell_checker.enable()
            except:
                try:
                    self.spell_checker = SpellChecker(
                      self.text_editor, locale.getdefaultlocale()[0],
                      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)
                except:
                    self.spell_checker = None
                    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()
                return
            return
        else:
            try:
                self.spell_checker.disable()
            except:
                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)

                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)

        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

    def toggle_preview(self, state):
        """Toggle the preview mode

        Arguments:
            state {gtk bool} -- Desired state of the preview mode (enabled/disabled)
        """

        if state.get_boolean():
            self.show_preview()
        else:
            self.show_text_editor()

        return True

    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.preview_webview.destroy()
        self.preview_webview = None
        self.queue_draw()

    def show_preview(self, loaded=False):
        if loaded:
            self.scrolled_window.remove(self.scrolled_window.get_child())
            self.scrolled_window.add(self.preview_webview)
            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',
                    '--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]

            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)

                # This saying that all links will be opened in default browser, \
                # 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/')

    def load_file(self, filename=None):
        """Open File from command line or open / open recent etc."""
        if self.check_change() == Gtk.ResponseType.CANCEL:
            return

        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)

                self.set_headerbar_title(
                    os.path.basename(filename) + self.title_end)
                self.text_editor.undo_stack = []
                self.text_editor.redo_stack = []
                self.set_filename(filename)

            except Exception:
                LOGGER.warning("Error Reading File: %r" % Exception)
            self.did_change = False
        else:
            LOGGER.warning("No File arg")

    def open_uberwriter_markdown(self, _widget=None, _data=None):
        """open a markdown mini tutorial
        """
        if self.check_change() == Gtk.ResponseType.CANCEL:
            return

        self.load_file(helpers.get_media_file('uberwriter_markdown.md'))

    def open_search_and_replace(self):
        """toggle the search box
        """

        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)

        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
        """
        print("open")

        if data:
            if self.check_change() == Gtk.ResponseType.CANCEL:
                return
            self.load_file(data)

    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
                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.headerbar.hb_revealer.set_reveal_child(False)
            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.headerbar.hb_revealer.set_reveal_child(True)
                self.headerbar.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 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.headerbar.hb_revealer.set_reveal_child(True)
            self.headerbar.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()

    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.
        """
        LOGGER.info('delete called')
        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
        """
        if self.on_delete_called(self):  # Really destroy?
            return
        self.destroy()
        return

    def on_destroy(self, _widget, _data=None):
        """Called when the TexteditorWindow is closed.
        """
        # Clean up code for saving application state should be added here.
        Gtk.main_quit()

    def set_headerbar_title(self, title):
        """set the desired headerbar title
        """
        self.headerbar.hb.props.title = title
        self.fs_headerbar.hb.props.title = title
        self.set_title(title)

    def set_filename(self, filename=None):
        """set filename
        """
        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_preview_load_change(self, webview, event):
        """swaps text editor with preview once the load is complete
        """
        if event == WebKit.LoadEvent.FINISHED:
            self.show_preview(loaded=True)

    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"
Exemple #4
0
class MainWindow(StyledWindow):
    __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, ())
    }

    def __init__(self, app):
        """Set up the main window"""

        super().__init__(application=Gio.Application.get_default(),
                         title="Uberwriter")

        self.get_style_context().add_class('uberwriter-window')

        # Set UI
        builder = Gtk.Builder()
        builder.add_from_resource("/de/wolfvollprecht/UberWriter/ui/Window.ui")
        root = builder.get_object("FullscreenOverlay")
        self.connect("delete-event", self.on_delete_called)
        self.add(root)

        self.set_default_size(1000, 600)

        # Preferences
        self.settings = Settings.new()

        # Headerbars
        self.headerbar = headerbars.MainHeaderbar(app)
        self.set_titlebar(self.headerbar.hb_container)
        self.fs_headerbar = headerbars.FullscreenHeaderbar(builder, app)

        self.title_end = "  –  UberWriter"
        self.set_headerbar_title("New File" + self.title_end)

        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)

        self.accel_group = Gtk.AccelGroup()
        self.add_accel_group(self.accel_group)

        self.scrolled_window = builder.get_object('editor_scrolledwindow')

        # Setup text editor
        self.text_view = TextView(self.settings.get_int("characters-per-line"))
        self.text_view.connect('focus-out-event', self.focus_out)
        self.text_view.get_buffer().connect('changed', self.on_text_changed)
        self.text_view.show()
        self.text_view.grab_focus()
        self.scrolled_window.add(self.text_view)

        # Setup stats counter
        self.stats_revealer = builder.get_object('editor_stats_revealer')
        self.stats_button = builder.get_object('editor_stats_button')
        self.stats_handler = StatsHandler(self.stats_button, self.text_view)

        # Setup preview
        content = builder.get_object('content')
        editor = builder.get_object('editor')
        self.preview_handler = PreviewHandler(self, content, editor,
                                              self.text_view)

        # Setup header/stats bar hide after 3 seconds
        self.top_bottom_bars_visible = True
        self.was_motion = True
        self.buffer_modified_for_status_bar = False

        # some people seems to have performance problems with the overlay.
        # Let them disable it
        self.overlay_id = None
        self.toggle_gradient_overlay(
            self.settings.get_value("gradient-overlay"))

        # Init file name with None
        self.set_filename()

        # Setting up spellcheck
        self.auto_correct = None
        self.toggle_spellcheck(self.settings.get_value("spellcheck"))
        self.did_change = False

        ###
        #   Sidebar initialization test
        ###
        self.paned_window = builder.get_object("main_paned")
        self.sidebar_box = builder.get_object("sidebar_box")
        self.sidebar = Sidebar(self)
        self.sidebar_box.hide()

        ###
        #   Search and replace initialization
        #   Same interface as Sidebar ;)
        ###
        self.searchreplace = SearchAndReplace(self, self.text_view, builder)

    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
        """

        if self.did_change is False:
            self.did_change = True
            title = self.get_title()
            self.set_headerbar_title("* " + title)

        self.buffer_modified_for_status_bar = True

    def set_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():
            self.fullscreen()
            self.fs_headerbar.events.show()
        else:
            self.unfullscreen()
            self.fs_headerbar.events.hide()
        self.text_view.grab_focus()

    def set_focus_mode(self, state):
        """toggle focusmode
        """

        self.text_view.set_focus_mode(state.get_boolean())
        self.text_view.grab_focus()

    def set_hemingway_mode(self, state):
        """toggle hemingwaymode
        """

        self.text_view.set_hemingway_mode(state.get_boolean())
        self.text_view.grab_focus()

    def toggle_preview(self, state):
        """Toggle the preview mode

        Arguments:
            state {gtk bool} -- Desired state of the preview mode (enabled/disabled)
        """

        if state.get_boolean():
            self.text_view.grab_focus()
            self.preview_handler.show()
        else:
            self.preview_handler.hide()
            self.text_view.grab_focus()

        return True

    # 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
        """

        if self.filename:
            LOGGER.info("saving")
            filename = self.filename
            file_to_save = codecs.open(filename, encoding="utf-8", mode='w')
            file_to_save.write(self.text_view.get_text())
            file_to_save.close()
            if self.did_change:
                self.did_change = False
                title = self.get_title()
                self.set_headerbar_title(title[2:])
            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))

        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

            file_to_save = codecs.open(filename, encoding="utf-8", mode='w')
            file_to_save.write(self.text_view.get_text())
            file_to_save.close()

            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

    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
        """
        filechooser = Gtk.FileChooserDialog(
            "Save your File", self, Gtk.FileChooserAction.SAVE,
            ("_Cancel", Gtk.ResponseType.CANCEL, "_Save", Gtk.ResponseType.OK))
        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:
                    self.recent_manager.remove_item("file:/" + filename)
                    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.text_view.get_text())
            file_to_save.close()

            self.set_filename(filename)
            self.set_headerbar_title(
                os.path.basename(filename) + self.title_end)

            try:
                self.recent_manager.add_item(filename)
            except:
                pass

            filechooser.destroy()
            self.did_change = False

        else:
            filechooser.destroy()
            return Gtk.ResponseType.CANCEL

    def copy_html_to_clipboard(self, _widget=None, _date=None):
        """Copies only html without headers etc. to Clipboard
        """

        output = helpers.pandoc_convert(self.text_view.get_text())
        clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
        clipboard.set_text(output, -1)
        clipboard.store()

    def open_document(self, _widget=None):
        """open the desired file
        """

        if self.check_change() == Gtk.ResponseType.CANCEL:
            return

        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"), self, Gtk.FileChooserAction.OPEN,
            ("_Cancel", Gtk.ResponseType.CANCEL, "_Open", Gtk.ResponseType.OK))
        filechooser.add_filter(markdown_filter)
        filechooser.add_filter(plaintext_filter)
        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.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(_("Cancel"), Gtk.ResponseType.CANCEL)
            dialog.add_button(_("Save now"), Gtk.ResponseType.YES)
            # dialog.set_default_size(200, 60)
            dialog.set_default_response(Gtk.ResponseType.YES)
            response = dialog.run()

            if response == Gtk.ResponseType.YES:
                if self.save_document() == Gtk.ResponseType.CANCEL:
                    dialog.destroy()
                    return self.check_change()

                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
        """

        if self.check_change() == Gtk.ResponseType.CANCEL:
            return
        self.text_view.clear()

        self.did_change = False
        self.set_filename()
        self.set_headerbar_title(_("New File") + self.title_end)

    def update_default_stat(self):
        self.stats_handler.update_default_stat()

    def update_preview_mode(self):
        self.preview_handler.update_preview_mode()

    def menu_toggle_sidebar(self, _widget=None):
        """WIP
        """
        self.sidebar.toggle_sidebar()

    def toggle_spellcheck(self, state):
        """Enable/disable the autospellchecking

        Arguments:
            status {gtk bool} -- Desired status of the spellchecking
        """

        self.text_view.set_spellcheck(state.get_boolean())

    def toggle_gradient_overlay(self, state):
        """Toggle the gradient overlay

        Arguments:
            state {gtk bool} -- Desired state of the gradient overlay (enabled/disabled)
        """

        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 reload_preview(self, reshow=False):
        self.preview_handler.reload(reshow=reshow)

    def load_file(self, filename=None):
        """Open File from command line or open / open recent etc."""
        if self.check_change() == Gtk.ResponseType.CANCEL:
            return

        if filename:
            if filename.startswith('file://'):
                filename = filename[7:]
            self.text_view.clear()
            try:
                if os.path.exists(filename):
                    with codecs.open(filename, encoding="utf-8",
                                     mode='r') as current_file:
                        self.text_view.set_text(current_file.read())
                else:
                    dialog = Gtk.MessageDialog(
                        self, Gtk.DialogFlags.MODAL
                        | Gtk.DialogFlags.DESTROY_WITH_PARENT,
                        Gtk.MessageType.WARNING, Gtk.ButtonsType.CLOSE,
                        _("The file you tried to open doesn't exist.\
                                            \nA new file will be created in its place when you save the current one."
                          ))
                    dialog.run()
                    dialog.destroy()

                self.set_headerbar_title(
                    os.path.basename(filename) + self.title_end)
                self.set_filename(filename)

            except Exception as e:
                LOGGER.warning(_("Error Reading File: %r") % e)
                dialog = Gtk.MessageDialog(
                    self, Gtk.DialogFlags.MODAL
                    | Gtk.DialogFlags.DESTROY_WITH_PARENT,
                    Gtk.MessageType.WARNING, Gtk.ButtonsType.CLOSE,
                    _("Error reading file:\
                                         \n%r" % e))
                dialog.run()
                dialog.destroy()
            self.did_change = False
        else:
            LOGGER.warning("No File arg")

    def open_uberwriter_markdown(self, _widget=None, _data=None):
        """open a markdown mini tutorial
        """
        if self.check_change() == Gtk.ResponseType.CANCEL:
            return

        self.load_file(helpers.get_media_file('uberwriter_markdown.md'))

    def open_search(self, replace=False):
        """toggle the search box
        """

        self.searchreplace.toggle_search(replace=replace)

    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)

        response = self.export.dialog.run()
        if response == 1:
            try:
                self.export.export(bytes(self.text_view.get_text(), "utf-8"))
            except Exception as e:
                dialog = Gtk.MessageDialog(
                    self, Gtk.DialogFlags.MODAL
                    | Gtk.DialogFlags.DESTROY_WITH_PARENT,
                    Gtk.MessageType.ERROR, Gtk.ButtonsType.CLOSE,
                    _("An error happened while trying to export:\n\n{err_msg}"
                      ).format(
                          err_msg=str(e).encode().decode("unicode-escape")))
                dialog.run()
                dialog.destroy()

        self.export.dialog.destroy()

    def open_recent(self, _widget, data=None):
        """open the given recent document
        """
        print("open")

        if data:
            if self.check_change() == Gtk.ResponseType.CANCEL:
                return
            self.load_file(data)

    def poll_for_motion(self):
        """check if the user has moved the cursor to show the headerbar

        Returns:
            True -- Gtk things
        """

        if (not self.was_motion and self.buffer_modified_for_status_bar
                and self.text_view.props.has_focus):
            self.reveal_top_bottom_bars(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
            self.reveal_top_bottom_bars(True)
            self.was_motion = True

    def focus_out(self, _widget, _data=None):
        """events called when the window losses focus
        """
        self.reveal_top_bottom_bars(True)

    def reveal_top_bottom_bars(self, reveal):
        if self.top_bottom_bars_visible != reveal:
            self.headerbar.hb_revealer.set_reveal_child(reveal)
            self.stats_revealer.set_reveal_child(reveal)
            for revealer in self.preview_handler.get_top_bottom_bar_revealers(
            ):
                revealer.set_reveal_child(reveal)
            self.top_bottom_bars_visible = reveal
            self.buffer_modified_for_status_bar = reveal

    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, 32)  # 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, 32)
        cr.set_source(lg_top)
        cr.fill()
        cr.rectangle(0, height - 32, width, height)

        lg_btm = cairo.LinearGradient(0, height - 32, 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()

    def on_delete_called(self, _widget, _data=None):
        """Called when the TexteditorWindow is closed.
        """
        LOGGER.info('delete called')
        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 Window.
           Overriden from parent Window Class
        """
        if self.on_delete_called(self):  # Really destroy?
            return
        self.destroy()
        return

    def set_headerbar_title(self, title):
        """set the desired headerbar title
        """
        self.headerbar.hb.props.title = title
        self.fs_headerbar.hb.props.title = title
        self.set_title(title)

    def set_filename(self, filename=None):
        """set filename
        """
        if filename:
            self.filename = filename
            base_path = os.path.dirname(self.filename)
        else:
            self.filename = None
            base_path = "/"
        self.settings.set_string("open-file-path", base_path)
Exemple #5
0
    def __init__(self, app):
        """Set up the main window"""

        super().__init__(application=Gio.Application.get_default(), title="Uberwriter")

        self.get_style_context().add_class('uberwriter-window')

        # Set UI
        builder = get_builder('Window')
        root = builder.get_object("FullscreenOverlay")
        self.connect("delete-event", self.on_delete_called)
        self.add(root)

        self.set_default_size(1000, 600)

        # Preferences
        self.settings = Settings.new()

        # Headerbars
        self.headerbar = headerbars.MainHeaderbar(app)
        self.set_titlebar(self.headerbar.hb_container)
        self.fs_headerbar = headerbars.FullscreenHeaderbar(builder, app)

        self.title_end = "  –  UberWriter"
        self.set_headerbar_title("New File" + self.title_end)

        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)

        self.accel_group = Gtk.AccelGroup()
        self.add_accel_group(self.accel_group)

        self.scrolled_window = builder.get_object('editor_scrolledwindow')

        # Setup text editor
        self.text_view = TextView(self.settings.get_int("characters-per-line"))
        self.text_view.connect('focus-out-event', self.focus_out)
        self.text_view.get_buffer().connect('changed', self.on_text_changed)
        self.text_view.show()
        self.text_view.grab_focus()
        self.scrolled_window.add(self.text_view)

        # Setup stats counter
        self.stats_revealer = builder.get_object('editor_stats_revealer')
        self.stats_button = builder.get_object('editor_stats_button')
        self.stats_handler = StatsHandler(self.stats_button, self.text_view)

        # Setup preview
        content = builder.get_object('content')
        editor = builder.get_object('editor')
        self.preview_handler = PreviewHandler(self, content, editor, self.text_view)

        # Setup header/stats bar hide after 3 seconds
        self.top_bottom_bars_visible = True
        self.was_motion = True
        self.buffer_modified_for_status_bar = False

        # some people seems to have performance problems with the overlay.
        # Let them disable it
        self.overlay_id = None
        self.toggle_gradient_overlay(self.settings.get_value("gradient-overlay"))

        # Init file name with None
        self.set_filename()

        # Setting up spellcheck
        self.auto_correct = None
        self.toggle_spellcheck(self.settings.get_value("spellcheck"))
        self.did_change = False

        ###
        #   Sidebar initialization test
        ###
        self.paned_window = builder.get_object("main_paned")
        self.sidebar_box = builder.get_object("sidebar_box")
        self.sidebar = Sidebar(self)
        self.sidebar_box.hide()

        ###
        #   Search and replace initialization
        #   Same interface as Sidebar ;)
        ###
        self.searchreplace = SearchAndReplace(self, self.text_view, builder)
Exemple #6
0
class MainWindow(StyledWindow):
    __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, ())
    }

    def __init__(self, app):
        """Set up the main window"""

        super().__init__(application=Gio.Application.get_default(), title="Uberwriter")

        self.get_style_context().add_class('uberwriter-window')

        # Set UI
        builder = get_builder('Window')
        root = builder.get_object("FullscreenOverlay")
        self.connect("delete-event", self.on_delete_called)
        self.add(root)

        self.set_default_size(1000, 600)

        # Preferences
        self.settings = Settings.new()

        # Headerbars
        self.headerbar = headerbars.MainHeaderbar(app)
        self.set_titlebar(self.headerbar.hb_container)
        self.fs_headerbar = headerbars.FullscreenHeaderbar(builder, app)

        self.title_end = "  –  UberWriter"
        self.set_headerbar_title("New File" + self.title_end)

        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)

        self.accel_group = Gtk.AccelGroup()
        self.add_accel_group(self.accel_group)

        self.scrolled_window = builder.get_object('editor_scrolledwindow')

        # Setup text editor
        self.text_view = TextView(self.settings.get_int("characters-per-line"))
        self.text_view.connect('focus-out-event', self.focus_out)
        self.text_view.get_buffer().connect('changed', self.on_text_changed)
        self.text_view.show()
        self.text_view.grab_focus()
        self.scrolled_window.add(self.text_view)

        # Setup stats counter
        self.stats_revealer = builder.get_object('editor_stats_revealer')
        self.stats_button = builder.get_object('editor_stats_button')
        self.stats_handler = StatsHandler(self.stats_button, self.text_view)

        # Setup preview
        content = builder.get_object('content')
        editor = builder.get_object('editor')
        self.preview_handler = PreviewHandler(self, content, editor, self.text_view)

        # Setup header/stats bar hide after 3 seconds
        self.top_bottom_bars_visible = True
        self.was_motion = True
        self.buffer_modified_for_status_bar = False

        # some people seems to have performance problems with the overlay.
        # Let them disable it
        self.overlay_id = None
        self.toggle_gradient_overlay(self.settings.get_value("gradient-overlay"))

        # Init file name with None
        self.set_filename()

        # Setting up spellcheck
        self.auto_correct = None
        self.toggle_spellcheck(self.settings.get_value("spellcheck"))
        self.did_change = False

        ###
        #   Sidebar initialization test
        ###
        self.paned_window = builder.get_object("main_paned")
        self.sidebar_box = builder.get_object("sidebar_box")
        self.sidebar = Sidebar(self)
        self.sidebar_box.hide()

        ###
        #   Search and replace initialization
        #   Same interface as Sidebar ;)
        ###
        self.searchreplace = SearchAndReplace(self, self.text_view, builder)

    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
        """

        if self.did_change is False:
            self.did_change = True
            title = self.get_title()
            self.set_headerbar_title("* " + title)

        self.buffer_modified_for_status_bar = True

    def set_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():
            self.fullscreen()
            self.fs_headerbar.events.show()
        else:
            self.unfullscreen()
            self.fs_headerbar.events.hide()
        self.text_view.grab_focus()

    def set_focus_mode(self, state):
        """toggle focusmode
        """

        self.text_view.set_focus_mode(state.get_boolean())
        self.text_view.grab_focus()

    def set_hemingway_mode(self, state):
        """toggle hemingwaymode
        """

        self.text_view.set_hemingway_mode(state.get_boolean())
        self.text_view.grab_focus()

    def toggle_preview(self, state):
        """Toggle the preview mode

        Arguments:
            state {gtk bool} -- Desired state of the preview mode (enabled/disabled)
        """

        if state.get_boolean():
            self.text_view.grab_focus()
            self.preview_handler.show()
        else:
            self.preview_handler.hide()
            self.text_view.grab_focus()

        return True

    # 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
        """

        if self.filename:
            LOGGER.info("saving")
            filename = self.filename
            file_to_save = codecs.open(filename, encoding="utf-8", mode='w')
            file_to_save.write(self.text_view.get_text())
            file_to_save.close()
            if self.did_change:
                self.did_change = False
                title = self.get_title()
                self.set_headerbar_title(title[2:])
            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)
        )

        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

            file_to_save = codecs.open(filename, encoding="utf-8", mode='w')
            file_to_save.write(self.text_view.get_text())
            file_to_save.close()

            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

    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
        """
        filechooser = Gtk.FileChooserDialog(
            "Save your File",
            self,
            Gtk.FileChooserAction.SAVE,
            ("_Cancel", Gtk.ResponseType.CANCEL,
             "_Save", Gtk.ResponseType.OK)
        )
        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:
                    self.recent_manager.remove_item("file:/" + filename)
                    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.text_view.get_text())
            file_to_save.close()

            self.set_filename(filename)
            self.set_headerbar_title(
                os.path.basename(filename) + self.title_end)

            try:
                self.recent_manager.add_item(filename)
            except:
                pass

            filechooser.destroy()
            self.did_change = False

        else:
            filechooser.destroy()
            return Gtk.ResponseType.CANCEL

    def copy_html_to_clipboard(self, _widget=None, _date=None):
        """Copies only html without headers etc. to Clipboard
        """

        output = helpers.pandoc_convert(self.text_view.get_text())
        clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
        clipboard.set_text(output, -1)
        clipboard.store()

    def open_document(self, _widget=None):
        """open the desired file
        """

        if self.check_change() == Gtk.ResponseType.CANCEL:
            return

        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"),
            self,
            Gtk.FileChooserAction.OPEN,
            ("_Cancel", Gtk.ResponseType.CANCEL,
             "_Open", Gtk.ResponseType.OK)
        )
        filechooser.add_filter(markdown_filter)
        filechooser.add_filter(plaintext_filter)
        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.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(_("Cancel"), Gtk.ResponseType.CANCEL)
            dialog.add_button(_("Save now"), Gtk.ResponseType.YES)
            # dialog.set_default_size(200, 60)
            dialog.set_default_response(Gtk.ResponseType.YES)
            response = dialog.run()

            if response == Gtk.ResponseType.YES:
                if self.save_document() == Gtk.ResponseType.CANCEL:
                    dialog.destroy()
                    return self.check_change()

                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
        """

        if self.check_change() == Gtk.ResponseType.CANCEL:
            return
        self.text_view.clear()

        self.did_change = False
        self.set_filename()
        self.set_headerbar_title(_("New File") + self.title_end)

    def update_default_stat(self):
        self.stats_handler.update_default_stat()

    def update_preview_mode(self):
        self.preview_handler.update_preview_mode()

    def menu_toggle_sidebar(self, _widget=None):
        """WIP
        """
        self.sidebar.toggle_sidebar()

    def toggle_spellcheck(self, state):
        """Enable/disable the autospellchecking

        Arguments:
            status {gtk bool} -- Desired status of the spellchecking
        """

        self.text_view.set_spellcheck(state.get_boolean())

    def toggle_gradient_overlay(self, state):
        """Toggle the gradient overlay

        Arguments:
            state {gtk bool} -- Desired state of the gradient overlay (enabled/disabled)
        """

        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 reload_preview(self, reshow=False):
        self.preview_handler.reload(reshow=reshow)

    def load_file(self, filename=None):
        """Open File from command line or open / open recent etc."""
        if self.check_change() == Gtk.ResponseType.CANCEL:
            return

        if filename:
            if filename.startswith('file://'):
                filename = filename[7:]
            filename = urllib.parse.unquote_plus(filename)
            self.text_view.clear()
            try:
                if os.path.exists(filename):
                    current_file = codecs.open(filename, encoding="utf-8", mode='r')
                    self.text_view.set_text(current_file.read())
                    current_file.close()

                self.set_headerbar_title(os.path.basename(filename) + self.title_end)
                self.set_filename(filename)

            except Exception:
                LOGGER.warning("Error Reading File: %r" % Exception)
            self.did_change = False
        else:
            LOGGER.warning("No File arg")

    def open_uberwriter_markdown(self, _widget=None, _data=None):
        """open a markdown mini tutorial
        """
        if self.check_change() == Gtk.ResponseType.CANCEL:
            return

        self.load_file(helpers.get_media_file('uberwriter_markdown.md'))

    def open_search_and_replace(self):
        """toggle the search box
        """

        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)

        response = self.export.dialog.run()
        if response == 1:
            self.export.export(bytes(self.text_view.get_text(), "utf-8"))

        self.export.dialog.destroy()

    def open_recent(self, _widget, data=None):
        """open the given recent document
        """
        print("open")

        if data:
            if self.check_change() == Gtk.ResponseType.CANCEL:
                return
            self.load_file(data)

    def poll_for_motion(self):
        """check if the user has moved the cursor to show the headerbar

        Returns:
            True -- Gtk things
        """

        if (not self.was_motion
                and self.buffer_modified_for_status_bar
                and self.text_view.props.has_focus):
            self.reveal_top_bottom_bars(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
            self.reveal_top_bottom_bars(True)
            self.was_motion = True

    def focus_out(self, _widget, _data=None):
        """events called when the window losses focus
        """
        self.reveal_top_bottom_bars(True)

    def reveal_top_bottom_bars(self, reveal):
        if self.top_bottom_bars_visible != reveal:
            self.headerbar.hb_revealer.set_reveal_child(reveal)
            self.stats_revealer.set_reveal_child(reveal)
            for revealer in self.preview_handler.get_top_bottom_bar_revealers():
                revealer.set_reveal_child(reveal)
            self.top_bottom_bars_visible = reveal
            self.buffer_modified_for_status_bar = reveal

    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, 32)  # 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, 32)
        cr.set_source(lg_top)
        cr.fill()
        cr.rectangle(0, height - 32, width, height)

        lg_btm = cairo.LinearGradient(0, height - 32, 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()

    def on_delete_called(self, _widget, _data=None):
        """Called when the TexteditorWindow is closed.
        """
        LOGGER.info('delete called')
        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 Window.
           Overriden from parent Window Class
        """
        if self.on_delete_called(self):  # Really destroy?
            return
        self.destroy()
        return

    def set_headerbar_title(self, title):
        """set the desired headerbar title
        """
        self.headerbar.hb.props.title = title
        self.fs_headerbar.hb.props.title = title
        self.set_title(title)

    def set_filename(self, filename=None):
        """set filename
        """
        if filename:
            self.filename = filename
            base_path = os.path.dirname(self.filename)
        else:
            self.filename = None
            base_path = "/"
        self.settings.set_string("open-file-path", base_path)
Exemple #7
0
    def __init__(self, app):
        """Set up the main window"""

        Gtk.ApplicationWindow.__init__(
            self,
            application=Gio.Application.get_default(),
            title="Uberwriter")

        # 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(900, 500)

        # Preferences
        self.settings = Settings.new()

        # Headerbars
        self.headerbar = headerbars.MainHeaderbar(app)
        self.set_titlebar(self.headerbar.hb_container)
        self.fs_headerbar = headerbars.FullscreenHeaderbar(self.builder, app)

        self.title_end = "  –  UberWriter"
        self.set_headerbar_title("New File" + self.title_end)

        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)

        self.accel_group = Gtk.AccelGroup()
        self.add_accel_group(self.accel_group)

        # Setup text editor
        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.get_buffer().connect('changed', self.on_text_changed)
        self.text_view.show()
        self.text_view.grab_focus()

        # Setup preview webview
        self.preview_webview = None

        self.scrolled_window = self.builder.get_object('editor_scrolledwindow')
        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')

        # Stats counter
        self.stats_counter_revealer = self.builder.get_object(
            'stats_counter_revealer')
        self.stats_button = self.builder.get_object('stats_counter')
        self.stats_button.get_style_context().add_class('stats-counter')
        self.stats_handler = StatsHandler(self.stats_button, self.text_view)

        # Setup header/stats bar hide after 3 seconds
        self.top_bottom_bars_visible = True
        self.was_motion = True
        self.buffer_modified_for_status_bar = False

        # some people seems to have performance problems with the overlay.
        # Let them disable it
        self.overlay_id = None
        self.toggle_gradient_overlay(
            self.settings.get_value("gradient-overlay"))

        # Init file name with None
        self.set_filename()

        # Setting up spellcheck
        self.auto_correct = None
        self.toggle_spellcheck(self.settings.get_value("spellcheck"))
        self.did_change = False

        ###
        #   Sidebar initialization test
        ###
        self.paned_window = self.builder.get_object("main_pained")
        self.sidebar_box = self.builder.get_object("sidebar_box")
        self.sidebar = Sidebar(self)
        self.sidebar_box.hide()

        ###
        #   Search and replace initialization
        #   Same interface as Sidebar ;)
        ###
        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)

        # Set current theme
        self.apply_current_theme()
        self.get_style_context().add_class('uberwriter-window')
Exemple #8
0
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, ())
    }

    def __init__(self, app):
        """Set up the main window"""

        Gtk.ApplicationWindow.__init__(
            self,
            application=Gio.Application.get_default(),
            title="Uberwriter")

        # 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(900, 500)

        # Preferences
        self.settings = Settings.new()

        # Headerbars
        self.headerbar = headerbars.MainHeaderbar(app)
        self.set_titlebar(self.headerbar.hb_container)
        self.fs_headerbar = headerbars.FullscreenHeaderbar(self.builder, app)

        self.title_end = "  –  UberWriter"
        self.set_headerbar_title("New File" + self.title_end)

        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)

        self.accel_group = Gtk.AccelGroup()
        self.add_accel_group(self.accel_group)

        # Setup text editor
        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.get_buffer().connect('changed', self.on_text_changed)
        self.text_view.show()
        self.text_view.grab_focus()

        # Setup preview webview
        self.preview_webview = None

        self.scrolled_window = self.builder.get_object('editor_scrolledwindow')
        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')

        # Stats counter
        self.stats_counter_revealer = self.builder.get_object(
            'stats_counter_revealer')
        self.stats_button = self.builder.get_object('stats_counter')
        self.stats_button.get_style_context().add_class('stats-counter')
        self.stats_handler = StatsHandler(self.stats_button, self.text_view)

        # Setup header/stats bar hide after 3 seconds
        self.top_bottom_bars_visible = True
        self.was_motion = True
        self.buffer_modified_for_status_bar = False

        # some people seems to have performance problems with the overlay.
        # Let them disable it
        self.overlay_id = None
        self.toggle_gradient_overlay(
            self.settings.get_value("gradient-overlay"))

        # Init file name with None
        self.set_filename()

        # Setting up spellcheck
        self.auto_correct = None
        self.toggle_spellcheck(self.settings.get_value("spellcheck"))
        self.did_change = False

        ###
        #   Sidebar initialization test
        ###
        self.paned_window = self.builder.get_object("main_pained")
        self.sidebar_box = self.builder.get_object("sidebar_box")
        self.sidebar = Sidebar(self)
        self.sidebar_box.hide()

        ###
        #   Search and replace initialization
        #   Same interface as Sidebar ;)
        ###
        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)

        # Set current theme
        self.apply_current_theme()
        self.get_style_context().add_class('uberwriter-window')

    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))

            # Set theme css
            style_provider = Gtk.CssProvider()
            style_provider.load_from_path(helpers.get_css_path("gtk/base.css"))
            Gtk.StyleContext.add_provider_for_screen(
                self.get_screen(), style_provider,
                Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)

            # Reload preview if it exists
            self.reload_preview()

            # Redraw contents of window
            self.queue_draw()

    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
        """

        if self.did_change is False:
            self.did_change = True
            title = self.get_title()
            self.set_headerbar_title("* " + title)

        self.buffer_modified_for_status_bar = True

    def set_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():
            self.fullscreen()
            self.fs_headerbar.events.show()

        else:
            self.unfullscreen()
            self.fs_headerbar.events.hide()

        self.text_view.grab_focus()

    def set_focus_mode(self, state):
        """toggle focusmode
        """

        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_view.set_hemingway_mode(state.get_boolean())
        self.text_view.grab_focus()

    def window_resize(self, window, event=None):
        """set paddings dependant of the window size
        """

        # Ensure the window receiving the event is the one we care about, ie. the main window.
        # On Wayland (bug?), sub-windows such as the recents popover will also trigger this.
        if event and event.window != window.get_window():
            return

        # 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:
            font_size = 14
            self.get_style_context().add_class("small")
            self.get_style_context().remove_class("large")

        elif w_width < 1280:
            font_size = 16
            self.get_style_context().remove_class("small")
            self.get_style_context().remove_class("large")

        else:
            font_size = 18
            self.get_style_context().remove_class("small")
            self.get_style_context().add_class("large")

        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

        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

    # 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
        """

        if self.filename:
            LOGGER.info("saving")
            filename = self.filename
            file_to_save = codecs.open(filename, encoding="utf-8", mode='w')
            file_to_save.write(self.text_view.get_text())
            file_to_save.close()
            if self.did_change:
                self.did_change = False
                title = self.get_title()
                self.set_headerbar_title(title[2:])
            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))

        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

            file_to_save = codecs.open(filename, encoding="utf-8", mode='w')
            file_to_save.write(self.text_view.get_text())
            file_to_save.close()

            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

    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
        """
        filechooser = Gtk.FileChooserDialog(
            "Save your File", self, Gtk.FileChooserAction.SAVE,
            ("_Cancel", Gtk.ResponseType.CANCEL, "_Save", Gtk.ResponseType.OK))
        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:
                    self.recent_manager.remove_item("file:/" + filename)
                    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.text_view.get_text())
            file_to_save.close()

            self.set_filename(filename)
            self.set_headerbar_title(
                os.path.basename(filename) + self.title_end)

            try:
                self.recent_manager.add_item(filename)
            except:
                pass

            filechooser.destroy()
            self.did_change = False

        else:
            filechooser.destroy()
            return Gtk.ResponseType.CANCEL

    def copy_html_to_clipboard(self, _widget=None, _date=None):
        """Copies only html without headers etc. to Clipboard
        """

        output = helpers.pandoc_convert(self.text_view.get_text())
        clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
        clipboard.set_text(output, -1)
        clipboard.store()

    def open_document(self, _widget=None):
        """open the desired file
        """

        if self.check_change() == Gtk.ResponseType.CANCEL:
            return

        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"), self, Gtk.FileChooserAction.OPEN,
            ("_Cancel", Gtk.ResponseType.CANCEL, "_Open", Gtk.ResponseType.OK))
        filechooser.add_filter(markdown_filter)
        filechooser.add_filter(plaintext_filter)
        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.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(_("Cancel"), Gtk.ResponseType.CANCEL)
            dialog.add_button(_("Save now"), Gtk.ResponseType.YES)
            # dialog.set_default_size(200, 60)
            dialog.set_default_response(Gtk.ResponseType.YES)
            response = dialog.run()

            if response == Gtk.ResponseType.YES:
                if self.save_document() == Gtk.ResponseType.CANCEL:
                    dialog.destroy()
                    return self.check_change()

                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
        """

        if self.check_change() == Gtk.ResponseType.CANCEL:
            return
        self.text_view.clear()

        self.did_change = False
        self.set_filename()
        self.set_headerbar_title(_("New File") + self.title_end)

    def update_default_stat(self):
        self.stats_handler.update_default_stat()

    def menu_toggle_sidebar(self, _widget=None):
        """WIP
        """
        self.sidebar.toggle_sidebar()

    def toggle_spellcheck(self, state):
        """Enable/disable the autospellchecking

        Arguments:
            status {gtk bool} -- Desired status of the spellchecking
        """

        if state.get_boolean():
            try:
                self.spell_checker.enable()
            except:
                try:
                    self.spell_checker = SpellChecker(
                        self.text_view,
                        locale.getdefaultlocale()[0],
                        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)
                except:
                    self.spell_checker = None
                    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' dictionaries"
                          + " for your language from the software center."))
                    _response = dialog.run()
                return
            return
        else:
            try:
                self.spell_checker.disable()
            except:
                pass
        return

    def toggle_gradient_overlay(self, state):
        """Toggle the gradient overlay

        Arguments:
            state {gtk bool} -- Desired state of the gradient overlay (enabled/disabled)
        """

        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

        Arguments:
            state {gtk bool} -- Desired state of the preview mode (enabled/disabled)
        """

        if state.get_boolean():
            self.show_preview()
        else:
            self.show_text_editor()

        return True

    def show_text_editor(self):
        self.scrolled_window.remove(self.scrolled_window.get_child())
        self.scrolled_window.add(self.text_view)
        self.text_view.show()
        self.preview_webview.destroy()
        self.preview_webview = None
        self.queue_draw()

    def show_preview(self, loaded=False):
        if loaded:
            self.scrolled_window.remove(self.scrolled_window.get_child())
            self.scrolled_window.add(self.preview_webview)
            self.preview_webview.show()
            self.queue_draw()
        else:
            args = [
                '--standalone', '--mathjax',
                '--css=' + Theme.get_current().web_css_path, '--lua-filter=' +
                helpers.get_script_path('relative_to_absolute.lua'),
                '--lua-filter=' + helpers.get_script_path('task-list.lua')
            ]
            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)

                # Show preview once the load is finished
                self.preview_webview.connect("load-changed",
                                             self.on_preview_load_change)

                # This saying that all links will be opened in default browser, \
                # but local files are opened in appropriate apps:
                self.preview_webview.connect("decide-policy",
                                             self.on_click_link)

            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."""
        if self.check_change() == Gtk.ResponseType.CANCEL:
            return

        if filename:
            if filename.startswith('file://'):
                filename = filename[7:]
            filename = urllib.parse.unquote_plus(filename)
            self.text_view.clear()
            try:
                if os.path.exists(filename):
                    current_file = codecs.open(filename,
                                               encoding="utf-8",
                                               mode='r')
                    self.text_view.set_text(current_file.read())
                    current_file.close()

                self.set_headerbar_title(
                    os.path.basename(filename) + self.title_end)
                self.set_filename(filename)

            except Exception:
                LOGGER.warning("Error Reading File: %r" % Exception)
            self.did_change = False
        else:
            LOGGER.warning("No File arg")

    def open_uberwriter_markdown(self, _widget=None, _data=None):
        """open a markdown mini tutorial
        """
        if self.check_change() == Gtk.ResponseType.CANCEL:
            return

        self.load_file(helpers.get_media_file('uberwriter_markdown.md'))

    def open_search_and_replace(self):
        """toggle the search box
        """

        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)

        response = self.export.dialog.run()
        if response == 1:
            self.export.export(bytes(self.text_view.get_text(), "utf-8"))

        self.export.dialog.destroy()

    def open_recent(self, _widget, data=None):
        """open the given recent document
        """
        print("open")

        if data:
            if self.check_change() == Gtk.ResponseType.CANCEL:
                return
            self.load_file(data)

    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 and self.top_bottom_bars_visible
                and self.buffer_modified_for_status_bar
                and self.text_view.props.has_focus):  # pylint: disable=no-member
            # self.status_bar.set_state_flags(Gtk.StateFlags.INSENSITIVE, True)
            self.stats_counter_revealer.set_reveal_child(False)
            self.headerbar.hb_revealer.set_reveal_child(False)
            self.top_bottom_bars_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.top_bottom_bars_visible is False:
                self.stats_counter_revealer.set_reveal_child(True)
                self.headerbar.hb_revealer.set_reveal_child(True)
                self.headerbar.hb.props.opacity = 1
                self.top_bottom_bars_visible = True
                self.buffer_modified_for_status_bar = False
                # self.status_bar.set_state_flags(Gtk.StateFlags.NORMAL, True)
            self.was_motion = True

    def focus_out(self, _widget, _data=None):
        """events called when the window losses focus
        """
        if self.top_bottom_bars_visible is False:
            self.stats_counter_revealer.set_reveal_child(True)
            self.headerbar.hb_revealer.set_reveal_child(True)
            self.headerbar.hb.props.opacity = 1
            self.top_bottom_bars_visible = True
            self.buffer_modified_for_status_bar = False

    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, 32)  # 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, 32)
        cr.set_source(lg_top)
        cr.fill()
        cr.rectangle(0, height - 32, width, height)

        lg_btm = cairo.LinearGradient(0, height - 32, 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()

    def on_delete_called(self, _widget, _data=None):
        """Called when the TexteditorWindow is closed.
        """
        LOGGER.info('delete called')
        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 Window.
           Overriden from parent Window Class
        """
        if self.on_delete_called(self):  # Really destroy?
            return
        self.destroy()
        return

    def set_headerbar_title(self, title):
        """set the desired headerbar title
        """
        self.headerbar.hb.props.title = title
        self.fs_headerbar.hb.props.title = title
        self.set_title(title)

    def set_filename(self, filename=None):
        """set filename
        """
        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_preview_load_change(self, webview, event):
        """swaps text editor with preview once the load is complete
        """
        if event == WebKit.LoadEvent.FINISHED:
            self.show_preview(loaded=True)

    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"