Beispiel #1
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="Apostrophe")

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

        # Set UI
        builder = Gtk.Builder()
        builder.add_from_resource(
            "/org/gnome/gitlab/somas/Apostrophe/ui/Window.ui")
        root = builder.get_object("AppOverlay")
        self.connect("delete-event", self.on_delete_called)
        self.add(root)

        self.set_default_size(1000, 600)

        # Preferences
        self.settings = Settings.new()

        # Headerbars
        self.last_height = 0
        self.headerbar = headerbars.MainHeaderbar(app)
        self.headerbar.hb_revealer.connect(
            "size_allocate", self.header_size_allocate)
        self.set_titlebar(self.headerbar.hb_revealer)

        # remove .titlebar class from hb_revealer 
        # to don't mess things up on Elementary OS 
        self.headerbar.hb_revealer.get_style_context().remove_class("titlebar")

        self.fs_headerbar = headerbars.FullscreenHeaderbar(builder, app)

        # Bind properties between normal and fs headerbar
        self.headerbar.light_button.bind_property(
            "active", self.fs_headerbar.light_button, "active",
            GObject.BindingFlags.BIDIRECTIONAL
            | GObject.BindingFlags.SYNC_CREATE)

        self.headerbar.dark_button.bind_property(
            "active", self.fs_headerbar.dark_button, "active",
            GObject.BindingFlags.BIDIRECTIONAL
            | GObject.BindingFlags.SYNC_CREATE)

        # The dummy headerbar is a cosmetic hack to be able to
        # crossfade the hb on top of the window
        self.dm_headerbar = headerbars.DummyHeaderbar(app)
        root.add_overlay(self.dm_headerbar.hb_revealer)
        root.reorder_overlay(self.dm_headerbar.hb_revealer, 0)
        root.set_overlay_pass_through(self.dm_headerbar.hb_revealer, True)

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

        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.set_top_margin(80)
        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.scrolled_window.add(self.text_view)
        self.text_view.grab_focus()

        # 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
        self.headerbar_visible = True
        self.bottombar_visible = True
        self.buffer_modified_for_status_bar = False

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

        # EventBoxes

        self.headerbar_eventbox = builder.get_object("HeaderbarEventbox")
        self.headerbar_eventbox.connect('enter_notify_event',
                                        self.reveal_headerbar_bottombar)

        self.stats_revealer.connect('enter_notify_event', self.reveal_bottombar)

    def header_size_allocate(self, widget, allocation):
        """ When the main hb starts to shrink its size, add that size
            to the textview margin, so it stays in place
        """

        # prevent 1px jumps
        if not widget.get_child_revealed():
            allocation.height = 0

        height = self.headerbar.hb.get_allocated_height() - allocation.height
        if height == self.last_height:
            return

        self.last_height = height

        self.text_view.update_vertical_margin(height)
        self.text_view.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
        if self.settings.get_value("autohide-headerbar"):
            self.hide_headerbar_bottombar()

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

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

        self.text_view.set_focus_mode(state.get_boolean(), self.headerbar.hb.get_allocated_height())
        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()
            self.headerbar.preview_toggle_revealer.set_reveal_child(True)
            self.fs_headerbar.preview_toggle_revealer.set_reveal_child(True)
            self.dm_headerbar.preview_toggle_revealer.set_reveal_child(True)
        else:
            self.preview_handler.hide()
            self.text_view.grab_focus()
            self.headerbar.preview_toggle_revealer.set_reveal_child(False)
            self.fs_headerbar.preview_toggle_revealer.set_reveal_child(False)
            self.dm_headerbar.preview_toggle_revealer.set_reveal_child(False)

        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 = io.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 = io.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, filename)

            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 = io.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, filename)

            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.filename:
            title = os.path.basename(self.filename)
        else:
            title = _("New File")

        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,
                                       _("Save changes to document “%s” before closing?") %
                                       title
                                       )
            
            dialog.props.secondary_text = _("If you don’t save, " +
                                            "all your changes will be permanently lost.")
            close_button = dialog.add_button(_("Close without saving"), Gtk.ResponseType.NO)
            dialog.add_button(_("Cancel"), Gtk.ResponseType.CANCEL)
            dialog.add_button(_("Save now"), Gtk.ResponseType.YES)

            close_button.get_style_context().add_class("destructive-action")
            # 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()
        self.headerbar.update_preview_layout_icon()
        self.headerbar.select_preview_layout_row()
        self.fs_headerbar.update_preview_layout_icon()
        self.fs_headerbar.select_preview_layout_row()

    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 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."""
        LOGGER.info("trying to open " + filename)
        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):
                    with io.open(filename, encoding="utf-8", mode='r') as current_file:
                        text = current_file.read()
                    self.text_view.set_text(text)
                    start_iter = self.text_view.get_buffer().get_start_iter()
                    GLib.idle_add(lambda: self.text_view.get_buffer().place_cursor(start_iter))
                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, filename)
                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_apostrophe_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('apostrophe_markdown.md'))

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

        self.searchreplace.toggle_search(replace=replace)

    def open_advanced_export(self, export_format):
        """open the export and advanced export dialog
        """
        text = bytes(self.text_view.get_text(), "utf-8")

        self.export = Export(self.filename, export_format, text)

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

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

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

    def reveal_headerbar_bottombar(self, _widget=None, _data=None):

        def __reveal_hb():
            self.headerbar_eventbox.hide()
            self.headerbar.hb_revealer.set_reveal_child(True)
            self.get_style_context().remove_class("focus")
            return False

        self.reveal_bottombar()

        if not self.headerbar_visible:
            self.dm_headerbar.hide_dm_hb()
            GLib.timeout_add(400, __reveal_hb)

            self.headerbar_visible = True

    def reveal_bottombar(self, _widget=None, _data=None):

        if not self.bottombar_visible:
            self.stats_revealer.set_reveal_child(True)

            self.bottombar_visible = True

        self.buffer_modified_for_status_bar = True

    def hide_headerbar_bottombar(self):

        if self.headerbar_visible:
            self.headerbar.hb_revealer.set_reveal_child(False)
            self.dm_headerbar.show_dm_hb()
            self.get_style_context().add_class("focus")

            self.headerbar_visible = False

        if self.bottombar_visible:
            self.stats_revealer.set_reveal_child(False)

            self.bottombar_visible = False

        self.headerbar_eventbox.show()
        self.buffer_modified_for_status_bar = False

    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, subtitle=""):
        """set the desired headerbar title
        """
        self.headerbar.hb.set_title(title)
        self.dm_headerbar.hb.set_title(title)
        self.fs_headerbar.hb.set_title(title)
        
        self.headerbar.hb.set_subtitle(subtitle)
        self.dm_headerbar.hb.set_subtitle(subtitle)
        self.fs_headerbar.hb.set_subtitle(subtitle)

        self.headerbar.hb.set_tooltip_text(subtitle)
        self.fs_headerbar.hb.set_tooltip_text(subtitle)
        
        self.set_title(title)

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

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

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

        # Set UI
        builder = Gtk.Builder()
        builder.add_from_resource(
            "/org/gnome/gitlab/somas/Apostrophe/ui/Window.ui")
        root = builder.get_object("AppOverlay")
        self.connect("delete-event", self.on_delete_called)
        self.add(root)

        self.set_default_size(1000, 600)

        # Preferences
        self.settings = Settings.new()

        # Create new, empty file
        # TODO: load last opened file?

        self.current = File()

        # Headerbars
        self.last_height = 0
        self.headerbar = headerbars.MainHeaderbar(app)
        self.headerbar.hb_revealer.connect("size_allocate",
                                           self.header_size_allocate)
        self.set_titlebar(self.headerbar.hb_revealer)

        # remove .titlebar class from hb_revealer
        # to don't mess things up on Elementary OS
        self.headerbar.hb_revealer.get_style_context().remove_class("titlebar")

        self.fs_headerbar = headerbars.FullscreenHeaderbar(builder, app)

        # Bind properties between normal and fs headerbar
        self.headerbar.light_button.bind_property(
            "active", self.fs_headerbar.light_button, "active",
            GObject.BindingFlags.BIDIRECTIONAL
            | GObject.BindingFlags.SYNC_CREATE)

        self.headerbar.dark_button.bind_property(
            "active", self.fs_headerbar.dark_button, "active",
            GObject.BindingFlags.BIDIRECTIONAL
            | GObject.BindingFlags.SYNC_CREATE)

        # The dummy headerbar is a cosmetic hack to be able to
        # crossfade the hb on top of the window
        self.dm_headerbar = headerbars.DummyHeaderbar(app)
        root.add_overlay(self.dm_headerbar.hb_revealer)
        root.reorder_overlay(self.dm_headerbar.hb_revealer, 0)
        root.set_overlay_pass_through(self.dm_headerbar.hb_revealer, True)

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

        scrolled_window = builder.get_object('editor_scrolledwindow')

        # Setup text editor
        self.text_view = TextView(self.settings.get_int("characters-per-line"))
        self.text_view.set_top_margin(80)
        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()
        scrolled_window.add(self.text_view)
        self.text_view.grab_focus()

        # Setup save progressbar an its animator

        self.progressbar = builder.get_object("save_progressbar")
        self.progressbar_initiate_tw = Tweener(self.progressbar,
                                               self.progressbar.set_fraction,
                                               0, 0.125, 40)
        self.progressbar_finalize_tw = Tweener(self.progressbar,
                                               self.progressbar.set_fraction,
                                               0.125, 1, 400)
        self.progressbar_opacity_tw = Tweener(self.progressbar,
                                              self.progressbar.set_opacity, 1,
                                              0, 300, 200)

        # 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
        self.headerbar_visible = True
        self.bottombar_visible = True
        self.buffer_modified_for_status_bar = False

        # Setting up spellcheck
        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
        ###
        self.searchreplace = SearchAndReplace(self, self.text_view, builder)

        # EventBoxes

        self.headerbar_eventbox = builder.get_object("HeaderbarEventbox")
        self.headerbar_eventbox.connect('enter_notify_event',
                                        self.reveal_headerbar_bottombar)

        self.stats_revealer.connect('enter_notify_event',
                                    self.reveal_bottombar)

        self.new_document()

    def header_size_allocate(self, widget, allocation):
        """ When the main hb starts to shrink its size, add that size
            to the textview margin, so it stays in place
        """

        # prevent 1px jumps
        if not widget.get_child_revealed():
            allocation.height = 0

        height = self.headerbar.hb.get_allocated_height() - allocation.height
        if height == self.last_height:
            return

        self.last_height = height

        self.text_view.update_vertical_margin(height)
        self.text_view.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

        self.update_headerbar_title(True, True)
        self.buffer_modified_for_status_bar = True
        if self.settings.get_value("autohide-headerbar"):
            self.hide_headerbar_bottombar()

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

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

        self.text_view.set_focus_mode(state.get_boolean(),
                                      self.headerbar.hb.get_allocated_height())
        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
        """

        if state.get_boolean():
            self.text_view.grab_focus()
            self.preview_handler.show()
            self.headerbar.preview_toggle_revealer.set_reveal_child(True)
            self.fs_headerbar.preview_toggle_revealer.set_reveal_child(True)
            self.dm_headerbar.preview_toggle_revealer.set_reveal_child(True)
        else:
            self.preview_handler.hide()
            self.text_view.grab_focus()
            self.headerbar.preview_toggle_revealer.set_reveal_child(False)
            self.fs_headerbar.preview_toggle_revealer.set_reveal_child(False)
            self.dm_headerbar.preview_toggle_revealer.set_reveal_child(False)

        return True

    def save_document(self, sync: bool = False) -> bool:
        """Try to save buffer in the current gfile.
        If the file doesn't exist calls save_document_as

        Args:
            sync (bool, optional): Wheter the save operation should be done
            synchronously. Defaults to False.

        Returns:
            bool: True if the document was saved correctly
        """

        if self.current.gfile:
            LOGGER.info("saving")

            # We try to encode the file with the given encoding
            # if that doesn't work, we try with UTF-8
            # if that fails as well, we return False
            try:
                try:
                    encoded_text = self.text_view.get_text()\
                        .encode(self.current.encoding)
                except UnicodeEncodeError:
                    encoded_text = self.text_view.get_text()\
                        .encode("UTF-8")
                    self.current.encoding = "UTF-8"
            except UnicodeEncodeError as error:
                helpers.show_error(self, str(error.reason))
                LOGGER.warning(str(error.reason))
                return False
            else:
                self.progressbar.set_opacity(1)
                self.progressbar_initiate_tw.start()

                # we allow synchronously saving operations
                # for result-dependant code
                if sync:
                    try:
                        res = self.current.gfile.replace_contents(
                            encoded_text,
                            etag=None,
                            make_backup=False,
                            flags=Gio.FileCreateFlags.NONE,
                            cancellable=None)
                    except GLib.GError as error:
                        helpers.show_error(self, str(error.message))
                        LOGGER.warning(str(error.message))
                        self.progressbar_opacity_tw.start()
                        self.did_change = True
                        return False

                    if res:
                        self.progressbar_initiate_tw.stop()
                        self.progressbar_finalize_tw.start()
                        self.progressbar_opacity_tw.start()

                        self.update_headerbar_title()
                        self.did_change = False
                        return True

                    else:
                        self.progressbar_opacity_tw.start()
                        self.did_change = True
                        return False

                else:
                    self.current.gfile.replace_contents_bytes_async(
                        GLib.Bytes.new(encoded_text),
                        etag=None,
                        make_backup=False,
                        flags=Gio.FileCreateFlags.NONE,
                        cancellable=None,
                        callback=self._replace_contents_cb,
                        user_data=None)
                    return True
        # if there's no GFile we ask for one:
        else:
            return self.save_document_as(sync=sync)

    def save_document_as(self,
                         _widget=None,
                         _data=None,
                         sync: bool = False) -> bool:
        """provide to the user a filechooser and save the document
           where they want. Call set_headbar_title after that
        """

        filefilter = Gtk.FileFilter.new()
        filefilter.add_mime_type('text/x-markdown')
        filefilter.add_mime_type('text/plain')
        filefilter.set_name('Markdown (.md)')
        filechooser = Gtk.FileChooserNative.new(_("Save your File"), self,
                                                Gtk.FileChooserAction.SAVE,
                                                _("Save"), _("Cancel"))
        filechooser.set_do_overwrite_confirmation(True)
        filechooser.set_local_only(False)
        filechooser.add_filter(filefilter)
        filechooser.set_modal(True)
        filechooser.set_transient_for(self)

        title = self.current.title
        if not title.endswith(".md"):
            title += ".md"
        filechooser.set_current_name(title)

        response = filechooser.run()

        if response == Gtk.ResponseType.ACCEPT:

            file = filechooser.get_file()

            if not file.query_exists():
                try:
                    file.create(Gio.FileCreateFlags.NONE)
                except GLib.GError as error:
                    helpers.show_error(self, str(error.message))
                    LOGGER.warning(str(error.message))
                    return False

            self.current.gfile = file

            self.update_headerbar_title(False, True)
            filechooser.destroy()
            return self.save_document()
        else:
            return False

    def _replace_contents_cb(self, gfile, result, _user_data=None):
        try:
            success, _etag = gfile.replace_contents_finish(result)
        except GLib.GError as error:
            helpers.show_error(self, str(error.message))
            LOGGER.warning(str(error.message))
            self.progressbar_opacity_tw.start()
            self.did_change = True
            return False

        if success:
            recents_manager = Gtk.RecentManager.get_default()
            recents_manager.add_item(self.current.gfile.get_uri())

            self.progressbar_initiate_tw.stop()
            self.progressbar_finalize_tw.start()
            self.progressbar_opacity_tw.start()

            self.update_headerbar_title()
            self.did_change = False
        else:
            self.did_change = True
            self.progressbar_opacity_tw.start()

        return success

    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.FileChooserNative.new(_("Open a .md file"), self,
                                                Gtk.FileChooserAction.OPEN,
                                                _("Open"), _("Cancel"))

        filechooser.set_local_only(False)
        filechooser.add_filter(markdown_filter)
        filechooser.add_filter(plaintext_filter)
        response = filechooser.run()
        if response == Gtk.ResponseType.ACCEPT:
            self.load_file(filechooser.get_file())
            filechooser.destroy()

        elif response == Gtk.ResponseType.CANCEL:
            filechooser.destroy()

    def load_file(self, file=None):
        """Open File from command line or open / open recent etc."""
        LOGGER.info("trying to open %s", file.get_path())

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

        self.current.gfile.load_contents_async(None, self._load_contents_cb,
                                               None)

    def _load_contents_cb(self, gfile, result, user_data=None):
        try:
            _success, contents, _etag = gfile.load_contents_finish(result)
        except GLib.GError as error:
            helpers.show_error(self, str(error.message))
            LOGGER.warning(str(error.message))
            return

        try:
            try:
                self.current.encoding = 'UTF-8'
                decoded = contents.decode(self.current.encoding)
            except UnicodeDecodeError:
                self.current.encoding = chardet.detect(contents)['encoding']
                decoded = contents.decode(self.current.encoding)
        except UnicodeDecodeError as error:
            helpers.show_error(self, str(error.message))
            LOGGER.warning(str(error.message))
            return
        else:
            self.text_view.set_text(decoded)
            start_iter = self.text_view.get_buffer().get_start_iter()
            GLib.idle_add(
                lambda: self.text_view.get_buffer().place_cursor(start_iter))

            self.update_headerbar_title()
            self.did_change = False

    def check_change(self) -> Gtk.ResponseType:
        """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,
                _("Save changes to document " + "“%s” before closing?") %
                self.current.title)

            dialog.props.secondary_text = _("If you don’t save, " +
                                            "all your changes will be " +
                                            "permanently lost.")
            close_button = dialog.add_button(_("Close without saving"),
                                             Gtk.ResponseType.NO)
            dialog.add_button(_("Cancel"), Gtk.ResponseType.CANCEL)
            dialog.add_button(_("Save now"), Gtk.ResponseType.YES)

            close_button.get_style_context().add_class("destructive-action")
            dialog.set_default_response(Gtk.ResponseType.YES)
            response = dialog.run()

            dialog.destroy()

            if response == Gtk.ResponseType.YES:
                # If the saving fails, retry
                if self.save_document(sync=True) is False:
                    return self.check_change()

            return response

    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.current.gfile = None
        self.update_headerbar_title(False, False)

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

    def update_preview_mode(self):
        self.preview_handler.update_preview_mode()
        self.headerbar.update_preview_layout_icon()
        self.headerbar.select_preview_layout_row()
        self.fs_headerbar.update_preview_layout_icon()
        self.fs_headerbar.select_preview_layout_row()

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

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

    def open_export(self, export_format):
        """open the export dialog
        """
        text = bytes(self.text_view.get_text(), "utf-8")

        export_dialog = ExportDialog(self.current, export_format, text)
        export_dialog.dialog.set_transient_for(self)
        export_dialog.export()

    def open_advanced_export(self):
        """open the advanced export dialog
        """
        text = bytes(self.text_view.get_text(), "utf-8")

        export_dialog = AdvancedExportDialog(self.current, text)
        export_dialog.set_transient_for(self)
        export_dialog.show()

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

    def reveal_headerbar_bottombar(self, _widget=None, _data=None):
        def __reveal_hb():
            self.headerbar_eventbox.hide()
            self.headerbar.hb_revealer.set_reveal_child(True)
            self.get_style_context().remove_class("focus")
            return False

        self.reveal_bottombar()

        if not self.headerbar_visible:
            self.dm_headerbar.hide_dm_hb()
            GLib.timeout_add(400, __reveal_hb)

            self.headerbar_visible = True

    def reveal_bottombar(self, _widget=None, _data=None):

        if not self.bottombar_visible:
            self.stats_revealer.set_reveal_child(True)
            self.stats_revealer.set_halign(Gtk.Align.END)
            self.stats_revealer.queue_resize()

            self.bottombar_visible = True

        self.buffer_modified_for_status_bar = True

    def hide_headerbar_bottombar(self):

        if self.headerbar_visible:
            self.headerbar.hb_revealer.set_reveal_child(False)
            self.dm_headerbar.show_dm_hb()
            self.get_style_context().add_class("focus")

            self.headerbar_visible = False

        if self.bottombar_visible:
            self.stats_revealer.set_reveal_child(False)
            self.stats_revealer.set_halign(Gtk.Align.FILL)

            self.bottombar_visible = False

        self.headerbar_eventbox.show()
        self.buffer_modified_for_status_bar = False

    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 update_headerbar_title(self,
                               is_unsaved: bool = False,
                               has_subtitle: bool = True):
        """update headerbar title and subtitle
        """

        if is_unsaved:
            prefix = "• "
            # TODO: this doesn't really belong here
            App().inhibitor.inhibit(Gtk.ApplicationInhibitFlags.LOGOUT)
        else:
            prefix = ""
            App().inhibitor.uninhibit()

        title = prefix + self.current.title

        if has_subtitle:
            subtitle = self.current.path
        else:
            subtitle = ""

        self.headerbar.hb.set_title(title)
        self.dm_headerbar.hb.set_title(title)
        self.fs_headerbar.hb.set_title(title)

        self.headerbar.hb.set_subtitle(subtitle)
        self.dm_headerbar.hb.set_subtitle(subtitle)
        self.fs_headerbar.hb.set_subtitle(subtitle)

        self.headerbar.hb.set_tooltip_text(subtitle)
        self.fs_headerbar.hb.set_tooltip_text(subtitle)

        self.set_title(title)