def toggle_spellcheck(self, widget, data=None): if self.spellcheck: if widget.get_active(): self.SpellChecker.enable() else: self.SpellChecker.disable() elif widget.get_active(): try: self.SpellChecker = SpellChecker(self.TextEditor, locale.getdefaultlocale()[0], collapse=False) self.spellcheck = True except: self.SpellChecker = None self.spellcheck = False dialog = Gtk.MessageDialog( self, Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.INFO, 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
def __init__(self, liststore, entry=None, source=None, account=None): self.entry = entry self.child = None # for GtkGrid.get_child_at no available gui = Gtk.Builder() gui.add_from_file(SHARED_DATA_FILE('update.glade')) self.media = MediaFile(gui) self.config = AuthorizedTwitterAccount.CONFIG host_re = '//[A-Za-z0-9\'~+\-=_.,/%\?!;:@#\*&\(\)]+' self.http_re = re.compile("(http:%s)" % host_re) self.https_re = re.compile("(https:%s)" % host_re) self.screen_name_pattern = re.compile('\B@[0-9A-Za-z_]{1,15}') self.account_combobox = AccountCombobox(gui, liststore, source, account) is_above = SETTINGS.get_boolean('update-window-keep-above') self.update_window = gui.get_object('window1') self.update_window.set_keep_above(is_above) self.button_image = gui.get_object('button_image') self.label_num = gui.get_object('label_num') self.comboboxtext_privacy = FacebookPrivacyCombobox(gui) self.grid_button = gui.get_object('grid_button') self.on_combobox_account_changed() self.button_tweet = gui.get_object('button_tweet') self.text_buffer = gui.get_object('textbuffer') self.on_textbuffer_changed(self.text_buffer) textview = gui.get_object('textview') if SpellChecker: self.spellchecker = SpellChecker(textview) if not SETTINGS.get_boolean('spell-checker'): self.spellchecker.disable() gui.connect_signals(self) if entry: widget = 'buttonbox1' if entry.get('protected') else 'image_secret' gui.get_object(widget).hide() self._download_user_icon_with_callback(gui, entry) else: gui.get_object('grid_entry').destroy() self.update_window.present()
def create_textview(self): self.vbox_left1.add(self.text_grid) scrolledwindow = Gtk.ScrolledWindow() scrolledwindow.set_hexpand(True) scrolledwindow.set_vexpand(True) self.text_grid.attach(scrolledwindow, 0, 1, 3, 1) self.textview = Gtk.TextView() spellchecker = SpellChecker(self.textview, locale.getdefaultlocale()[0]) self.textbuffer = self.textview.get_buffer() scrolledwindow.add(self.textview) self.tag_bold = self.textbuffer.create_tag("bold", weight=Pango.Weight.BOLD) self.tag_italic = self.textbuffer.create_tag("italic", style=Pango.Style.ITALIC) self.tag_underline = self.textbuffer.create_tag( "underline", underline=Pango.Underline.SINGLE) self.tag_found = self.textbuffer.create_tag("found", background="yellow") self.textview.connect("key_release_event", self.on_key_release)
def activate (self, recEditor): UIPlugin.activate(self,recEditor) for module in self.pluggable.modules: tvs = harvest_textviews(module.main) for tv in tvs: # gtkspell.Spell(tv) SpellChecker(tv)
def toggle_spellchecker(self, value): if self._spellchecker_loaded: if value: if self._ssb_script_spellcheck: self._ssb_script_spellcheck.enable() if self._explorerscript_spellcheck: self._explorerscript_spellcheck.enable() else: if self._ssb_script_spellcheck: self._ssb_script_spellcheck.disable() if self._explorerscript_spellcheck: self._explorerscript_spellcheck.disable() elif value: self._spellchecker_loaded = True if self._ssb_script_view: self._ssb_script_spellcheck = SpellChecker( self._ssb_script_view, 'en_US') if self._explorerscript_view: self._explorerscript_spellcheck = SpellChecker( self._explorerscript_view, 'en_US')
def __init__(self, liststore, entry=None, source=None, account=None): self.entry = entry self.child = None # for GtkGrid.get_child_at no available gui = Gtk.Builder() gui.add_from_file(SHARED_DATA_FILE('update.glade')) self.media = MediaFile(gui) self.config = AuthorizedTwitterAccount.CONFIG host_re = '//[A-Za-z0-9\'~+\-=_.,/%\?!;:@#\*&\(\)]+' self.http_re = re.compile("(http:%s)" % host_re) self.https_re = re.compile("(https:%s)" % host_re) self.screen_name_pattern = re.compile('\B@[0-9A-Za-z_]{1,15}') self.account_combobox = AccountCombobox( gui, liststore, source, account) is_above = SETTINGS.get_boolean('update-window-keep-above') self.update_window = gui.get_object('window1') self.update_window.set_keep_above(is_above) self.button_image = gui.get_object('button_image') self.label_num = gui.get_object('label_num') self.comboboxtext_privacy = FacebookPrivacyCombobox(gui) self.grid_button = gui.get_object('grid_button') self.on_combobox_account_changed() self.button_tweet = gui.get_object('button_tweet') self.text_buffer = gui.get_object('textbuffer') self.on_textbuffer_changed(self.text_buffer) textview = gui.get_object('textview') if SpellChecker: self.spellchecker = SpellChecker(textview) if not SETTINGS.get_boolean('spell-checker'): self.spellchecker.disable() gui.connect_signals(self) if entry: widget = 'buttonbox1' if entry.get('protected') else 'image_secret' gui.get_object(widget).hide() self._download_user_icon_with_callback(gui, entry) else: gui.get_object('grid_entry').destroy() self.update_window.present()
def toggle_spellcheck(self, widget, data=None): if self.spellcheck: if widget.get_active(): self.SpellChecker.enable() else: self.SpellChecker.disable() elif widget.get_active(): try: self.SpellChecker = SpellChecker(self.TextEditor, locale.getdefaultlocale()[0], collapse=False) self.spellcheck = True except: self.SpellChecker = None self.spellcheck = False dialog = Gtk.MessageDialog(self, Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.INFO, 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
def finish_initializing(self, builder): # pylint: disable=E1002 """Set up the main window""" super(UberwriterWindow, self).finish_initializing(builder) self.AboutDialog = AboutUberwriterDialog self.UberwriterAdvancedExportDialog = UberwriterAdvancedExportDialog self.builder = builder self.connect('save-file', self.save_document) self.connect('save-file-as', self.save_document_as) self.connect('new-file', self.new_document) self.connect('open-file', self.open_document) self.connect('toggle-fullscreen', self.menu_activate_fullscreen) self.connect('toggle-focusmode', self.menu_activate_focusmode) self.connect('toggle-preview', self.menu_activate_preview) self.connect('toggle-spellcheck', self.toggle_spellcheck) self.connect('close-window', self.on_mnu_close_activate) self.connect('toggle-search', self.open_search_and_replace) self.scroll_adjusted = False # Code for other initialization actions should be added here. # Texlive checker self.texlive_installed = False self.set_name('UberwriterWindow') self.use_headerbar = True if self.use_headerbar == True: self.hb_revealer = Gtk.Revealer() self.hb = Gtk.HeaderBar() self.hb_revealer.add(self.hb) self.hb_revealer.props.transition_duration = 1000 self.hb_revealer.props.transition_type = Gtk.RevealerTransitionType.CROSSFADE self.hb.props.show_close_button = True self.hb.get_style_context().add_class("titlebar") self.set_titlebar(self.hb_revealer) self.hb_revealer.show() self.hb_revealer.set_reveal_child(True) self.hb.show() bbtn = Gtk.MenuButton() btn_settings = Gtk.MenuButton() btn_settings.props.image = Gtk.Image.new_from_icon_name('emblem-system-symbolic', Gtk.IconSize.BUTTON) btn_settings.set_popup(self.builder.get_object("menu4")) # icon = Gio.ThemedIcon(name="mail-sendm receive-symbolic") # image = Gtk.Image.new_from_gicon(icon, Gtk.IconSize.BUTTON) # bbtn.add(image) bbtn.set_popup(self.builder.get_object("menu1")) self.hb.pack_start(bbtn) self.hb.pack_end(btn_settings) self.hb.show_all() self.testbits = Gdk.WindowState.TILED | Gdk.WindowState.MAXIMIZED self.connect('draw', self.override_headerbar_background) self.title_end = " – UberWriter" self.set_headerbar_title("New File" + self.title_end) self.focusmode = False self.word_count = builder.get_object('word_count') self.char_count = builder.get_object('char_count') self.menubar = builder.get_object('menubar1') self.menubar.hide() # Wire up buttons self.fullscreen_button = builder.get_object('fullscreen_toggle') self.focusmode_button = builder.get_object('focus_toggle') self.preview_button = builder.get_object('preview_toggle') self.fullscreen_button.set_name('fullscreen_toggle') self.focusmode_button.set_name('focus_toggle') self.preview_button.set_name('preview_toggle') # Setup status bar hide after 3 seconds self.status_bar = builder.get_object('status_bar_box') self.statusbar_revealer = builder.get_object('status_bar_revealer') self.status_bar.set_name('status_bar_box') self.status_bar_visible = True self.was_motion = True self.buffer_modified_for_status_bar = False self.connect("motion-notify-event", self.on_motion_notify) GObject.timeout_add(3000, self.poll_for_motion) self.accel_group = Gtk.AccelGroup() self.add_accel_group(self.accel_group) # Setup light background self.TextEditor = TextEditor() self.TextEditor.set_name('UberwriterEditor') base_leftmargin = 40 # self.TextEditor.set_left_margin(base_leftmargin) self.TextEditor.set_left_margin(40) self.TextEditor.props.width_request = 600 self.TextEditor.props.halign = Gtk.Align.CENTER self.TextEditor.set_vadjustment(builder.get_object('vadjustment1')) self.TextEditor.set_wrap_mode(Gtk.WrapMode.WORD) self.TextEditor.connect('focus-out-event', self.focus_out) self.TextEditor.get_style_context().connect('changed', self.style_changed) # self.TextEditor.install_style_property_parser self.TextEditor.show() self.TextEditor.grab_focus() self.ScrolledWindow = builder.get_object('editor_scrolledwindow') self.EditorAlignment = builder.get_object('editor_alignment') self.EditorAlignment.add(self.TextEditor) self.alignment_padding = 40 self.EditorViewport = builder.get_object('editor_viewport') self.EditorViewport.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.PreviewPane = builder.get_object('preview_scrolledwindow') self.TextEditor.set_margin_top(38) self.TextEditor.set_margin_bottom(16) self.TextEditor.set_pixels_above_lines(4) self.TextEditor.set_pixels_below_lines(4) self.TextEditor.set_pixels_inside_wrap(8) tab_array = Pango.TabArray.new(1, True) tab_array.set_tab(0, Pango.TabAlign.LEFT, 20) self.TextEditor.set_tabs(tab_array) self.TextBuffer = self.TextEditor.get_buffer() self.TextBuffer.set_text('') # Init Window height for top/bottom padding self.window_height = self.get_size()[1] self.text_change_event = self.TextBuffer.connect('changed', self.text_changed) # Init file name with None self.filename = None self.generate_recent_files_menu(self.builder.get_object('recent')) self.style_provider = Gtk.CssProvider() self.style_provider.load_from_path(helpers.get_media_path('style.css')) Gtk.StyleContext.add_provider_for_screen( Gdk.Screen.get_default(), self.style_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION ) # Markup and Shortcuts for the TextBuffer self.MarkupBuffer = MarkupBuffer(self, self.TextBuffer, base_leftmargin) self.MarkupBuffer.markup_buffer() # Scrolling -> Dark or not? self.textchange = False self.scroll_count = 0 self.timestamp_last_mouse_motion = 0 self.TextBuffer.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.TextEditor.drag_dest_set_target_list(self.target_list) self.TextEditor.connect_after('drag-data-received', self.on_drag_data_received) def on_drop(widget, *args): print("drop") self.TextEditor.connect('drag-drop', on_drop) self.TextBuffer.connect('paste-done', self.paste_done) # self.connect('key-press-event', self.alt_mod) # Events for Typewriter mode # Setting up inline preview self.InlinePreview = UberwriterInlinePreview(self.TextEditor, self.TextBuffer) # Vertical scrolling self.vadjustment = self.ScrolledWindow.get_vadjustment() self.vadjustment.connect('value-changed', self.scrolled) # Setting up spellcheck try: self.SpellChecker = SpellChecker(self.TextEditor, locale.getdefaultlocale()[0], collapse=False) self.spellcheck = True except: self.SpellChecker = None self.spellcheck = False builder.get_object("disable_spellcheck").set_active(False) if self.spellcheck: self.SpellChecker.append_filter('[#*]+', SpellChecker.FILTER_WORD) self.did_change = False ### # Sidebar initialization test ### self.paned_window = builder.get_object("main_pained") self.sidebar_box = builder.get_object("sidebar_box") self.sidebar = UberwriterSidebar(self) self.sidebar_box.hide() ### # Search and replace initialization # Same interface as Sidebar ;) ### self.searchreplace = UberwriterSearchAndReplace(self) # Window resize self.window_resize(self) self.connect("configure-event", self.window_resize) self.connect("delete-event", self.on_delete_called) self.gtk_settings = Gtk.Settings.get_default() self.load_settings(builder) self.connect_after('realize', self.color_window)
class UberwriterWindow(Window): __gtype_name__ = "UberwriterWindow" __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-focusmode': (GObject.SIGNAL_ACTION, None, ()), 'toggle-fullscreen': (GObject.SIGNAL_ACTION, None, ()), 'toggle-spellcheck': (GObject.SIGNAL_ACTION, None, ()), 'toggle-preview': (GObject.SIGNAL_ACTION, None, ()), 'toggle-search': (GObject.SIGNAL_ACTION, None, ()), 'toggle-search-replace': (GObject.SIGNAL_ACTION, None, ()), 'close-window': (GObject.SIGNAL_ACTION, None, ()) } 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, *args): self.MarkupBuffer.markup_buffer(0) def init_typewriter(self): self.EditorAlignment.props.top_padding = self.window_height / 2 self.EditorAlignment.props.bottom_padding = self.window_height / 2 self.typewriter_initiated = True def remove_typewriter(self): self.EditorAlignment.props.top_padding = self.alignment_padding self.EditorAlignment.props.bottom_padding = self.alignment_padding self.text_change_event = self.TextBuffer.connect('changed', self.text_changed) def get_text(self): start_iter = self.TextBuffer.get_start_iter() end_iter = self.TextBuffer.get_end_iter() return self.TextBuffer.get_text(start_iter, end_iter, False) WORDCOUNT = re.compile(r"(?!\-\w)[\s#*\+\-]+", re.UNICODE) def update_line_and_char_count(self): if self.status_bar_visible == False: return self.char_count.set_text(str(self.TextBuffer.get_char_count())) text = self.get_text() words = re.split(self.WORDCOUNT, text) length = len(words) # Last word a "space" if len(words[-1]) == 0: length = length - 1 # First word a "space" (happens in focus mode...) if len(words[0]) == 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, widget, data=None): if self.did_change == False: self.did_change = True title = self.get_title() self.set_headerbar_title("* " + title) self.MarkupBuffer.markup_buffer(1) self.textchange = True self.buffer_modified_for_status_bar = True self.update_line_and_char_count() self.check_scroll(self.TextBuffer.get_insert()) def toggle_fullscreen(self, widget, data=None): if widget.get_active(): self.fullscreen() key, mod = Gtk.accelerator_parse("Escape") self.fullscreen_button.add_accelerator("activate", self.accel_group, key, mod, Gtk.AccelFlags.VISIBLE) # Hide Menu self.menubar.hide() else: self.unfullscreen() key, mod = Gtk.accelerator_parse("Escape") self.fullscreen_button.remove_accelerator( self.accel_group, key, mod) self.menubar.show() self.TextEditor.grab_focus() def set_focusmode(self, widget, data=None): if widget.get_active(): self.init_typewriter() self.MarkupBuffer.focusmode_highlight() self.focusmode = True self.TextEditor.grab_focus() self.check_scroll(self.TextBuffer.get_insert()) if self.spellcheck != False: self.SpellChecker._misspelled.set_property('underline', 0) else: self.remove_typewriter() self.focusmode = False self.TextBuffer.remove_tag(self.MarkupBuffer.grayfont, self.TextBuffer.get_start_iter(), self.TextBuffer.get_end_iter()) self.TextBuffer.remove_tag(self.MarkupBuffer.blackfont, self.TextBuffer.get_start_iter(), self.TextBuffer.get_end_iter()) self.MarkupBuffer.markup_buffer(1) self.TextEditor.grab_focus() self.update_line_and_char_count() self.check_scroll() if self.spellcheck != False: self.SpellChecker._misspelled.set_property('underline', 4) def scroll_smoothly(self, widget, frame_clock, data = None): if self.smooth_scroll_data['target_pos'] == -1: return True def ease_out_cubic(t): p = t - 1; return p * p * p + 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): t = float(now - self.smooth_scroll_starttime) / float(self.smooth_scroll_endtime - self.smooth_scroll_starttime) else: t = 1 t = ease_out_cubic(t) pos = self.smooth_scroll_data['source_pos'] + (t * (self.smooth_scroll_data['target_pos'] - self.smooth_scroll_data['source_pos'])) # print("n %i, t %f, p %i, st %i, et %i" % (now, t, pos, self.smooth_scroll_starttime, self.smooth_scroll_endtime)) widget.get_vadjustment().props.value = pos return True # continue ticking def check_scroll(self, mark): gradient_offset = 80 buf = self.TextEditor.get_buffer() ins_it = buf.get_iter_at_mark(mark) loc_rect = self.TextEditor.get_iter_location(ins_it) # alignment offset added from top pos_y = loc_rect.y + loc_rect.height + self.EditorAlignment.props.top_padding ha = self.ScrolledWindow.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 + 60 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.ScrolledWindow.add_tick_callback(self.scroll_smoothly) def window_resize(self, widget, data=None): # To calc padding top / bottom self.window_height = widget.get_size()[1] w_width = widget.get_size()[0] # Calculate left / right margin width_request = 600 if(w_width < 900): # self.MarkupBuffer.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): # self.MarkupBuffer.set_multiplier(10) width_request = 800 self.current_font_size = 15 self.alignment_padding = 40 lm = 7 * 10 self.get_style_context().remove_class("small") self.get_style_context().remove_class("large") self.get_style_context().add_class("medium") else: # self.MarkupBuffer.set_multiplier(13) self.current_font_size = 17 width_request = 1000 self.alignment_padding = 60 lm = 7 * 13 self.get_style_context().remove_class("medium") self.get_style_context().remove_class("small") self.get_style_context().add_class("large") self.EditorAlignment.props.top_padding = self.alignment_padding self.EditorAlignment.props.bottom_padding = self.alignment_padding self.TextEditor.set_left_margin(lm) self.TextEditor.set_right_margin(lm) self.MarkupBuffer.recalculate(lm) if self.focusmode: self.remove_typewriter() self.init_typewriter() if self.TextEditor.props.width_request != width_request: self.TextEditor.props.width_request = width_request alloc = self.TextEditor.get_allocation() alloc.width = width_request self.TextEditor.size_allocate(alloc) def style_changed(self, widget, data=None): pgc = self.TextEditor.get_pango_context() mets = pgc.get_metrics() self.MarkupBuffer.set_multiplier(Pango.units_to_double(mets.get_approximate_char_width()) + 1) print(Pango.units_to_double(mets.get_approximate_char_width())) def save_document(self, widget, data=None): if self.filename: logger.info("saving") filename = self.filename f = codecs.open(filename, encoding="utf-8", mode='w') f.write(self.get_text()) f.close() if self.did_change: self.did_change = False title = self.get_title() self.set_headerbar_title(title[2:]) return Gtk.ResponseType.OK else: 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, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_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 f = codecs.open(filename, encoding="utf-8", mode='w') f.write(self.get_text()) f.close() self.filename = filename self.set_headerbar_title(os.path.basename(filename) + self.title_end) self.did_change = False filechooser.destroy() return response else: filechooser.destroy() return Gtk.ResponseType.CANCEL def save_document_as(self, widget, data=None): filechooser = Gtk.FileChooserDialog( "Save your File", self, Gtk.FileChooserAction.SAVE, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_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 f = codecs.open(filename, encoding="utf-8", mode='w') f.write(self.get_text()) f.close() self.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 export(self, export_type="html"): filechooser = Gtk.FileChooserDialog( "Export as %s" % export_type.upper(), self, Gtk.FileChooserAction.SAVE, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_SAVE, Gtk.ResponseType.OK) ) filechooser.set_do_overwrite_confirmation(True) if self.filename: filechooser.set_filename(self.filename[:-2] + export_type.lower()) response = filechooser.run() if response == Gtk.ResponseType.OK: filename = filechooser.get_filename() if filename.endswith("." + export_type): filename = filename[:-len(export_type)-1] filechooser.destroy() else: filechooser.destroy() return # Converting text to bytes for python 3 text = bytes(self.get_text(), "utf-8") output_dir = os.path.abspath(os.path.join(filename, os.path.pardir)) basename = os.path.basename(filename) args = ['pandoc', '--from=markdown', '--smart'] if export_type == "pdf": args.append("-o%s.pdf" % basename) elif export_type == "odt": args.append("-o%s.odt" % basename) elif export_type == "html": css = helpers.get_media_file('uberwriter.css') args.append("-c%s" % css) args.append("-o%s.html" % basename) args.append("--mathjax") p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, cwd=output_dir) output = p.communicate(text)[0] return filename def export_as_odt(self, widget, data=None): self.export("odt") def export_as_html(self, widget, data=None): self.export("html") def export_as_pdf(self, widget, data=None): if self.texlive_installed == False and APT_ENABLED: try: cache = apt.Cache() inst = cache["texlive"].is_installed except: inst = True if inst == False: dialog = Gtk.MessageDialog(self, Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.INFO, Gtk.ButtonsType.NONE, _("You can not export to PDF.") ) dialog.format_secondary_markup(_("Please install <a href=\"apt:texlive\">texlive</a> from the software center.")) response = dialog.run() return else: self.texlive_installed = True self.export("pdf") def copy_html_to_clipboard(self, widget, date=None): """Copies only html without headers etc. to Clipboard""" args = ['pandoc', '--from=markdown', '--smart', '-thtml'] p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE) text = bytes(self.get_text(), "utf-8") output = p.communicate(text)[0] cb = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) cb.set_text(output.decode("utf-8"), -1) cb.store() def open_document(self, widget): if self.check_change() == Gtk.ResponseType.CANCEL: return if self.focusmode: self.focusmode_button.set_active(False) 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, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_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): if self.did_change and len(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_title(_('Unsaved changes')) dialog.set_default_size(200, 150) dialog.set_default_response(Gtk.ResponseType.YES) response = dialog.run() if response == Gtk.ResponseType.YES: title = self.get_title() if self.save_document(widget = None) == Gtk.ResponseType.CANCEL: dialog.destroy() return self.check_change() else: dialog.destroy() return response elif response == Gtk.ResponseType.NO: dialog.destroy() return response else: dialog.destroy() return Gtk.ResponseType.CANCEL def new_document(self, widget): if self.check_change() == Gtk.ResponseType.CANCEL: return else: self.TextBuffer.set_text('') self.TextEditor.undos = [] self.TextEditor.redos = [] self.did_change = False self.filename = None self.set_headerbar_title("New File" + self.title_end) def menu_activate_focusmode(self, widget=None): self.focusmode_button.emit('activate') def menu_activate_fullscreen(self, widget=None): self.fullscreen_button.emit('activate') def menu_toggle_sidebar(self, widget=None): self.sidebar.toggle_sidebar() def menu_activate_preview(self, widget=None): self.preview_button.emit('activate') # # Not added as menu button as of now. Standard is typewriter active. # def toggle_typewriter(self, widget, data=None): # self.typewriter_active = widget.get_active() def toggle_spellcheck(self, widget, data=None): if self.spellcheck: if widget.get_active(): self.SpellChecker.enable() else: self.SpellChecker.disable() elif widget.get_active(): try: self.SpellChecker = SpellChecker(self.TextEditor, locale.getdefaultlocale()[0], collapse=False) self.spellcheck = True except: self.SpellChecker = None self.spellcheck = False dialog = Gtk.MessageDialog(self, Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.INFO, Gtk.ButtonsType.NONE, _("You can not enable the Spell Checker.") ) dialog.format_secondary_text(_("Please install 'hunspell' or 'aspell' dictionarys for your language from the software center.")) response = dialog.run() return 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() print(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 ll = 2 lr = 23 else: text = "[Insert link title here](%s)" % uri ll = 1 lr = 22 self.TextBuffer.place_cursor(self.TextBuffer.get_iter_at_mark( self.TextBuffer.get_mark('gtk_drag_target'))) self.TextBuffer.insert_at_cursor(text) insert_mark = self.TextBuffer.get_insert() selection_bound = self.TextBuffer.get_selection_bound() cursor_iter = self.TextBuffer.get_iter_at_mark(insert_mark) cursor_iter.backward_chars(len(text) - ll) print('move_cursor') self.TextBuffer.move_mark(insert_mark, cursor_iter) cursor_iter.forward_chars(lr) self.TextBuffer.move_mark(selection_bound, cursor_iter) print('move selection') elif info == 2: # Text target self.TextBuffer.place_cursor(self.TextBuffer.get_iter_at_mark( self.TextBuffer.get_mark('gtk_drag_target'))) self.TextBuffer.insert_at_cursor(data.get_text()) Gtk.drag_finish(drag_context, True, True, time) self.present() print("returning true") return False def toggle_preview(self, widget, data=None): if widget.get_active(): # 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. args = ['pandoc', '--from=markdown', '--smart', '-thtml', '--mathjax', '-c', helpers.get_media_file('uberwriter.css')] p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE) text = bytes(self.get_text(), "utf-8") output = p.communicate(text)[0] # Load in Webview and scroll to #ID self.webview = WebKit.WebView() self.webview.load_html_string(output.decode("utf-8"), 'file://localhost/') # Delete the cursor-scroll mark again # cursor_iter = self.TextBuffer.get_iter_at_mark(self.TextBuffer.get_insert()) # begin_del = cursor_iter.copy() # begin_del.backward_chars(30) # self.TextBuffer.delete(begin_del, cursor_iter) self.ScrolledWindow.remove(self.EditorViewport) self.ScrolledWindow.add(self.webview) self.webview.show() # This saying that all links will be opened in default browser, but local files are opened in appropriate apps: self.webview.connect("navigation-requested", self.on_click_link) else: self.ScrolledWindow.remove(self.webview) self.webview.destroy() self.ScrolledWindow.add(self.EditorViewport) self.TextEditor.show() self.queue_draw() return True def on_click_link(self, view, frame, req, data=None): # This provide ability for self.webview to open links in default browser if(req.get_uri().startswith("http://")): webbrowser.open(req.get_uri()) return True # Don't let the event "bubble up" def dark_mode_toggled(self, widget, data=None): # Save state for saving settings later self.dark_mode = widget.get_active() if self.dark_mode: # Dark Mode is on self.gtk_settings.set_property('gtk-application-prefer-dark-theme', True) self.get_style_context().add_class("dark_mode") self.MarkupBuffer.dark_mode(True) # self.background_image = helpers.get_media_path('bg_dark.png') else: # Dark mode off self.gtk_settings.set_property('gtk-application-prefer-dark-theme', False) self.get_style_context().remove_class("dark_mode") self.MarkupBuffer.dark_mode(False) # self.background_image = helpers.get_media_path('bg_light.png') # surface = cairo.ImageSurface.create_from_png(self.background_image) # self.background_pattern = cairo.SurfacePattern(surface) # self.background_pattern.set_extend(cairo.EXTEND_REPEAT) # Gtk.StyleContext.add_provider_for_screen( # Gdk.Screen.get_default(), self.style_provider, # Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION # ) # Redraw contents of window (self) self.queue_draw() def load_file(self, filename=None): """Open File from command line or open / open recent etc.""" if filename: if filename.startswith('file://'): filename = filename[7:] filename = urllib.parse.unquote_plus(filename) try: self.preview_button.set_active(False) self.filename = filename f = codecs.open(filename, encoding="utf-8", mode='r') self.TextBuffer.set_text(f.read()) f.close() self.MarkupBuffer.markup_buffer(0) self.set_headerbar_title(os.path.basename(filename) + self.title_end) self.TextEditor.undo_stack = [] self.TextEditor.redo_stack = [] # ei = self.TextBuffer.get_end_iter() # anchor = self.TextBuffer.create_child_anchor(ei) # al = Gtk.Label.new('asd') # al.set_text('...') # al.show() # self.TextEditor.add_child_at_anchor(al, anchor) except Exception as e: logger.warning("Error Reading File: %r" % e) self.did_change = False else: logger.warning("No File arg") # Help Menu def open_launchpad_translation(self, widget, data=None): webbrowser.open("https://translations.launchpad.net/uberwriter") def open_launchpad_help(self, widget, data=None): webbrowser.open("https://answers.launchpad.net/uberwriter") def open_pandoc_markdown(self, widget, data=None): webbrowser.open("http://johnmacfarlane.net/pandoc/README.html#pandocs-markdown") def open_uberwriter_markdown(self, widget, data=None): self.load_file(helpers.get_media_file('uberwriter_markdown.md')) def open_search_and_replace(self, widget, data=None): self.searchreplace.toggle_search() def open_advanced_export(self, widget, data=None): if self.UberwriterAdvancedExportDialog is not None: advexp = self.UberwriterAdvancedExportDialog() # pylint: disable= response = advexp.run() if response == 1: advexp.advanced_export(bytes(self.get_text(), "utf-8")) advexp.destroy() def open_recent(self, widget, data=None): if data: if self.check_change() == Gtk.ResponseType.CANCEL: return else: self.load_file(data) def generate_recent_files_menu(self, parent_menu): # Recent file filter self.recent_manager = Gtk.RecentManager.get_default() self.recent_files_menu = Gtk.RecentChooserMenu.new_for_manager(self.recent_manager) self.recent_files_menu.set_sort_type(Gtk.RecentSortType.MRU) recent_filter = Gtk.RecentFilter.new() recent_filter.add_mime_type('text/x-markdown') self.recent_files_menu.set_filter(recent_filter) menu = Gtk.Menu.new() for entry in self.recent_files_menu.get_items(): if entry.exists(): item = Gtk.MenuItem.new_with_label(entry.get_display_name()) item.connect('activate', self.open_recent, entry.get_uri()) menu.append(item) item.show() menu.show() parent_menu.set_submenu(menu) parent_menu.show() def poll_for_motion(self): if (self.was_motion == False and self.status_bar_visible and self.buffer_modified_for_status_bar and self.TextEditor.props.has_focus): # self.status_bar.set_state_flags(Gtk.StateFlags.INSENSITIVE, True) self.statusbar_revealer.set_reveal_child(False) self.hb_revealer.set_reveal_child(False) self.status_bar_visible = False self.buffer_modified_for_status_bar = False return False self.was_motion = False return True def on_motion_notify(self, widget, event, data=None): now = event.get_time() if now - self.timestamp_last_mouse_motion > 150: self.timestamp_last_mouse_motion = now return if now - self.timestamp_last_mouse_motion < 100: return if now - self.timestamp_last_mouse_motion > 100: if self.status_bar_visible == False: self.statusbar_revealer.set_reveal_child(True) self.hb_revealer.set_reveal_child(True) self.hb.props.opacity = 1 self.status_bar_visible = True self.buffer_modified_for_status_bar = False self.update_line_and_char_count() # self.status_bar.set_state_flags(Gtk.StateFlags.NORMAL, True) GObject.timeout_add(3000, self.poll_for_motion) self.was_motion = True def focus_out(self, widget, data=None): if self.status_bar_visible == False: self.statusbar_revealer.set_reveal_child(True) self.hb_revealer.set_reveal_child(True) self.hb.props.opacity = 1 self.status_bar_visible = True self.buffer_modified_for_status_bar = False self.update_line_and_char_count() def override_headerbar_background(self, widget, cr): if(widget.get_window().get_state() & self.testbits): bg_color = self.get_style_context().get_background_color(Gtk.StateFlags.ACTIVE) alloc = widget.get_allocation() width = alloc.width height = alloc.height cr.rectangle(0,0, width, height) cr.set_source_rgb(bg_color.red, bg_color.green, bg_color.blue) cr.fill() def draw_gradient(self, widget, cr): bg_color = self.get_style_context().get_background_color(Gtk.StateFlags.ACTIVE) lg_top = cairo.LinearGradient(0, 0, 0, 80) 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 = widget.get_allocation().width height = widget.get_allocation().height cr.rectangle(0, 0, width, 80) cr.set_source(lg_top) cr.fill() cr.rectangle(0, height - 80, width, height) lg_btm = cairo.LinearGradient(0, height - 80, 0, height) 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 finish_initializing(self, builder): # pylint: disable=E1002 """Set up the main window""" super(UberwriterWindow, self).finish_initializing(builder) self.AboutDialog = AboutUberwriterDialog self.UberwriterAdvancedExportDialog = UberwriterAdvancedExportDialog self.builder = builder self.connect('save-file', self.save_document) self.connect('save-file-as', self.save_document_as) self.connect('new-file', self.new_document) self.connect('open-file', self.open_document) self.connect('toggle-fullscreen', self.menu_activate_fullscreen) self.connect('toggle-focusmode', self.menu_activate_focusmode) self.connect('toggle-preview', self.menu_activate_preview) self.connect('toggle-spellcheck', self.toggle_spellcheck) self.connect('close-window', self.on_mnu_close_activate) self.connect('toggle-search', self.open_search_and_replace) self.scroll_adjusted = False # Code for other initialization actions should be added here. # Texlive checker self.texlive_installed = False self.set_name('UberwriterWindow') self.use_headerbar = True if self.use_headerbar == True: self.hb_revealer = Gtk.Revealer() self.hb = Gtk.HeaderBar() self.hb_revealer.add(self.hb) self.hb_revealer.props.transition_duration = 1000 self.hb_revealer.props.transition_type = Gtk.RevealerTransitionType.CROSSFADE self.hb.props.show_close_button = True self.hb.get_style_context().add_class("titlebar") self.set_titlebar(self.hb_revealer) self.hb_revealer.show() self.hb_revealer.set_reveal_child(True) self.hb.show() bbtn = Gtk.MenuButton() btn_settings = Gtk.MenuButton() btn_settings.props.image = Gtk.Image.new_from_icon_name('emblem-system-symbolic', Gtk.IconSize.BUTTON) btn_settings.set_popup(self.builder.get_object("menu4")) # icon = Gio.ThemedIcon(name="mail-sendm receive-symbolic") # image = Gtk.Image.new_from_gicon(icon, Gtk.IconSize.BUTTON) # bbtn.add(image) bbtn.set_popup(self.builder.get_object("menu1")) self.hb.pack_start(bbtn) self.hb.pack_end(btn_settings) self.hb.show_all() self.testbits = Gdk.WindowState.TILED | Gdk.WindowState.MAXIMIZED self.connect('draw', self.override_headerbar_background) self.title_end = " – UberWriter" self.set_headerbar_title("New File" + self.title_end) self.focusmode = False self.word_count = builder.get_object('word_count') self.char_count = builder.get_object('char_count') self.menubar = builder.get_object('menubar1') self.menubar.hide() # Wire up buttons self.fullscreen_button = builder.get_object('fullscreen_toggle') self.focusmode_button = builder.get_object('focus_toggle') self.preview_button = builder.get_object('preview_toggle') self.fullscreen_button.set_name('fullscreen_toggle') self.focusmode_button.set_name('focus_toggle') self.preview_button.set_name('preview_toggle') # Setup status bar hide after 3 seconds self.status_bar = builder.get_object('status_bar_box') self.statusbar_revealer = builder.get_object('status_bar_revealer') self.status_bar.set_name('status_bar_box') self.status_bar_visible = True self.was_motion = True self.buffer_modified_for_status_bar = False self.connect("motion-notify-event", self.on_motion_notify) GObject.timeout_add(3000, self.poll_for_motion) self.accel_group = Gtk.AccelGroup() self.add_accel_group(self.accel_group) # Setup light background self.TextEditor = TextEditor() self.TextEditor.set_name('UberwriterEditor') base_leftmargin = 40 # self.TextEditor.set_left_margin(base_leftmargin) self.TextEditor.set_left_margin(40) self.TextEditor.props.width_request = 600 self.TextEditor.props.halign = Gtk.Align.CENTER self.TextEditor.set_vadjustment(builder.get_object('vadjustment1')) self.TextEditor.set_wrap_mode(Gtk.WrapMode.WORD) self.TextEditor.connect('focus-out-event', self.focus_out) self.TextEditor.get_style_context().connect('changed', self.style_changed) # self.TextEditor.install_style_property_parser self.TextEditor.show() self.TextEditor.grab_focus() self.ScrolledWindow = builder.get_object('editor_scrolledwindow') self.EditorAlignment = builder.get_object('editor_alignment') self.EditorAlignment.add(self.TextEditor) self.alignment_padding = 40 self.EditorViewport = builder.get_object('editor_viewport') self.EditorViewport.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.PreviewPane = builder.get_object('preview_scrolledwindow') self.TextEditor.set_margin_top(38) self.TextEditor.set_margin_bottom(16) self.TextEditor.set_pixels_above_lines(4) self.TextEditor.set_pixels_below_lines(4) self.TextEditor.set_pixels_inside_wrap(8) tab_array = Pango.TabArray.new(1, True) tab_array.set_tab(0, Pango.TabAlign.LEFT, 20) self.TextEditor.set_tabs(tab_array) self.TextBuffer = self.TextEditor.get_buffer() self.TextBuffer.set_text('') # Init Window height for top/bottom padding self.window_height = self.get_size()[1] self.text_change_event = self.TextBuffer.connect('changed', self.text_changed) # Init file name with None self.filename = None self.generate_recent_files_menu(self.builder.get_object('recent')) self.style_provider = Gtk.CssProvider() self.style_provider.load_from_path(helpers.get_media_path('style.css')) Gtk.StyleContext.add_provider_for_screen( Gdk.Screen.get_default(), self.style_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION ) # Markup and Shortcuts for the TextBuffer self.MarkupBuffer = MarkupBuffer(self, self.TextBuffer, base_leftmargin) self.MarkupBuffer.markup_buffer() # Scrolling -> Dark or not? self.textchange = False self.scroll_count = 0 self.timestamp_last_mouse_motion = 0 self.TextBuffer.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.TextEditor.drag_dest_set_target_list(self.target_list) self.TextEditor.connect_after('drag-data-received', self.on_drag_data_received) def on_drop(widget, *args): print("drop") self.TextEditor.connect('drag-drop', on_drop) self.TextBuffer.connect('paste-done', self.paste_done) # self.connect('key-press-event', self.alt_mod) # Events for Typewriter mode # Setting up inline preview self.InlinePreview = UberwriterInlinePreview(self.TextEditor, self.TextBuffer) # Vertical scrolling self.vadjustment = self.ScrolledWindow.get_vadjustment() self.vadjustment.connect('value-changed', self.scrolled) # Setting up spellcheck try: self.SpellChecker = SpellChecker(self.TextEditor, locale.getdefaultlocale()[0], collapse=False) self.spellcheck = True except: self.SpellChecker = None self.spellcheck = False builder.get_object("disable_spellcheck").set_active(False) if self.spellcheck: self.SpellChecker.append_filter('[#*]+', SpellChecker.FILTER_WORD) self.did_change = False ### # Sidebar initialization test ### self.paned_window = builder.get_object("main_pained") self.sidebar_box = builder.get_object("sidebar_box") self.sidebar = UberwriterSidebar(self) self.sidebar_box.hide() ### # Search and replace initialization # Same interface as Sidebar ;) ### self.searchreplace = UberwriterSearchAndReplace(self) # Window resize self.window_resize(self) self.connect("configure-event", self.window_resize) self.connect("delete-event", self.on_delete_called) self.gtk_settings = Gtk.Settings.get_default() self.load_settings(builder) self.connect_after('realize', self.color_window) def color_window(self, widget, data=None): window_gdk = self.get_window() window_gdk.set_background(Gdk.Color(0, 1, 0)) 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 else: 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. self.save_settings() Gtk.main_quit() def set_headerbar_title(self, title): if self.use_headerbar: self.hb.props.title = title self.set_title(title) def save_settings(self): if not os.path.exists(CONFIG_PATH): try: os.makedirs(CONFIG_PATH) except Exception as e: logger.debug("Failed to make uberwriter config path in\ ~/.config/uberwriter. Error: %r" % e) try: settings = dict() settings["dark_mode"] = self.dark_mode settings["spellcheck"] = self.SpellChecker.enabled f = open(CONFIG_PATH + "settings.pickle", "wb+") pickle.dump(settings, f) f.close() logger.debug("Saved settings: %r" % settings) except Exception as e: logger.debug("Error writing settings file to disk. Error: %r" % e) def load_settings(self, builder): dark_mode_button = builder.get_object("dark_mode") spellcheck_button = builder.get_object("disable_spellcheck") try: f = open(CONFIG_PATH + "settings.pickle", "rb") settings = pickle.load(f) f.close() self.dark_mode = settings['dark_mode'] dark_mode_button.set_active(settings['dark_mode']) spellcheck_button.set_active(settings['spellcheck']) logger.debug("loaded settings: %r" % settings) except Exception as e: logger.debug("(First Run?) Error loading settings from home dir. \ Error: %r", e) return True
import sys from os.path import join, dirname sys.path.append(join(dirname(__file__), '../src/')) import locale import gtk from gtkspellcheck import SpellChecker if __name__ == '__main__': def quit(*args): gtk.main_quit() window = gtk.Window(gtk.WINDOW_TOPLEVEL) window.set_title('PyGtkSpellCheck Example') view = gtk.TextView() spellchecker = SpellChecker(view, locale.getdefaultlocale()[0]) for code, name in spellchecker.languages: print('code: %5s, language: %s' % (code, name)) window.set_default_size(600, 400) window.add(view) window.show_all() window.connect('delete-event', quit) gtk.main()
def finish_initializing(self, builder): # pylint: disable=E1002 """Set up the main window""" super(UberwriterWindow, self).finish_initializing(builder) self.AboutDialog = AboutUberwriterDialog self.UberwriterAdvancedExportDialog = UberwriterAdvancedExportDialog # Code for other initialization actions should be added here. # Texlive checker self.texlive_installed = False # Draw background self.background_image = helpers.get_media_path('bg_light.png') self.connect('draw', self.draw_bg) self.set_name('UberwriterWindow') self.title_end = " – UberWriter" self.set_title("New File" + self.title_end) # Drag and drop self.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.drag_dest_set_target_list(self.target_list) self.focusmode = False self.word_count = builder.get_object('word_count') self.char_count = builder.get_object('char_count') self.menubar = builder.get_object('menubar1') # Wire up buttons self.fullscreen_button = builder.get_object('fullscreen_toggle') self.focusmode_button = builder.get_object('focus_toggle') self.preview_button = builder.get_object('preview_toggle') self.fullscreen_button.set_name('fullscreen_toggle') self.focusmode_button.set_name('focus_toggle') self.preview_button.set_name('preview_toggle') # Setup status bar hide after 3 seconds self.status_bar = builder.get_object('status_bar_box') self.status_bar.set_name('status_bar_box') self.status_bar_visible = True self.was_motion = True self.buffer_modified_for_status_bar = False self.connect("motion-notify-event", self.on_motion_notify) GObject.timeout_add(3000, self.poll_for_motion) self.accel_group = Gtk.AccelGroup() self.add_accel_group(self.accel_group) # Setup light background surface = cairo.ImageSurface.create_from_png(self.background_image) self.background_pattern = cairo.SurfacePattern(surface) self.background_pattern.set_extend(cairo.EXTEND_REPEAT) self.TextEditor = TextEditor() base_leftmargin = 100 self.TextEditor.set_left_margin(base_leftmargin) self.TextEditor.set_left_margin(40) self.TextEditor.set_wrap_mode(Gtk.WrapMode.WORD) self.TextEditor.show() self.ScrolledWindow = builder.get_object('editor_scrolledwindow') self.ScrolledWindow.add(self.TextEditor) self.PreviewPane = builder.get_object('preview_scrolledwindow') pangoFont = Pango.FontDescription("Ubuntu Mono 15px") self.TextEditor.modify_font(pangoFont) self.TextEditor.set_margin_top(38) self.TextEditor.set_margin_bottom(16) self.TextEditor.set_pixels_above_lines(4) self.TextEditor.set_pixels_below_lines(4) self.TextEditor.set_pixels_inside_wrap(8) tab_array = Pango.TabArray.new(1, True) tab_array.set_tab(0, Pango.TabAlign.LEFT, 20) self.TextEditor.set_tabs(tab_array) self.TextBuffer = self.TextEditor.get_buffer() self.TextBuffer.set_text('') # Init Window height for top/bottom padding self.window_height = self.get_size()[1] self.text_change_event = self.TextBuffer.connect( 'changed', self.text_changed) self.TextEditor.connect('move-cursor', self.cursor_moved) # Init file name with None self.filename = None self.generate_recent_files_menu(self.builder.get_object('recent')) self.style_provider = Gtk.CssProvider() css = open(helpers.get_media_path('style.css'), 'rb') css_data = css.read() css.close() self.style_provider.load_from_data(css_data) Gtk.StyleContext.add_provider_for_screen( Gdk.Screen.get_default(), self.style_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION) # Still needed. self.fflines = 0 # Markup and Shortcuts for the TextBuffer self.MarkupBuffer = MarkupBuffer(self, self.TextBuffer, base_leftmargin) self.MarkupBuffer.markup_buffer() self.FormatShortcuts = FormatShortcuts(self.TextBuffer, self.TextEditor) # Scrolling -> Dark or not? self.textchange = False self.scroll_count = 0 self.TextBuffer.connect('mark-set', self.mark_set) self.TextEditor.drag_dest_unset() # Events to preserve margin. (To be deleted.) self.TextEditor.connect('delete-from-cursor', self.delete_from_cursor) self.TextEditor.connect('backspace', self.backspace) self.TextBuffer.connect('paste-done', self.paste_done) # self.connect('key-press-event', self.alt_mod) # Events for Typewriter mode self.TextBuffer.connect_after('mark-set', self.after_mark_set) self.TextBuffer.connect_after('changed', self.after_modify_text) self.TextEditor.connect_after('move-cursor', self.after_cursor_moved) self.TextEditor.connect_after('insert-at-cursor', self.after_insert_at_cursor) # Setting up inline preview self.InlinePreview = UberwriterInlinePreview(self.TextEditor, self.TextBuffer) # Vertical scrolling self.vadjustment = self.TextEditor.get_vadjustment() self.vadjustment.connect('value-changed', self.scrolled) # Setting up spellcheck try: self.SpellChecker = SpellChecker(self.TextEditor, locale.getdefaultlocale()[0], collapse=False) self.spellcheck = True except: self.SpellChecker = None self.spellcheck = False builder.get_object("disable_spellcheck").set_active(False) if self.spellcheck: self.SpellChecker.append_filter('[#*]+', SpellChecker.FILTER_WORD) self.did_change = False # Window resize self.connect("configure-event", self.window_resize) self.connect("delete-event", self.on_delete_called) self.load_settings(builder)
class UberwriterWindow(Window): __gtype_name__ = "UberwriterWindow" def scrolled(self, widget): """if window scrolled + focusmode make font black again""" # if self.focusmode: # if self.textchange == False: # if self.scroll_count >= 1: # 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.typewriter() # self.textchange = False def after_modify_text(self, *arg): if self.focusmode: self.typewriter() def after_insert_at_cursor(self, *arg): if self.focusmode: self.typewriter() def paste_done(self, *args): self.MarkupBuffer.markup_buffer(0) def init_typewriter(self): self.TextBuffer.disconnect(self.TextEditor.delete_event) self.TextBuffer.disconnect(self.TextEditor.insert_event) self.TextBuffer.disconnect(self.text_change_event) ci = self.TextBuffer.get_iter_at_mark( self.TextBuffer.get_mark('insert')) co = ci.get_offset() fflines = int(round((self.window_height - 55) / (2 * 30))) self.fflines = fflines self.TextEditor.fflines = fflines s = '\n' * fflines start_iter = self.TextBuffer.get_iter_at_offset(0) self.TextBuffer.insert(start_iter, s) end_iter = self.TextBuffer.get_iter_at_offset(-1) self.TextBuffer.insert(end_iter, s) ne_ci = self.TextBuffer.get_iter_at_offset(co + fflines) self.TextBuffer.place_cursor(ne_ci) # Scroll it to the center self.TextEditor.scroll_to_mark(self.TextBuffer.get_mark('insert'), 0.0, True, 0.0, 0.5) self.TextEditor.insert_event = self.TextBuffer.connect( "insert-text", self.TextEditor._on_insert) self.TextEditor.delete_event = self.TextBuffer.connect( "delete-range", self.TextEditor._on_delete) self.text_change_event = self.TextBuffer.connect( 'changed', self.text_changed) self.typewriter_initiated = True def typewriter(self): cursor = self.TextBuffer.get_mark("insert") cursor_iter = self.TextBuffer.get_iter_at_mark(cursor) self.TextEditor.scroll_to_iter(cursor_iter, 0.0, True, 0.0, 0.5) def remove_typewriter(self): self.TextBuffer.disconnect(self.TextEditor.delete_event) self.TextBuffer.disconnect(self.TextEditor.insert_event) self.TextBuffer.disconnect(self.text_change_event) startIter = self.TextBuffer.get_start_iter() endLineIter = startIter.copy() endLineIter.forward_lines(self.fflines) self.TextBuffer.delete(startIter, endLineIter) startIter = self.TextBuffer.get_end_iter() endLineIter = startIter.copy() # Move to line before last line endLineIter.backward_lines(self.fflines - 1) # Move to last char in last line endLineIter.backward_char() self.TextBuffer.delete(startIter, endLineIter) self.fflines = 0 self.TextEditor.fflines = 0 self.TextEditor.insert_event = self.TextBuffer.connect( "insert-text", self.TextEditor._on_insert) self.TextEditor.delete_event = self.TextBuffer.connect( "delete-range", self.TextEditor._on_delete) self.text_change_event = self.TextBuffer.connect( 'changed', self.text_changed) WORDCOUNT = re.compile(r"[\s#*\+\-]+", re.UNICODE) def update_line_and_char_count(self): if self.status_bar_visible == False: return self.char_count.set_text( str(self.TextBuffer.get_char_count() - (2 * self.fflines))) text = self.get_text() words = re.split(self.WORDCOUNT, text) length = len(words) # Last word a "space" if len(words[-1]) == 0: length = length - 1 # First word a "space" (happens in focus mode...) if len(words[0]) == 0: length = length - 1 if length == -1: length = 0 self.word_count.set_text(str(length)) # TODO rename line_count to word_count def get_text(self): if self.focusmode == False: start_iter = self.TextBuffer.get_start_iter() end_iter = self.TextBuffer.get_end_iter() else: start_iter = self.TextBuffer.get_iter_at_line(self.fflines) rbline = self.TextBuffer.get_line_count() - self.fflines end_iter = self.TextBuffer.get_iter_at_line(rbline) return self.TextBuffer.get_text(start_iter, end_iter, False) def mark_set(self, buffer, location, mark, data=None): if self.focusmode and (mark.get_name() == 'insert' or mark.get_name() == 'selection_bound'): akt_lines = self.TextBuffer.get_line_count() lb = self.fflines rb = akt_lines - self.fflines #print "a %d, lb %d, rb %d" % (akt_lines, lb, rb) #lb = self.TextBuffer.get_iter_at_line(self.fflines) #rbline = self.TextBuffer.get_line_count() - self.fflines #rb = self.TextBuffer.get_iter_at_line( # rbline) #rb.backward_line() linecount = location.get_line() #print "a %d, lb %d, rb %d, lc %d" % (akt_lines, lb, rb, linecount) if linecount < lb: move_to_line = self.TextBuffer.get_iter_at_line(lb) self.TextBuffer.move_mark(mark, move_to_line) elif linecount >= rb: move_to_line = self.TextBuffer.get_iter_at_line(rb) move_to_line.backward_char() self.TextBuffer.move_mark(mark, move_to_line) def after_mark_set(self, buffer, location, mark, data=None): if self.focusmode and mark.get_name() == 'insert': self.typewriter() def delete_from_cursor(self, editor, typ, count, Data=None): if not self.focusmode: return cursor = self.TextBuffer.get_mark("insert") cursor_iter = self.TextBuffer.get_iter_at_mark(cursor) if count < 0 and cursor_iter.starts_line(): lb = self.fflines linecount = cursor_iter.get_line() #print "lb %d, lc %d" % (lb, linecount) if linecount <= lb: self.TextEditor.emit_stop_by_name('delete-from-cursor') elif count > 0 and cursor_iter.ends_line(): akt_lines = self.TextBuffer.get_line_count() rb = akt_lines - self.fflines linecount = cursor_iter.get_line() + 1 #print "rb %d, lc %d" % (rb, linecount) if linecount >= rb: self.TextEditor.emit_stop_by_name('delete-from-cursor') def backspace(self, data=None): if not self.focusmode: return cursor = self.TextBuffer.get_mark("insert") cursor_iter = self.TextBuffer.get_iter_at_mark(cursor) if cursor_iter.starts_line(): lb = self.fflines linecount = cursor_iter.get_line() #print "lb %d, lc %d" % (lb, linecount) if linecount <= lb: self.TextEditor.emit_stop_by_name('backspace') def cursor_moved(self, widget, a, b, data=None): pass def after_cursor_moved(self, widget, step, count, extend_selection, data=None): if self.focusmode: self.typewriter() def text_changed(self, widget, data=None): if self.did_change == False: self.did_change = True title = self.get_title() self.set_title("* " + title) self.MarkupBuffer.markup_buffer(1) self.textchange = True self.buffer_modified_for_status_bar = True self.update_line_and_char_count() def toggle_fullscreen(self, widget, data=None): if widget.get_active(): self.fullscreen() key, mod = Gtk.accelerator_parse("Escape") self.fullscreen_button.add_accelerator("activate", self.accel_group, key, mod, Gtk.AccelFlags.VISIBLE) # Hide Menu self.menubar.hide() else: self.unfullscreen() key, mod = Gtk.accelerator_parse("Escape") self.fullscreen_button.remove_accelerator(self.accel_group, key, mod) self.menubar.show() self.TextEditor.grab_focus() def delete_text(self, widget): pass def cut_text(self, widget, data=None): self.TextEditor.cut() def paste_text(self, widget, data=None): self.TextEditor.paste() def copy_text(self, widget, data=None): self.TextEditor.copy() def undo(self, widget, data=None): self.TextEditor.undo() def redo(self, widget, data=None): self.TextEditor.redo() def set_italic(self, widget, data=None): """Ctrl + I""" self.FormatShortcuts.italic() def set_bold(self, widget, data=None): """Ctrl + B""" self.FormatShortcuts.bold() def insert_horizontal_rule(self, widget, data=None): """Ctrl + R""" self.FormatShortcuts.rule() def insert_unordered_list_item(self, widget, data=None): """Ctrl + U""" self.FormatShortcuts.unordered_list_item() def insert_ordered_list(self, widget, data=None): """CTRL + O""" self.FormatShortcuts.ordered_list_item() def insert_heading(self, widget, data=None): """CTRL + H""" self.FormatShortcuts.heading() def set_focusmode(self, widget, data=None): if widget.get_active(): self.init_typewriter() self.MarkupBuffer.focusmode_highlight() self.focusmode = True self.TextEditor.grab_focus() if self.spellcheck != False: self.SpellChecker._misspelled.set_property('underline', 0) else: self.remove_typewriter() self.focusmode = False self.TextBuffer.remove_tag(self.MarkupBuffer.grayfont, self.TextBuffer.get_start_iter(), self.TextBuffer.get_end_iter()) self.TextBuffer.remove_tag(self.MarkupBuffer.blackfont, self.TextBuffer.get_start_iter(), self.TextBuffer.get_end_iter()) self.MarkupBuffer.markup_buffer(1) self.TextEditor.grab_focus() self.update_line_and_char_count() if self.spellcheck != False: self.SpellChecker._misspelled.set_property('underline', 4) def window_resize(self, widget, data=None): # To calc padding top / bottom self.window_height = widget.get_size()[1] # Calculate left / right margin lm = (widget.get_size()[0] - 1050) / 2 self.TextEditor.set_left_margin(lm) self.TextEditor.set_right_margin(lm) self.MarkupBuffer.recalculate(lm) if self.focusmode: self.remove_typewriter() self.init_typewriter() def window_close(self, widget, data=None): return True def save_document(self, widget, data=None): if self.filename: logger.info("saving") filename = self.filename f = codecs.open(filename, encoding="utf-8", mode='w') f.write(self.get_text()) f.close() if self.did_change: self.did_change = False title = self.get_title() self.set_title(title[2:]) return Gtk.ResponseType.OK else: 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, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_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 f = codecs.open(filename, encoding="utf-8", mode='w') f.write(self.get_text()) f.close() self.filename = filename self.set_title(os.path.basename(filename) + self.title_end) self.did_change = False filechooser.destroy() return response elif response == Gtk.ResponseType.CANCEL: filechooser.destroy() return response def save_document_as(self, widget, data=None): filechooser = Gtk.FileChooserDialog( "Save your File", self, Gtk.FileChooserAction.SAVE, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_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 f = codecs.open(filename, encoding="utf-8", mode='w') f.write(self.get_text()) f.close() self.filename = filename self.set_title(os.path.basename(filename) + self.title_end) try: self.recent_manager.add_item(filename) except: pass filechooser.destroy() self.did_change = False elif response == Gtk.ResponseType.CANCEL: filechooser.destroy() def export(self, export_type="html"): filechooser = Gtk.FileChooserDialog( "Export as %s" % export_type.upper(), self, Gtk.FileChooserAction.SAVE, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_SAVE, Gtk.ResponseType.OK)) filechooser.set_do_overwrite_confirmation(True) if self.filename: filechooser.set_filename(self.filename[:-2] + export_type.lower()) response = filechooser.run() if response == Gtk.ResponseType.OK: filename = filechooser.get_filename() if filename.endswith("." + export_type): filename = filename[:-len(export_type) - 1] filechooser.destroy() else: filechooser.destroy() return # Converting text to bytes for python 3 text = bytes(self.get_text(), "utf-8") output_dir = os.path.abspath(os.path.join(filename, os.path.pardir)) basename = os.path.basename(filename) args = ['pandoc', '--from=markdown', '--smart'] if export_type == "pdf": args.append("-o%s.pdf" % basename) elif export_type == "odt": args.append("-o%s.odt" % basename) elif export_type == "html": css = helpers.get_media_file('uberwriter.css') args.append("-c%s" % css) args.append("-o%s.html" % basename) args.append("--mathjax") p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, cwd=output_dir) output = p.communicate(text)[0] return filename def export_as_odt(self, widget, data=None): self.export("odt") def export_as_html(self, widget, data=None): self.export("html") def export_as_pdf(self, widget, data=None): if self.texlive_installed == False and APT_ENABLED: try: cache = apt.Cache() inst = cache["texlive"].is_installed except: inst = True if inst == False: dialog = Gtk.MessageDialog( self, Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.INFO, None, _("You can not export to PDF.")) dialog.format_secondary_markup( _("Please install <a href=\"apt:texlive\">texlive</a> from the software center." )) response = dialog.run() return else: self.texlive_installed = True self.export("pdf") def copy_html_to_clipboard(self, widget, date=None): """Copies only html without headers etc. to Clipboard""" args = ['pandoc', '--from=markdown', '--smart', '-thtml'] p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE) text = bytes(self.get_text(), "utf-8") output = p.communicate(text)[0] cb = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) cb.set_text(output.decode("utf-8"), -1) cb.store() def open_document(self, widget): 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, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_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): if self.did_change and len(self.get_text()): dialog = Gtk.MessageDialog( self, Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.WARNING, 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).grab_focus() dialog.set_title(_('Unsaved changes')) dialog.set_default_size(200, 150) response = dialog.run() if response == Gtk.ResponseType.YES: title = self.get_title() if self.save_document(widget=None) == Gtk.ResponseType.CANCEL: dialog.destroy() return self.check_change() else: dialog.destroy() return response elif response == Gtk.ResponseType.CANCEL: dialog.destroy() return response elif response == Gtk.ResponseType.NO: dialog.destroy() return response def new_document(self, widget): if self.check_change() == Gtk.ResponseType.CANCEL: return else: self.TextBuffer.set_text('') self.TextEditor.undos = [] self.TextEditor.redos = [] self.did_change = False self.filename = None self.set_title("New File" + self.title_end) def menu_activate_focusmode(self, widget): self.focusmode_button.emit('activate') def menu_activate_fullscreen(self, widget): self.fullscreen_button.emit('activate') def menu_activate_preview(self, widget): self.preview_button.emit('activate') # Not added as menu button as of now. Standard is typewriter active. def toggle_typewriter(self, widget, data=None): self.typewriter_active = widget.get_active() def toggle_spellcheck(self, widget, data=None): if self.spellcheck: if widget.get_active(): self.SpellChecker.enable() else: self.SpellChecker.disable() elif widget.get_active(): try: self.SpellChecker = SpellChecker(self.TextEditor, locale.getdefaultlocale()[0], collapse=False) self.spellcheck = True except: self.SpellChecker = None self.spellcheck = False dialog = Gtk.MessageDialog( self, Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.INFO, 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 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'): text = "![Insert image title here](%s)" % uri ll = 2 lr = 23 else: text = "[Insert link title here](%s)" % uri ll = 1 lr = 22 self.TextBuffer.insert_at_cursor(text) insert_mark = self.TextBuffer.get_insert() selection_bound = self.TextBuffer.get_selection_bound() cursor_iter = self.TextBuffer.get_iter_at_mark(insert_mark) cursor_iter.backward_chars(len(text) - ll) self.TextBuffer.move_mark(insert_mark, cursor_iter) cursor_iter.forward_chars(lr) self.TextBuffer.move_mark(selection_bound, cursor_iter) elif info == 2: # Text target self.TextBuffer.insert_at_cursor(data.get_text()) self.present() def toggle_preview(self, widget, data=None): if widget.get_active(): # Insert a tag with ID to scroll to self.TextBuffer.insert_at_cursor('<span id="scroll_mark"></span>') args = [ 'pandoc', '--from=markdown', '--smart', '-thtml', '--mathjax', '-c', helpers.get_media_file('uberwriter.css') ] p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE) text = bytes(self.get_text(), "utf-8") output = p.communicate(text)[0] # Load in Webview and scroll to #ID self.webview = WebKit.WebView() self.webview.load_html_string(output.decode("utf-8"), 'file://localhost/' + '#scroll_mark') # Delete the cursor-scroll mark again cursor_iter = self.TextBuffer.get_iter_at_mark( self.TextBuffer.get_insert()) begin_del = cursor_iter.copy() begin_del.backward_chars(30) self.TextBuffer.delete(begin_del, cursor_iter) self.ScrolledWindow.remove(self.TextEditor) self.ScrolledWindow.add(self.webview) self.webview.show() # Making the background white white_background = helpers.get_media_path('white.png') surface = cairo.ImageSurface.create_from_png(white_background) self.background_pattern = cairo.SurfacePattern(surface) self.background_pattern.set_extend(cairo.EXTEND_REPEAT) # This saying that all links will be opened in default browser, but local files are opened in appropriate apps: self.webview.connect("navigation-requested", self.on_click_link) else: self.ScrolledWindow.remove(self.webview) self.webview.destroy() self.ScrolledWindow.add(self.TextEditor) self.TextEditor.show() surface = cairo.ImageSurface.create_from_png(self.background_image) self.background_pattern = cairo.SurfacePattern(surface) self.background_pattern.set_extend(cairo.EXTEND_REPEAT) self.queue_draw() def on_click_link(self, view, frame, req, data=None): # This provide ability for self.webview to open links in default browser webbrowser.open(req.get_uri()) return True # that string is god-damn-important: without it link will be opened in default browser AND also in self.webview def dark_mode_toggled(self, widget, data=None): # Save state for saving settings later self.dark_mode = widget.get_active() if self.dark_mode: # Dark Mode is on css = open(helpers.get_media_path('style_dark.css'), 'rb') css_data = css.read() css.close() self.style_provider.load_from_data(css_data) self.background_image = helpers.get_media_path('bg_dark.png') self.MarkupBuffer.dark_mode(True) else: # Dark mode off css = open(helpers.get_media_path('style.css'), 'rb') css_data = css.read() css.close() self.style_provider.load_from_data(css_data) self.background_image = helpers.get_media_path('bg_light.png') self.MarkupBuffer.dark_mode(False) surface = cairo.ImageSurface.create_from_png(self.background_image) self.background_pattern = cairo.SurfacePattern(surface) self.background_pattern.set_extend(cairo.EXTEND_REPEAT) Gtk.StyleContext.add_provider_for_screen( Gdk.Screen.get_default(), self.style_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION) # Redraw contents of window (self) self.queue_draw() def load_file(self, filename=None): """Open File from command line or open / open recent etc.""" if filename: if filename.startswith('file://'): filename = filename[7:] filename = urllib.parse.unquote_plus(filename) try: self.preview_button.set_active(False) self.filename = filename f = codecs.open(filename, encoding="utf-8", mode='r') self.TextBuffer.set_text(f.read()) f.close() self.MarkupBuffer.markup_buffer(0) self.set_title(os.path.basename(filename) + self.title_end) self.TextEditor.undos = [] self.TextEditor.redos = [] except Exception as e: logger.warning("Error Reading File: %r" % e) self.did_change = False else: logger.warning("No File arg") def draw_bg(self, widget, context): context.set_source(self.background_pattern) context.paint() # Help Menu def open_launchpad_translation(self, widget, data=None): webbrowser.open("https://translations.launchpad.net/uberwriter") def open_launchpad_help(self, widget, data=None): webbrowser.open("https://answers.launchpad.net/uberwriter") def open_pandoc_markdown(self, widget, data=None): webbrowser.open( "http://johnmacfarlane.net/pandoc/README.html#pandocs-markdown") def open_uberwriter_markdown(self, widget, data=None): self.load_file(helpers.get_media_file('uberwriter_markdown.md')) def open_advanced_export(self, widget, data=None): if self.UberwriterAdvancedExportDialog is not None: advexp = self.UberwriterAdvancedExportDialog() # pylint: disable= response = advexp.run() if response == 1: advexp.advanced_export(bytes(self.get_text(), "utf-8")) advexp.destroy() def open_recent(self, widget, data=None): if data: if self.check_change() == Gtk.ResponseType.CANCEL: return else: self.load_file(data) def generate_recent_files_menu(self, parent_menu): # Recent file filter self.recent_manager = Gtk.RecentManager.get_default() self.recent_files_menu = Gtk.RecentChooserMenu.new_for_manager( self.recent_manager) self.recent_files_menu.set_sort_type(Gtk.RecentSortType.MRU) recent_filter = Gtk.RecentFilter.new() recent_filter.add_mime_type('text/x-markdown') self.recent_files_menu.set_filter(recent_filter) menu = Gtk.Menu.new() for entry in self.recent_files_menu.get_items(): if entry.exists(): item = Gtk.MenuItem.new_with_label(entry.get_display_name()) item.connect('activate', self.open_recent, entry.get_uri()) menu.append(item) item.show() menu.show() parent_menu.set_submenu(menu) parent_menu.show() def poll_for_motion(self): if (self.was_motion == False and self.status_bar_visible and self.buffer_modified_for_status_bar): self.status_bar.set_state_flags(Gtk.StateFlags.INSENSITIVE, True) self.status_bar_visible = False self.buffer_modified_for_status_bar = False return False self.was_motion = False return True def on_motion_notify(self, widget, data=None): self.was_motion = True if self.status_bar_visible == False: 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) GObject.timeout_add(3000, self.poll_for_motion) def move_popup(self, widget, data=None): pass def finish_initializing(self, builder): # pylint: disable=E1002 """Set up the main window""" super(UberwriterWindow, self).finish_initializing(builder) self.AboutDialog = AboutUberwriterDialog self.UberwriterAdvancedExportDialog = UberwriterAdvancedExportDialog # Code for other initialization actions should be added here. # Texlive checker self.texlive_installed = False # Draw background self.background_image = helpers.get_media_path('bg_light.png') self.connect('draw', self.draw_bg) self.set_name('UberwriterWindow') self.title_end = " – UberWriter" self.set_title("New File" + self.title_end) # Drag and drop self.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.drag_dest_set_target_list(self.target_list) self.focusmode = False self.word_count = builder.get_object('word_count') self.char_count = builder.get_object('char_count') self.menubar = builder.get_object('menubar1') # Wire up buttons self.fullscreen_button = builder.get_object('fullscreen_toggle') self.focusmode_button = builder.get_object('focus_toggle') self.preview_button = builder.get_object('preview_toggle') self.fullscreen_button.set_name('fullscreen_toggle') self.focusmode_button.set_name('focus_toggle') self.preview_button.set_name('preview_toggle') # Setup status bar hide after 3 seconds self.status_bar = builder.get_object('status_bar_box') self.status_bar.set_name('status_bar_box') self.status_bar_visible = True self.was_motion = True self.buffer_modified_for_status_bar = False self.connect("motion-notify-event", self.on_motion_notify) GObject.timeout_add(3000, self.poll_for_motion) self.accel_group = Gtk.AccelGroup() self.add_accel_group(self.accel_group) # Setup light background surface = cairo.ImageSurface.create_from_png(self.background_image) self.background_pattern = cairo.SurfacePattern(surface) self.background_pattern.set_extend(cairo.EXTEND_REPEAT) self.TextEditor = TextEditor() base_leftmargin = 100 self.TextEditor.set_left_margin(base_leftmargin) self.TextEditor.set_left_margin(40) self.TextEditor.set_wrap_mode(Gtk.WrapMode.WORD) self.TextEditor.show() self.ScrolledWindow = builder.get_object('editor_scrolledwindow') self.ScrolledWindow.add(self.TextEditor) self.PreviewPane = builder.get_object('preview_scrolledwindow') pangoFont = Pango.FontDescription("Ubuntu Mono 15px") self.TextEditor.modify_font(pangoFont) self.TextEditor.set_margin_top(38) self.TextEditor.set_margin_bottom(16) self.TextEditor.set_pixels_above_lines(4) self.TextEditor.set_pixels_below_lines(4) self.TextEditor.set_pixels_inside_wrap(8) tab_array = Pango.TabArray.new(1, True) tab_array.set_tab(0, Pango.TabAlign.LEFT, 20) self.TextEditor.set_tabs(tab_array) self.TextBuffer = self.TextEditor.get_buffer() self.TextBuffer.set_text('') # Init Window height for top/bottom padding self.window_height = self.get_size()[1] self.text_change_event = self.TextBuffer.connect( 'changed', self.text_changed) self.TextEditor.connect('move-cursor', self.cursor_moved) # Init file name with None self.filename = None self.generate_recent_files_menu(self.builder.get_object('recent')) self.style_provider = Gtk.CssProvider() css = open(helpers.get_media_path('style.css'), 'rb') css_data = css.read() css.close() self.style_provider.load_from_data(css_data) Gtk.StyleContext.add_provider_for_screen( Gdk.Screen.get_default(), self.style_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION) # Still needed. self.fflines = 0 # Markup and Shortcuts for the TextBuffer self.MarkupBuffer = MarkupBuffer(self, self.TextBuffer, base_leftmargin) self.MarkupBuffer.markup_buffer() self.FormatShortcuts = FormatShortcuts(self.TextBuffer, self.TextEditor) # Scrolling -> Dark or not? self.textchange = False self.scroll_count = 0 self.TextBuffer.connect('mark-set', self.mark_set) self.TextEditor.drag_dest_unset() # Events to preserve margin. (To be deleted.) self.TextEditor.connect('delete-from-cursor', self.delete_from_cursor) self.TextEditor.connect('backspace', self.backspace) self.TextBuffer.connect('paste-done', self.paste_done) # self.connect('key-press-event', self.alt_mod) # Events for Typewriter mode self.TextBuffer.connect_after('mark-set', self.after_mark_set) self.TextBuffer.connect_after('changed', self.after_modify_text) self.TextEditor.connect_after('move-cursor', self.after_cursor_moved) self.TextEditor.connect_after('insert-at-cursor', self.after_insert_at_cursor) # Setting up inline preview self.InlinePreview = UberwriterInlinePreview(self.TextEditor, self.TextBuffer) # Vertical scrolling self.vadjustment = self.TextEditor.get_vadjustment() self.vadjustment.connect('value-changed', self.scrolled) # Setting up spellcheck try: self.SpellChecker = SpellChecker(self.TextEditor, locale.getdefaultlocale()[0], collapse=False) self.spellcheck = True except: self.SpellChecker = None self.spellcheck = False builder.get_object("disable_spellcheck").set_active(False) if self.spellcheck: self.SpellChecker.append_filter('[#*]+', SpellChecker.FILTER_WORD) self.did_change = False # Window resize self.connect("configure-event", self.window_resize) self.connect("delete-event", self.on_delete_called) self.load_settings(builder) 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 else: 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. self.window_close(widget) self.save_settings() Gtk.main_quit() def save_settings(self): if not os.path.exists(CONFIG_PATH): try: os.makedirs(CONFIG_PATH) except Exception as e: log.debug( "Failed to make uberwriter config path in ~/.config/uberwriter. Error: %r" % e) try: settings = dict() settings["dark_mode"] = self.dark_mode settings["spellcheck"] = self.SpellChecker.enabled f = open(CONFIG_PATH + "settings.pickle", "wb+") pickle.dump(settings, f) f.close() logger.debug("Saved settings: %r" % settings) except Exception as e: logger.debug("Error writing settings file to disk. Error: %r" % e) def load_settings(self, builder): dark_mode_button = builder.get_object("dark_mode") spellcheck_button = builder.get_object("disable_spellcheck") try: f = open(CONFIG_PATH + "settings.pickle", "rb") settings = pickle.load(f) f.close() self.dark_mode = settings['dark_mode'] dark_mode_button.set_active(settings['dark_mode']) spellcheck_button.set_active(settings['spellcheck']) logger.debug("loaded settings: %r" % settings) except Exception as e: logger.debug( "(First Run?) Error loading settings from home dir. Error: %r", e) return 1
class UpdateWindow(UpdateWidgetBase): def __init__(self, liststore, entry=None, source=None, account=None): self.entry = entry self.child = None # for GtkGrid.get_child_at no available gui = Gtk.Builder() gui.add_from_file(SHARED_DATA_FILE('update.glade')) self.media = MediaFile(gui) self.config = AuthorizedTwitterAccount.CONFIG host_re = '//[A-Za-z0-9\'~+\-=_.,/%\?!;:@#\*&\(\)]+' self.http_re = re.compile("(http:%s)" % host_re) self.https_re = re.compile("(https:%s)" % host_re) self.screen_name_pattern = re.compile('\B@[0-9A-Za-z_]{1,15}') self.account_combobox = AccountCombobox( gui, liststore, source, account) is_above = SETTINGS.get_boolean('update-window-keep-above') self.update_window = gui.get_object('window1') self.update_window.set_keep_above(is_above) self.button_image = gui.get_object('button_image') self.label_num = gui.get_object('label_num') self.comboboxtext_privacy = FacebookPrivacyCombobox(gui) self.grid_button = gui.get_object('grid_button') self.on_combobox_account_changed() self.button_tweet = gui.get_object('button_tweet') self.text_buffer = gui.get_object('textbuffer') self.on_textbuffer_changed(self.text_buffer) textview = gui.get_object('textview') if SpellChecker: self.spellchecker = SpellChecker(textview) if not SETTINGS.get_boolean('spell-checker'): self.spellchecker.disable() gui.connect_signals(self) if entry: widget = 'buttonbox1' if entry.get('protected') else 'image_secret' gui.get_object(widget).hide() self._download_user_icon_with_callback(gui, entry) else: gui.get_object('grid_entry').destroy() self.update_window.present() def _run(self, unknown, gui, entry, icon, *args): self._set_ui(gui, entry, icon) user = entry['user_name'] self.update_window.set_title(_('Reply to %s') % user.decode('utf-8')) self.text_buffer.set_text(self._get_all_mentions_from(entry)) self.update_window.present() def _get_all_mentions_from(self, entry): account_user = '******' + self.account_combobox.get_account_obj().user_name users = '@%s ' % entry['user_name'] matches = self.screen_name_pattern.finditer(entry['status_body']) other_users = ' '.join([x.group() for x in matches if x.group() != account_user]) if other_users: users += other_users + ' ' return users def set_upload_media(self, file): self.media.set(file) self.on_textbuffer_changed(self.text_buffer) def on_textview_populate_popup(self, textview, default_menu): if not SpellChecker: return menuitem = Gtk.CheckMenuItem.new_with_mnemonic(_('Check _Spelling')) menuitem.connect("toggled", self._toggle) is_enbled = SETTINGS.get_boolean('spell-checker') menuitem.set_active(is_enbled) if not menuitem.get_active(): separator = Gtk.SeparatorMenuItem.new() default_menu.prepend(separator) default_menu.prepend(menuitem) default_menu.show_all() def _toggle(self, menuitem): state = menuitem.get_active() SETTINGS.set_boolean('spell-checker', state) if state: self.spellchecker.enable() else: self.spellchecker.disable() def on_button_tweet_clicked(self, button): start, end = self.text_buffer.get_bounds() status = self.text_buffer.get_text(start, end, False).decode('utf-8') account_obj = self.account_combobox.get_account_obj() account_source = self.account_combobox.get_account_source() params = {'in_reply_to_status_id': self.entry.get('id')} \ if self.entry else {} if account_source == 'Facebook': params = self.comboboxtext_privacy.get_params() if self.media.file: # update with media is_shrink = True size = 1024 upload_file = self.media.get_upload_file_obj(is_shrink, size) account_obj.api.update_with_media( status.encode('utf-8'), upload_file.name, params=params) else: # normal update account_obj.api.update(status, params=params) if not self.entry: num = self.account_combobox.combobox_account.get_active() SETTINGS.set_int('recent-account', num) self.update_window.destroy() def on_button_image_clicked(self, button): dialog = FileChooserDialog() file = dialog.run(self.update_window) self.set_upload_media(file) def on_eventbox_attached_press_event(self, image_menu, event): if event.button == 3: image_menu.popup(None, None, None, None, event.button, event.time) def on_menuitem_remove_activate(self, menuitem): self.media.clear() self.on_textbuffer_changed(self.text_buffer) def on_file_activated(self, *args): print args def on_combobox_account_changed(self, *args): source = self.account_combobox.get_account_source() self.button_image.set_sensitive(source == 'Twitter') widget = self.label_num if source == 'Twitter' \ else self.comboboxtext_privacy.widget if source == 'Facebook' \ else None if self.child: # for GtkGrid.get_child_at no available self.grid_button.remove(self.child) if widget: self.grid_button.attach(widget, 0, 0, 1, 1) self.child = widget def on_textbuffer_changed(self, text_buffer): start, end = self.text_buffer.get_bounds() status = self.text_buffer.get_text(start, end, False).decode('utf-8') status = self.http_re.sub("*"*self.config.short_url_length, status) status = self.https_re.sub("*"*self.config.short_url_length_https, status) num = 140 - len(status) - self.media.get_link_letters() color = 'red' if num <= 10 else 'black' text = '<span fgcolor="%s">%s</span>' % (color, str(num)) self.label_num.set_markup(text) status = bool(0 <= num < 140) self.button_tweet.set_sensitive(status) def on_textview_key_press_event(self, textview, event): key = event.keyval masks = event.state.value_names if key == Gdk.KEY_Return and 'GDK_CONTROL_MASK' in masks: if self.button_tweet.get_sensitive(): self.on_button_tweet_clicked(None) else: return False return True def on_button_link_clicked(self, textbuffer): text = ' https://twitter.com/%s/status/%s' % ( self.entry['user_name'], self.entry['id']) textbuffer.place_cursor(textbuffer.get_end_iter()) textbuffer.insert_at_cursor(text) def on_button_quote_clicked(self, textbuffer): quote_format = SETTINGS_TWITTER.get_string('quote-format') text = quote_format.format(user=self.entry.get('user_name'), status=self.entry.get('status_body')) textbuffer.delete(textbuffer.get_start_iter(), textbuffer.get_end_iter(),) textbuffer.insert_at_cursor(text) textbuffer.place_cursor(textbuffer.get_start_iter())
class UberwriterWindow(Window): __gtype_name__ = "UberwriterWindow" def scrolled(self, widget): """if window scrolled + focusmode make font black again""" if self.focusmode: if self.textchange == False: if self.scroll_count >= 1: 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.typewriter() self.textchange = False def after_modify_text(self, *arg): if self.focusmode: self.typewriter() def after_insert_at_cursor(self, *arg): if self.focusmode: self.typewriter() def paste_done(self, *args): self.MarkupBuffer.markup_buffer(0) def init_typewriter(self): self.TextBuffer.disconnect(self.TextEditor.delete_event) self.TextBuffer.disconnect(self.TextEditor.insert_event) self.TextBuffer.disconnect(self.text_change_event) ci = self.TextBuffer.get_iter_at_mark(self.TextBuffer.get_mark('insert')) co = ci.get_offset() fflines = int(round((self.window_height-55)/(2*30))) self.fflines = fflines self.TextEditor.fflines = fflines s = '\n'*fflines start_iter = self.TextBuffer.get_iter_at_offset(0) self.TextBuffer.insert(start_iter, s) end_iter = self.TextBuffer.get_iter_at_offset(-1) self.TextBuffer.insert(end_iter, s) ne_ci = self.TextBuffer.get_iter_at_offset(co + fflines) self.TextBuffer.place_cursor(ne_ci) # Scroll it to the center self.TextEditor.scroll_to_mark(self.TextBuffer.get_mark('insert'), 0.0, True, 0.0, 0.5) self.TextEditor.insert_event = self.TextBuffer.connect("insert-text",self.TextEditor._on_insert) self.TextEditor.delete_event = self.TextBuffer.connect("delete-range",self.TextEditor._on_delete) self.text_change_event = self.TextBuffer.connect('changed', self.text_changed) self.typewriter_initiated = True def typewriter(self): cursor = self.TextBuffer.get_mark("insert") cursor_iter = self.TextBuffer.get_iter_at_mark(cursor) self.TextEditor.scroll_to_iter(cursor_iter, 0.0, True, 0.0, 0.5) def remove_typewriter(self): self.TextBuffer.disconnect(self.TextEditor.delete_event) self.TextBuffer.disconnect(self.TextEditor.insert_event) self.TextBuffer.disconnect(self.text_change_event) startIter = self.TextBuffer.get_start_iter() endLineIter = startIter.copy() endLineIter.forward_lines(self.fflines) self.TextBuffer.delete(startIter, endLineIter) startIter = self.TextBuffer.get_end_iter() endLineIter = startIter.copy() # Move to line before last line endLineIter.backward_lines(self.fflines - 1) # Move to last char in last line endLineIter.backward_char() self.TextBuffer.delete(startIter, endLineIter) self.fflines = 0 self.TextEditor.fflines = 0 self.TextEditor.insert_event = self.TextBuffer.connect("insert-text",self.TextEditor._on_insert) self.TextEditor.delete_event = self.TextBuffer.connect("delete-range",self.TextEditor._on_delete) self.text_change_event = self.TextBuffer.connect('changed', self.text_changed) WORDCOUNT = re.compile(r"[\s#*\+\-]+", re.UNICODE) def update_line_and_char_count(self): if self.status_bar_visible == False: return self.char_count.set_text(str(self.TextBuffer.get_char_count() - (2 * self.fflines))) text = self.get_text() words = re.split(self.WORDCOUNT, text) length = len(words) # Last word a "space" if len(words[-1]) == 0: length = length - 1 # First word a "space" (happens in focus mode...) if len(words[0]) == 0: length = length - 1 if length == -1: length = 0 self.word_count.set_text(str(length)) # TODO rename line_count to word_count def get_text(self): if self.focusmode == False: start_iter = self.TextBuffer.get_start_iter() end_iter = self.TextBuffer.get_end_iter() else: start_iter = self.TextBuffer.get_iter_at_line(self.fflines) rbline = self.TextBuffer.get_line_count() - self.fflines end_iter = self.TextBuffer.get_iter_at_line(rbline) return self.TextBuffer.get_text(start_iter, end_iter, False) def mark_set(self, buffer, location, mark, data=None): if self.focusmode and (mark.get_name() == 'insert' or mark.get_name() == 'selection_bound'): akt_lines = self.TextBuffer.get_line_count() lb = self.fflines rb = akt_lines - self.fflines #print "a %d, lb %d, rb %d" % (akt_lines, lb, rb) #lb = self.TextBuffer.get_iter_at_line(self.fflines) #rbline = self.TextBuffer.get_line_count() - self.fflines #rb = self.TextBuffer.get_iter_at_line( # rbline) #rb.backward_line() linecount = location.get_line() #print "a %d, lb %d, rb %d, lc %d" % (akt_lines, lb, rb, linecount) if linecount < lb: move_to_line = self.TextBuffer.get_iter_at_line(lb) self.TextBuffer.move_mark(mark, move_to_line) elif linecount >= rb: move_to_line = self.TextBuffer.get_iter_at_line(rb) move_to_line.backward_char() self.TextBuffer.move_mark(mark, move_to_line) def after_mark_set(self, buffer, location, mark, data=None): if self.focusmode and mark.get_name() == 'insert': self.typewriter() def delete_from_cursor(self, editor, typ, count, Data=None): if not self.focusmode: return cursor = self.TextBuffer.get_mark("insert") cursor_iter = self.TextBuffer.get_iter_at_mark(cursor) if count < 0 and cursor_iter.starts_line(): lb = self.fflines linecount = cursor_iter.get_line() #print "lb %d, lc %d" % (lb, linecount) if linecount <= lb: self.TextEditor.emit_stop_by_name('delete-from-cursor') elif count > 0 and cursor_iter.ends_line(): akt_lines = self.TextBuffer.get_line_count() rb = akt_lines - self.fflines linecount = cursor_iter.get_line() + 1 #print "rb %d, lc %d" % (rb, linecount) if linecount >= rb: self.TextEditor.emit_stop_by_name('delete-from-cursor') def backspace(self, data=None): if not self.focusmode: return cursor = self.TextBuffer.get_mark("insert") cursor_iter = self.TextBuffer.get_iter_at_mark(cursor) if cursor_iter.starts_line(): lb = self.fflines linecount = cursor_iter.get_line() #print "lb %d, lc %d" % (lb, linecount) if linecount <= lb: self.TextEditor.emit_stop_by_name('backspace') def cursor_moved(self, widget, a, b, data=None): pass def after_cursor_moved(self, widget, step, count, extend_selection, data=None): if self.focusmode: self.typewriter() def text_changed(self, widget, data=None): if self.did_change == False: self.did_change = True title = self.get_title() self.set_title("* " + title) self.MarkupBuffer.markup_buffer(1) self.textchange = True self.buffer_modified_for_status_bar = True self.update_line_and_char_count() def toggle_fullscreen(self, widget, data=None): if widget.get_active(): self.fullscreen() key, mod = Gtk.accelerator_parse("Escape") self.fullscreen_button.add_accelerator("activate", self.accel_group, key, mod, Gtk.AccelFlags.VISIBLE) else: self.unfullscreen() key, mod = Gtk.accelerator_parse("Escape") self.fullscreen_button.remove_accelerator( self.accel_group, key, mod) self.TextEditor.grab_focus() def delete_text(self, widget): pass def cut_text(self, widget, data=None): self.TextEditor.cut() def paste_text(self, widget, data=None): self.TextEditor.paste() def copy_text(self, widget, data=None): self.TextEditor.copy() def undo(self, widget, data=None): self.TextEditor.undo() def redo(self, widget, data=None): self.TextEditor.redo() def set_italic(self, widget, data=None): """Ctrl + I""" self.FormatShortcuts.italic() def set_bold(self, widget, data=None): """Ctrl + B""" self.FormatShortcuts.bold() def insert_horizontal_rule(self, widget, data=None): """Ctrl + R""" self.FormatShortcuts.rule() def insert_unordered_list_item(self, widget, data=None): """Ctrl + U""" self.FormatShortcuts.unordered_list_item() def insert_ordered_list(self, widget, data=None): """CTRL + O""" self.FormatShortcuts.ordered_list_item() def insert_heading(self, widget, data=None): """CTRL + H""" self.FormatShortcuts.heading() def set_focusmode(self, widget, data=None): if widget.get_active(): self.init_typewriter() self.MarkupBuffer.focusmode_highlight() self.focusmode = True self.TextEditor.grab_focus() if self.spellcheck != False: self.SpellChecker._misspelled.set_property('underline', 0) else: self.remove_typewriter() self.focusmode = False self.TextBuffer.remove_tag(self.MarkupBuffer.grayfont, self.TextBuffer.get_start_iter(), self.TextBuffer.get_end_iter()) self.TextBuffer.remove_tag(self.MarkupBuffer.blackfont, self.TextBuffer.get_start_iter(), self.TextBuffer.get_end_iter()) self.MarkupBuffer.markup_buffer(1) self.TextEditor.grab_focus() self.update_line_and_char_count() if self.spellcheck != False: self.SpellChecker._misspelled.set_property('underline', 4) def window_resize(self, widget, data=None): # To calc padding top / bottom self.window_height = widget.get_size()[1] # Calculate left / right margin lm = (widget.get_size()[0] - 600) / 2 self.TextEditor.set_left_margin(lm) self.TextEditor.set_right_margin(lm) self.MarkupBuffer.recalculate(lm) if self.focusmode: self.remove_typewriter() self.init_typewriter() def window_close(self, widget, data=None): return True def save_document(self, widget, data=None): if self.filename: logger.info("saving") filename = self.filename with open(filename, "wb") as f: f.write(self.get_text().encode("utf-8")) if self.did_change: self.did_change = False title = self.get_title() self.set_title(title[2:]) return Gtk.ResponseType.OK else: 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, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_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 text = self.get_text() with open(filename, "wb") as f: f.write(text.encode("utf-8")) self.filename = filename self.set_title(os.path.basename(filename) + self.title_end) self.did_change = False filechooser.destroy() return response elif response == Gtk.ResponseType.CANCEL: filechooser.destroy() return response def save_document_as(self, widget, data=None): filechooser = Gtk.FileChooserDialog( "Save your File", self, Gtk.FileChooserAction.SAVE, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_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 # Codecs have been deprecated http://bugs.python.org/issue8796 # f = codecs.open(filename, encoding="utf-8", mode='w') text = self.get_text() # use python3 with-statement with open(filename, "wb") as f: f.write(text.encode("utf-8")) self.filename = filename self.set_title(os.path.basename(filename) + self.title_end) try: self.recent_manager.add_item(filename) except: pass filechooser.destroy() self.did_change = False elif response == Gtk.ResponseType.CANCEL: filechooser.destroy() def export(self, export_type="html"): filechooser = Gtk.FileChooserDialog( "Export as %s" % export_type.upper(), self, Gtk.FileChooserAction.SAVE, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_SAVE, Gtk.ResponseType.OK) ) filechooser.set_do_overwrite_confirmation(True) if self.filename: filechooser.set_filename(self.filename[:-2] + export_type.lower()) response = filechooser.run() if response == Gtk.ResponseType.OK: filename = filechooser.get_filename() if filename.endswith("." + export_type): filename = filename[:-len(export_type)-1] filechooser.destroy() else: filechooser.destroy() return text = self.get_text().encode("utf-8") output_dir = os.path.abspath(os.path.join(filename, os.path.pardir)) basename = os.path.basename(filename) args = ['pandoc', '--from=markdown', '--smart'] if export_type == "pdf": args.append("-o%s.pdf" % basename) elif export_type == "odt": args.append("-o%s.odt" % basename) elif export_type == "html": css = helpers.get_media_file('uberwriter.css') args.append("-c%s" % css) args.append("-o%s.html" % basename) args.append("--mathjax") p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, cwd=output_dir) output = p.communicate(text)[0] return filename def export_as_odt(self, widget, data=None): self.export("odt") def export_as_html(self, widget, data=None): self.export("html") def export_as_pdf(self, widget, data=None): if self.texlive_installed == False: debian_like = True try: import apt except: debian_like = False if debian_like : try: cache = apt.Cache() inst = cache["texlive"].is_installed except: inst = True else: inst = False # replaces the apt check for non-apt distros fpath, fname = os.path.split('latex') if fpath: if is_exe('latex'): inst = True else: for path in os.environ["PATH"].split(os.pathsep): path = path.strip('"').strip() exe_file = os.path.join(path, 'latex') if os.path.isfile(exe_file) and os.access(exe_file, os.X_OK): inst = True if inst == False: dialog = Gtk.MessageDialog(self, Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.INFO, Gtk.ButtonsType.NONE, _("You can not export to PDF.") ) dialog.format_secondary_markup(_("Please install texlive from the software center.")) response = dialog.run() return else: self.texlive_installed = True self.export("pdf") def copy_html_to_clipboard(self, widget, date=None): # TODO connect to item in Menubar, and make new pandoc template for # only HTML, no headers etc. args = ['pandoc', '--from=markdown', '--smart', '-thtml'] p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE) text = self.get_text().encode("utf-8") output = p.communicate(text)[0] cb = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) cb.set_text(output.decode("utf-8"), -1) cb.store() def open_document(self, widget): 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, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_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): if self.did_change and len(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).grab_focus() dialog.set_title(_('Unsaved changes')) dialog.set_default_size(200, 150) response = dialog.run() if response == Gtk.ResponseType.YES: title = self.get_title() if self.save_document(widget = None) == Gtk.ResponseType.CANCEL: dialog.destroy() return self.check_change() else: dialog.destroy() return response elif response == Gtk.ResponseType.CANCEL: dialog.destroy() return response elif response == Gtk.ResponseType.NO: dialog.destroy() return response def new_document(self, widget): if self.check_change() == Gtk.ResponseType.CANCEL: return else: self.TextBuffer.set_text('') self.TextEditor.undos = [] self.TextEditor.redos = [] self.did_change = False self.filename = None self.set_title("New File" + self.title_end) def menu_activate_focusmode(self, widget): self.focusmode_button.emit('activate') def menu_activate_fullscreen(self, widget): self.fullscreen_button.emit('activate') # Not added as menu button as of now. Standard is typewriter active. def toggle_typewriter(self, widget, data=None): self.typewriter_active = widget.get_active() def toggle_spellcheck(self, widget, data=None): if self.spellcheck: if widget.get_active(): self.SpellChecker.enable() else: self.SpellChecker.disable() elif widget.get_active(): try: self.SpellChecker = SpellChecker(self.TextEditor, locale.getdefaultlocale()[0], collapse=False) self.spellcheck = True except: self.SpellChecker = None self.spellcheck = False dialog = Gtk.MessageDialog(self, Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.INFO, Gtk.ButtonsType.NONE, _("You can not enable the Spell Checker.") ) dialog.format_secondary_text(_("Please install 'hunspell' or 'aspell' dictionarys for your language from the software center.")) response = dialog.run() return 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'): text = "![Insert image title here](%s)" % uri ll = 2 lr = 23 else: text = "[Insert link title here](%s)" % uri ll = 1 lr = 22 self.TextBuffer.insert_at_cursor(text) insert_mark = self.TextBuffer.get_insert() selection_bound = self.TextBuffer.get_selection_bound() cursor_iter = self.TextBuffer.get_iter_at_mark(insert_mark) cursor_iter.backward_chars(len(text) - ll) self.TextBuffer.move_mark(insert_mark, cursor_iter) cursor_iter.forward_chars(lr) self.TextBuffer.move_mark(selection_bound, cursor_iter) elif info == 2: # Text target self.TextBuffer.insert_at_cursor(data.get_text()) self.present() def dark_mode_toggled(self, widget, data=None): if widget.get_active(): # Dark Mode is on # Hack for f*****g unico-shit if Gtk.get_minor_version() == 4: css = open(helpers.get_media_path('style_dark_old.css'), 'rb') else: css = open(helpers.get_media_path('style_dark.css'), 'rb') css_data = css.read() css.close() self.style_provider.load_from_data(css_data) self.background_image = helpers.get_media_path('bg_dark.png') self.MarkupBuffer.dark_mode(True) else: # Dark mode off css = open(helpers.get_media_path('style.css'), 'rb') css_data = css.read() css.close() self.style_provider.load_from_data(css_data) self.background_image = helpers.get_media_path('bg_light.png') self.MarkupBuffer.dark_mode(False) surface = cairo.ImageSurface.create_from_png(self.background_image) self.background_pattern = cairo.SurfacePattern(surface) self.background_pattern.set_extend(cairo.EXTEND_REPEAT) Gtk.StyleContext.add_provider_for_screen( Gdk.Screen.get_default(), self.style_provider, Gtk.STYLE_PROVIDER_PRIORITY_USER ) (w, h) = self.get_size() self.resize(w+1, h+1) def load_file(self, filename = None): """Open File from command line""" if filename: if filename.startswith('file://'): filename = filename[7:] filename = urllib.parse.unquote_plus(filename) self.filename = filename try: with open(filename, mode='r', encoding='utf-8') as f: self.TextBuffer.set_text(f.read()) self.MarkupBuffer.markup_buffer(0) self.set_title(os.path.basename(filename) + self.title_end) self.TextEditor.undos = [] self.TextEditor.redos = [] except Exception as e: logger.warning("Error Reading File") logger.warning(e) self.did_change = False else: logger.warning("No File arg") def draw_bg(self, widget, context): context.set_source(self.background_pattern) context.paint() def open_launchpad_translation(self, widget, data = None): webbrowser.open("https://translations.launchpad.net/uberwriter") def open_launchpad_help(self, widget, data = None): webbrowser.open("https://answers.launchpad.net/uberwriter") def open_advanced_export(self, widget, data=None): if self.UberwriterAdvancedExportDialog is not None: advexp = self.UberwriterAdvancedExportDialog() # pylint: disable= response = advexp.run() if response == 1: advexp.advanced_export(self.get_text()) advexp.destroy() def open_recent(self, widget, data=None): if data: logger.info("Filename: %s" % data) if self.check_change() == Gtk.ResponseType.CANCEL: return else: self.load_file(data) def generate_recent_files_menu(self, parent_menu): # Recent file filter self.recent_manager = Gtk.RecentManager.get_default() self.recent_files_menu = Gtk.RecentChooserMenu.new_for_manager(self.recent_manager) self.recent_files_menu.set_sort_type(Gtk.RecentSortType.MRU) recent_filter = Gtk.RecentFilter.new() recent_filter.add_mime_type('text/x-markdown') self.recent_files_menu.set_filter(recent_filter) menu = Gtk.Menu.new() for entry in self.recent_files_menu.get_items(): if entry.exists(): item = Gtk.MenuItem.new_with_label(entry.get_display_name()) item.connect('activate', self.open_recent, entry.get_uri()) menu.append(item) item.show() menu.show() parent_menu.set_submenu(menu) parent_menu.show() def poll_for_motion(self): if (self.was_motion == False and self.status_bar_visible and self.buffer_modified_for_status_bar): self.status_bar.set_state_flags(Gtk.StateFlags.INSENSITIVE, True) self.status_bar_visible = False self.buffer_modified_for_status_bar = False return False self.was_motion = False return True def on_motion_notify(self, widget, data=None): self.was_motion = True if self.status_bar_visible == False: 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) GObject.timeout_add(3000, self.poll_for_motion) def populate_popup(self, editor, menu, data=None): return item = Gtk.MenuItem.new() image = Gtk.Image.new_from_file('/home/wolf/test.jpg') image.show() item.add(image) item.show() logger.info("%s %s" % (menu, item)) menu.prepend(item) menu.show() def move_popup(self, widget, data=None): pass def finish_initializing(self, builder): # pylint: disable=E1002 """Set up the main window""" super(UberwriterWindow, self).finish_initializing(builder) self.AboutDialog = AboutUberwriterDialog self.UberwriterAdvancedExportDialog = UberwriterAdvancedExportDialog # Code for other initialization actions should be added here. # Texlive checker self.texlive_installed = False # Draw background self.background_image = helpers.get_media_path('bg_light.png') self.connect('draw', self.draw_bg) self.set_name('UberwriterWindow') self.title_end = " – UberWriter" self.set_title("New File" + self.title_end) # Drag and drop self.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.drag_dest_set_target_list(self.target_list) self.focusmode = False self.word_count = builder.get_object('word_count') self.char_count = builder.get_object('char_count') self.fullscreen_button = builder.get_object('fullscreen_toggle') self.focusmode_button = builder.get_object('focus_toggle') self.fullscreen_button.set_name('fullscreen_toggle') self.focusmode_button.set_name('focus_toggle') # Setup status bar hide after 3 seconds self.status_bar = builder.get_object('status_bar_box') self.status_bar.set_name('status_bar_box') self.status_bar_visible = True self.was_motion = True self.buffer_modified_for_status_bar = False self.connect("motion-notify-event", self.on_motion_notify) GObject.timeout_add(3000, self.poll_for_motion) self.accel_group = Gtk.AccelGroup() self.add_accel_group(self.accel_group) # p = "~/.uberwriter/" #p = os.path.expanduser(p) #self.temp_dir = p #if not os.path.exists(p): # os.makedirs(p) # Setting up light background surface = cairo.ImageSurface.create_from_png(self.background_image) self.background_pattern = cairo.SurfacePattern(surface) self.background_pattern.set_extend(cairo.EXTEND_REPEAT) self.TextEditor = TextEditor() base_leftmargin = 100 self.TextEditor.set_left_margin(base_leftmargin) self.TextEditor.set_left_margin(40) self.TextEditor.set_wrap_mode(Gtk.WrapMode.WORD) self.TextEditor.show() self.ScrolledWindow = builder.get_object('scrolledwindow1') self.ScrolledWindow.add(self.TextEditor) pangoFont = Pango.FontDescription("Ubuntu Mono 15px") self.TextEditor.modify_font(pangoFont) self.TextEditor.set_margin_top(38) self.TextEditor.set_margin_bottom(16) self.TextEditor.set_pixels_above_lines(5) self.TextEditor.set_pixels_below_lines(5) self.TextEditor.set_pixels_inside_wrap(10) tab_array = Pango.TabArray.new(1, True) tab_array.set_tab(0, Pango.TabAlign.LEFT, 20) self.TextEditor.set_tabs(tab_array) self.TextBuffer = self.TextEditor.get_buffer() self.TextBuffer.set_text('') # Init Window height for top/bottom padding self.window_height = self.get_size()[1] self.text_change_event = self.TextBuffer.connect('changed', self.text_changed) self.TextEditor.connect('move-cursor', self.cursor_moved) # Init file name with None self.filename = None self.generate_recent_files_menu(self.builder.get_object('recent')) self.style_provider = Gtk.CssProvider() css = open(helpers.get_media_path('style.css'), 'r') css_data = css.read() css.close() self.style_provider.load_from_data(css_data.encode("utf-8")) Gtk.StyleContext.add_provider_for_screen( Gdk.Screen.get_default(), self.style_provider, Gtk.STYLE_PROVIDER_PRIORITY_USER ) # Still needed. self.fflines = 0 # Markup and Shortcuts for the TextBuffer self.MarkupBuffer = MarkupBuffer(self, self.TextBuffer, base_leftmargin) self.MarkupBuffer.markup_buffer() self.FormatShortcuts = FormatShortcuts(self.TextBuffer, self.TextEditor) # Scrolling -> Dark or not? self.textchange = False self.scroll_count = 0 self.TextBuffer.connect('mark-set', self.mark_set) self.TextEditor.drag_dest_unset() # Events to preserve margin. (To be deleted.) self.TextEditor.connect('delete-from-cursor', self.delete_from_cursor) self.TextEditor.connect('backspace', self.backspace) self.TextBuffer.connect('paste-done', self.paste_done) # Events for Typewriter mode self.TextBuffer.connect_after('mark-set', self.after_mark_set) self.TextBuffer.connect_after('changed', self.after_modify_text) self.TextEditor.connect_after('move-cursor', self.after_cursor_moved) self.TextEditor.connect_after('insert-at-cursor', self.after_insert_at_cursor) # Events for popup menu self.TextEditor.connect_after('populate-popup', self.populate_popup) self.TextEditor.connect_after('popup-menu', self.move_popup) # Vertical scrolling self.vadjustment = self.TextEditor.get_vadjustment() self.vadjustment.connect('value-changed', self.scrolled) # Setting up spellcheck try: self.SpellChecker = SpellChecker(self.TextEditor, locale.getdefaultlocale()[0], collapse=False) self.spellcheck = True except: self.SpellChecker = None self.spellcheck = False builder.get_object("disable_spellcheck").set_active(False) if self.spellcheck: self.SpellChecker.append_filter('[#*]+', SpellChecker.FILTER_WORD) # Open file from commandline self.did_change = False # Window resize self.connect("configure-event", self.window_resize) # Window destroyed?? self.connect("delete-event", self.on_delete_called) 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_destroy(self, widget, data=None): """Called when the TexteditorWindow is closed.""" # Clean up code for saving application state should be added here. self.window_close(widget) Gtk.main_quit()
class UpdateWindow(UpdateWidgetBase): def __init__(self, liststore, entry=None, source=None, account=None): self.entry = entry self.child = None # for GtkGrid.get_child_at no available gui = Gtk.Builder() gui.add_from_file(SHARED_DATA_FILE('update.glade')) self.media = MediaFile(gui) self.config = AuthorizedTwitterAccount.CONFIG host_re = '//[A-Za-z0-9\'~+\-=_.,/%\?!;:@#\*&\(\)]+' self.http_re = re.compile("(http:%s)" % host_re) self.https_re = re.compile("(https:%s)" % host_re) self.screen_name_pattern = re.compile('\B@[0-9A-Za-z_]{1,15}') self.account_combobox = AccountCombobox(gui, liststore, source, account) is_above = SETTINGS.get_boolean('update-window-keep-above') self.update_window = gui.get_object('window1') self.update_window.set_keep_above(is_above) self.button_image = gui.get_object('button_image') self.label_num = gui.get_object('label_num') self.comboboxtext_privacy = FacebookPrivacyCombobox(gui) self.grid_button = gui.get_object('grid_button') self.on_combobox_account_changed() self.button_tweet = gui.get_object('button_tweet') self.text_buffer = gui.get_object('textbuffer') self.on_textbuffer_changed(self.text_buffer) textview = gui.get_object('textview') if SpellChecker: self.spellchecker = SpellChecker(textview) if not SETTINGS.get_boolean('spell-checker'): self.spellchecker.disable() gui.connect_signals(self) if entry: widget = 'buttonbox1' if entry.get('protected') else 'image_secret' gui.get_object(widget).hide() self._download_user_icon_with_callback(gui, entry) else: gui.get_object('grid_entry').destroy() self.update_window.present() def _run(self, unknown, gui, entry, icon, *args): self._set_ui(gui, entry, icon) user = entry['user_name'] self.update_window.set_title(_('Reply to %s') % user.decode('utf-8')) self.text_buffer.set_text(self._get_all_mentions_from(entry)) self.update_window.present() def _get_all_mentions_from(self, entry): account_user = '******' + self.account_combobox.get_account_obj().user_name users = '@%s ' % entry['user_name'] matches = self.screen_name_pattern.finditer(entry['status_body']) other_users = ' '.join( [x.group() for x in matches if x.group() != account_user]) if other_users: users += other_users + ' ' return users def set_upload_media(self, file): self.media.set(file) self.on_textbuffer_changed(self.text_buffer) def on_textview_populate_popup(self, textview, default_menu): if not SpellChecker: return menuitem = Gtk.CheckMenuItem.new_with_mnemonic(_('Check _Spelling')) menuitem.connect("toggled", self._toggle) is_enbled = SETTINGS.get_boolean('spell-checker') menuitem.set_active(is_enbled) if not menuitem.get_active(): separator = Gtk.SeparatorMenuItem.new() default_menu.prepend(separator) default_menu.prepend(menuitem) default_menu.show_all() def _toggle(self, menuitem): state = menuitem.get_active() SETTINGS.set_boolean('spell-checker', state) if state: self.spellchecker.enable() else: self.spellchecker.disable() def on_button_tweet_clicked(self, button): start, end = self.text_buffer.get_bounds() status = self.text_buffer.get_text(start, end, False).decode('utf-8') account_obj = self.account_combobox.get_account_obj() account_source = self.account_combobox.get_account_source() params = {'in_reply_to_status_id': self.entry.get('id')} \ if self.entry else {} if account_source == 'Facebook': params = self.comboboxtext_privacy.get_params() if self.media.file: # update with media is_shrink = True size = 1024 upload_file = self.media.get_upload_file_obj(is_shrink, size) account_obj.api.update_with_media(status.encode('utf-8'), upload_file.name, params=params) else: # normal update account_obj.api.update(status, params=params) if not self.entry: num = self.account_combobox.combobox_account.get_active() SETTINGS.set_int('recent-account', num) self.update_window.destroy() def on_button_image_clicked(self, button): dialog = FileChooserDialog() file = dialog.run(self.update_window) self.set_upload_media(file) def on_eventbox_attached_press_event(self, image_menu, event): if event.button == 3: image_menu.popup(None, None, None, None, event.button, event.time) def on_menuitem_remove_activate(self, menuitem): self.media.clear() self.on_textbuffer_changed(self.text_buffer) def on_file_activated(self, *args): print args def on_combobox_account_changed(self, *args): source = self.account_combobox.get_account_source() self.button_image.set_sensitive(source == 'Twitter') widget = self.label_num if source == 'Twitter' \ else self.comboboxtext_privacy.widget if source == 'Facebook' \ else None if self.child: # for GtkGrid.get_child_at no available self.grid_button.remove(self.child) if widget: self.grid_button.attach(widget, 0, 0, 1, 1) self.child = widget def on_textbuffer_changed(self, text_buffer): start, end = self.text_buffer.get_bounds() status = self.text_buffer.get_text(start, end, False).decode('utf-8') status = self.http_re.sub("*" * self.config.short_url_length, status) status = self.https_re.sub("*" * self.config.short_url_length_https, status) num = 140 - len(status) - self.media.get_link_letters() color = 'red' if num <= 10 else 'black' text = '<span fgcolor="%s">%s</span>' % (color, str(num)) self.label_num.set_markup(text) status = bool(0 <= num < 140) self.button_tweet.set_sensitive(status) def on_textview_key_press_event(self, textview, event): key = event.keyval masks = event.state.value_names if key == Gdk.KEY_Return and 'GDK_CONTROL_MASK' in masks: if self.button_tweet.get_sensitive(): self.on_button_tweet_clicked(None) else: return False return True def on_button_link_clicked(self, textbuffer): text = ' https://twitter.com/%s/status/%s' % (self.entry['user_name'], self.entry['id']) textbuffer.place_cursor(textbuffer.get_end_iter()) textbuffer.insert_at_cursor(text) def on_button_quote_clicked(self, textbuffer): quote_format = SETTINGS_TWITTER.get_string('quote-format') text = quote_format.format(user=self.entry.get('user_name'), status=self.entry.get('status_body')) textbuffer.delete( textbuffer.get_start_iter(), textbuffer.get_end_iter(), ) textbuffer.insert_at_cursor(text) textbuffer.place_cursor(textbuffer.get_start_iter())
class UberwriterWindow(Window): __gtype_name__ = "UberwriterWindow" def scrolled(self, widget): """if window scrolled + focusmode make font black again""" # if self.focusmode: # if self.textchange == False: # if self.scroll_count >= 1: # 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.typewriter() # self.textchange = False def after_modify_text(self, *arg): if self.focusmode: self.typewriter() def after_insert_at_cursor(self, *arg): if self.focusmode: self.typewriter() def paste_done(self, *args): self.MarkupBuffer.markup_buffer(0) def init_typewriter(self): self.TextBuffer.disconnect(self.TextEditor.delete_event) self.TextBuffer.disconnect(self.TextEditor.insert_event) self.TextBuffer.disconnect(self.text_change_event) ci = self.TextBuffer.get_iter_at_mark(self.TextBuffer.get_mark('insert')) co = ci.get_offset() fflines = int(round((self.window_height-55)/(2*30))) self.fflines = fflines self.TextEditor.fflines = fflines s = '\n'*fflines start_iter = self.TextBuffer.get_iter_at_offset(0) self.TextBuffer.insert(start_iter, s) end_iter = self.TextBuffer.get_iter_at_offset(-1) self.TextBuffer.insert(end_iter, s) ne_ci = self.TextBuffer.get_iter_at_offset(co + fflines) self.TextBuffer.place_cursor(ne_ci) # Scroll it to the center self.TextEditor.scroll_to_mark(self.TextBuffer.get_mark('insert'), 0.0, True, 0.0, 0.5) self.TextEditor.insert_event = self.TextBuffer.connect("insert-text",self.TextEditor._on_insert) self.TextEditor.delete_event = self.TextBuffer.connect("delete-range",self.TextEditor._on_delete) self.text_change_event = self.TextBuffer.connect('changed', self.text_changed) self.typewriter_initiated = True def typewriter(self): cursor = self.TextBuffer.get_mark("insert") cursor_iter = self.TextBuffer.get_iter_at_mark(cursor) self.TextEditor.scroll_to_iter(cursor_iter, 0.0, True, 0.0, 0.5) def remove_typewriter(self): self.TextBuffer.disconnect(self.TextEditor.delete_event) self.TextBuffer.disconnect(self.TextEditor.insert_event) self.TextBuffer.disconnect(self.text_change_event) startIter = self.TextBuffer.get_start_iter() endLineIter = startIter.copy() endLineIter.forward_lines(self.fflines) self.TextBuffer.delete(startIter, endLineIter) startIter = self.TextBuffer.get_end_iter() endLineIter = startIter.copy() # Move to line before last line endLineIter.backward_lines(self.fflines - 1) # Move to last char in last line endLineIter.backward_char() self.TextBuffer.delete(startIter, endLineIter) self.fflines = 0 self.TextEditor.fflines = 0 self.TextEditor.insert_event = self.TextBuffer.connect("insert-text",self.TextEditor._on_insert) self.TextEditor.delete_event = self.TextBuffer.connect("delete-range",self.TextEditor._on_delete) self.text_change_event = self.TextBuffer.connect('changed', self.text_changed) WORDCOUNT = re.compile(r"[\s#*\+\-]+", re.UNICODE) def update_line_and_char_count(self): if self.status_bar_visible == False: return self.char_count.set_text(str(self.TextBuffer.get_char_count() - (2 * self.fflines))) text = self.get_text() words = re.split(self.WORDCOUNT, text) length = len(words) # Last word a "space" if len(words[-1]) == 0: length = length - 1 # First word a "space" (happens in focus mode...) if len(words[0]) == 0: length = length - 1 if length == -1: length = 0 self.word_count.set_text(str(length)) # TODO rename line_count to word_count def get_text(self): if self.focusmode == False: start_iter = self.TextBuffer.get_start_iter() end_iter = self.TextBuffer.get_end_iter() else: start_iter = self.TextBuffer.get_iter_at_line(self.fflines) rbline = self.TextBuffer.get_line_count() - self.fflines end_iter = self.TextBuffer.get_iter_at_line(rbline) return self.TextBuffer.get_text(start_iter, end_iter, False) def mark_set(self, buffer, location, mark, data=None): if self.focusmode and (mark.get_name() == 'insert' or mark.get_name() == 'selection_bound'): akt_lines = self.TextBuffer.get_line_count() lb = self.fflines rb = akt_lines - self.fflines #print "a %d, lb %d, rb %d" % (akt_lines, lb, rb) #lb = self.TextBuffer.get_iter_at_line(self.fflines) #rbline = self.TextBuffer.get_line_count() - self.fflines #rb = self.TextBuffer.get_iter_at_line( # rbline) #rb.backward_line() linecount = location.get_line() #print "a %d, lb %d, rb %d, lc %d" % (akt_lines, lb, rb, linecount) if linecount < lb: move_to_line = self.TextBuffer.get_iter_at_line(lb) self.TextBuffer.move_mark(mark, move_to_line) elif linecount >= rb: move_to_line = self.TextBuffer.get_iter_at_line(rb) move_to_line.backward_char() self.TextBuffer.move_mark(mark, move_to_line) def after_mark_set(self, buffer, location, mark, data=None): if self.focusmode and mark.get_name() == 'insert': self.typewriter() def delete_from_cursor(self, editor, typ, count, Data=None): if not self.focusmode: return cursor = self.TextBuffer.get_mark("insert") cursor_iter = self.TextBuffer.get_iter_at_mark(cursor) if count < 0 and cursor_iter.starts_line(): lb = self.fflines linecount = cursor_iter.get_line() #print "lb %d, lc %d" % (lb, linecount) if linecount <= lb: self.TextEditor.emit_stop_by_name('delete-from-cursor') elif count > 0 and cursor_iter.ends_line(): akt_lines = self.TextBuffer.get_line_count() rb = akt_lines - self.fflines linecount = cursor_iter.get_line() + 1 #print "rb %d, lc %d" % (rb, linecount) if linecount >= rb: self.TextEditor.emit_stop_by_name('delete-from-cursor') def backspace(self, data=None): if not self.focusmode: return cursor = self.TextBuffer.get_mark("insert") cursor_iter = self.TextBuffer.get_iter_at_mark(cursor) if cursor_iter.starts_line(): lb = self.fflines linecount = cursor_iter.get_line() #print "lb %d, lc %d" % (lb, linecount) if linecount <= lb: self.TextEditor.emit_stop_by_name('backspace') def cursor_moved(self, widget, a, b, data=None): pass def after_cursor_moved(self, widget, step, count, extend_selection, data=None): if self.focusmode: self.typewriter() def text_changed(self, widget, data=None): if self.did_change == False: self.did_change = True title = self.get_title() self.set_title("* " + title) self.MarkupBuffer.markup_buffer(1) self.textchange = True self.buffer_modified_for_status_bar = True self.update_line_and_char_count() def toggle_fullscreen(self, widget, data=None): if widget.get_active(): self.fullscreen() key, mod = Gtk.accelerator_parse("Escape") self.fullscreen_button.add_accelerator("activate", self.accel_group, key, mod, Gtk.AccelFlags.VISIBLE) # Hide Menu self.menubar.hide() else: self.unfullscreen() key, mod = Gtk.accelerator_parse("Escape") self.fullscreen_button.remove_accelerator( self.accel_group, key, mod) self.menubar.show() self.TextEditor.grab_focus() def delete_text(self, widget): pass def cut_text(self, widget, data=None): self.TextEditor.cut() def paste_text(self, widget, data=None): self.TextEditor.paste() def copy_text(self, widget, data=None): self.TextEditor.copy() def undo(self, widget, data=None): self.TextEditor.undo() def redo(self, widget, data=None): self.TextEditor.redo() def set_italic(self, widget, data=None): """Ctrl + I""" self.FormatShortcuts.italic() def set_bold(self, widget, data=None): """Ctrl + B""" self.FormatShortcuts.bold() def insert_horizontal_rule(self, widget, data=None): """Ctrl + R""" self.FormatShortcuts.rule() def insert_unordered_list_item(self, widget, data=None): """Ctrl + U""" self.FormatShortcuts.unordered_list_item() def insert_ordered_list(self, widget, data=None): """CTRL + O""" self.FormatShortcuts.ordered_list_item() def insert_heading(self, widget, data=None): """CTRL + H""" self.FormatShortcuts.heading() def set_focusmode(self, widget, data=None): if widget.get_active(): self.init_typewriter() self.MarkupBuffer.focusmode_highlight() self.focusmode = True self.TextEditor.grab_focus() if self.spellcheck != False: self.SpellChecker._misspelled.set_property('underline', 0) else: self.remove_typewriter() self.focusmode = False self.TextBuffer.remove_tag(self.MarkupBuffer.grayfont, self.TextBuffer.get_start_iter(), self.TextBuffer.get_end_iter()) self.TextBuffer.remove_tag(self.MarkupBuffer.blackfont, self.TextBuffer.get_start_iter(), self.TextBuffer.get_end_iter()) self.MarkupBuffer.markup_buffer(1) self.TextEditor.grab_focus() self.update_line_and_char_count() if self.spellcheck != False: self.SpellChecker._misspelled.set_property('underline', 4) def window_resize(self, widget, data=None): # To calc padding top / bottom self.window_height = widget.get_size()[1] # Calculate left / right margin lm = (widget.get_size()[0] - 1050) / 2 self.TextEditor.set_left_margin(lm) self.TextEditor.set_right_margin(lm) self.MarkupBuffer.recalculate(lm) if self.focusmode: self.remove_typewriter() self.init_typewriter() def window_close(self, widget, data=None): return True def save_document(self, widget, data=None): if self.filename: logger.info("saving") filename = self.filename f = codecs.open(filename, encoding="utf-8", mode='w') f.write(self.get_text()) f.close() if self.did_change: self.did_change = False title = self.get_title() self.set_title(title[2:]) return Gtk.ResponseType.OK else: 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, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_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 f = codecs.open(filename, encoding="utf-8", mode='w') f.write(self.get_text()) f.close() self.filename = filename self.set_title(os.path.basename(filename) + self.title_end) self.did_change = False filechooser.destroy() return response elif response == Gtk.ResponseType.CANCEL: filechooser.destroy() return response def save_document_as(self, widget, data=None): filechooser = Gtk.FileChooserDialog( "Save your File", self, Gtk.FileChooserAction.SAVE, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_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 f = codecs.open(filename, encoding="utf-8", mode='w') f.write(self.get_text()) f.close() self.filename = filename self.set_title(os.path.basename(filename) + self.title_end) try: self.recent_manager.add_item(filename) except: pass filechooser.destroy() self.did_change = False elif response == Gtk.ResponseType.CANCEL: filechooser.destroy() def export(self, export_type="html"): filechooser = Gtk.FileChooserDialog( "Export as %s" % export_type.upper(), self, Gtk.FileChooserAction.SAVE, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_SAVE, Gtk.ResponseType.OK) ) filechooser.set_do_overwrite_confirmation(True) if self.filename: filechooser.set_filename(self.filename[:-2] + export_type.lower()) response = filechooser.run() if response == Gtk.ResponseType.OK: filename = filechooser.get_filename() if filename.endswith("." + export_type): filename = filename[:-len(export_type)-1] filechooser.destroy() else: filechooser.destroy() return # Converting text to bytes for python 3 text = bytes(self.get_text(), "utf-8") output_dir = os.path.abspath(os.path.join(filename, os.path.pardir)) basename = os.path.basename(filename) args = ['pandoc', '--from=markdown', '--smart'] if export_type == "pdf": args.append("-o%s.pdf" % basename) elif export_type == "odt": args.append("-o%s.odt" % basename) elif export_type == "html": css = helpers.get_media_file('uberwriter.css') args.append("-c%s" % css) args.append("-o%s.html" % basename) args.append("--mathjax") p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, cwd=output_dir) output = p.communicate(text)[0] return filename def export_as_odt(self, widget, data=None): self.export("odt") def export_as_html(self, widget, data=None): self.export("html") def export_as_pdf(self, widget, data=None): if self.texlive_installed == False and APT_ENABLED: try: cache = apt.Cache() inst = cache["texlive"].is_installed except: inst = True if inst == False: dialog = Gtk.MessageDialog(self, Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.INFO, None, _("You can not export to PDF.") ) dialog.format_secondary_markup(_("Please install <a href=\"apt:texlive\">texlive</a> from the software center.")) response = dialog.run() return else: self.texlive_installed = True self.export("pdf") def copy_html_to_clipboard(self, widget, date=None): """Copies only html without headers etc. to Clipboard""" args = ['pandoc', '--from=markdown', '--smart', '-thtml'] p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE) text = bytes(self.get_text(), "utf-8") output = p.communicate(text)[0] cb = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) cb.set_text(output.decode("utf-8"), -1) cb.store() def open_document(self, widget): 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, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_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): if self.did_change and len(self.get_text()): dialog = Gtk.MessageDialog(self, Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.WARNING, 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).grab_focus() dialog.set_title(_('Unsaved changes')) dialog.set_default_size(200, 150) response = dialog.run() if response == Gtk.ResponseType.YES: title = self.get_title() if self.save_document(widget = None) == Gtk.ResponseType.CANCEL: dialog.destroy() return self.check_change() else: dialog.destroy() return response elif response == Gtk.ResponseType.CANCEL: dialog.destroy() return response elif response == Gtk.ResponseType.NO: dialog.destroy() return response def new_document(self, widget): if self.check_change() == Gtk.ResponseType.CANCEL: return else: self.TextBuffer.set_text('') self.TextEditor.undos = [] self.TextEditor.redos = [] self.did_change = False self.filename = None self.set_title("New File" + self.title_end) def menu_activate_focusmode(self, widget): self.focusmode_button.emit('activate') def menu_activate_fullscreen(self, widget): self.fullscreen_button.emit('activate') def menu_activate_preview(self, widget): self.preview_button.emit('activate') # Not added as menu button as of now. Standard is typewriter active. def toggle_typewriter(self, widget, data=None): self.typewriter_active = widget.get_active() def toggle_spellcheck(self, widget, data=None): if self.spellcheck: if widget.get_active(): self.SpellChecker.enable() else: self.SpellChecker.disable() elif widget.get_active(): try: self.SpellChecker = SpellChecker(self.TextEditor, locale.getdefaultlocale()[0], collapse=False) self.spellcheck = True except: self.SpellChecker = None self.spellcheck = False dialog = Gtk.MessageDialog(self, Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.INFO, 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 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'): text = "![Insert image title here](%s)" % uri ll = 2 lr = 23 else: text = "[Insert link title here](%s)" % uri ll = 1 lr = 22 self.TextBuffer.insert_at_cursor(text) insert_mark = self.TextBuffer.get_insert() selection_bound = self.TextBuffer.get_selection_bound() cursor_iter = self.TextBuffer.get_iter_at_mark(insert_mark) cursor_iter.backward_chars(len(text) - ll) self.TextBuffer.move_mark(insert_mark, cursor_iter) cursor_iter.forward_chars(lr) self.TextBuffer.move_mark(selection_bound, cursor_iter) elif info == 2: # Text target self.TextBuffer.insert_at_cursor(data.get_text()) self.present() def toggle_preview(self, widget, data=None): if widget.get_active(): # Insert a tag with ID to scroll to self.TextBuffer.insert_at_cursor('<span id="scroll_mark"></span>') args = ['pandoc', '--from=markdown', '--smart', '-thtml', '--mathjax', '-c', helpers.get_media_file('uberwriter.css')] p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE) text = bytes(self.get_text(), "utf-8") output = p.communicate(text)[0] # Load in Webview and scroll to #ID self.webview = WebKit.WebView() self.webview.load_html_string(output.decode("utf-8"), 'file://localhost/' + '#scroll_mark') # Delete the cursor-scroll mark again cursor_iter = self.TextBuffer.get_iter_at_mark(self.TextBuffer.get_insert()) begin_del = cursor_iter.copy() begin_del.backward_chars(30) self.TextBuffer.delete(begin_del, cursor_iter) self.ScrolledWindow.remove(self.TextEditor) self.ScrolledWindow.add(self.webview) self.webview.show() # Making the background white white_background = helpers.get_media_path('white.png') surface = cairo.ImageSurface.create_from_png(white_background) self.background_pattern = cairo.SurfacePattern(surface) self.background_pattern.set_extend(cairo.EXTEND_REPEAT) # This saying that all links will be opened in default browser, but local files are opened in appropriate apps: self.webview.connect("navigation-requested", self.on_click_link) else: self.ScrolledWindow.remove(self.webview) self.webview.destroy() self.ScrolledWindow.add(self.TextEditor) self.TextEditor.show() surface = cairo.ImageSurface.create_from_png(self.background_image) self.background_pattern = cairo.SurfacePattern(surface) self.background_pattern.set_extend(cairo.EXTEND_REPEAT) self.queue_draw() def on_click_link(self, view, frame, req, data=None): # This provide ability for self.webview to open links in default browser webbrowser.open(req.get_uri()) return True # that string is god-damn-important: without it link will be opened in default browser AND also in self.webview def dark_mode_toggled(self, widget, data=None): # Save state for saving settings later self.dark_mode = widget.get_active() if self.dark_mode: # Dark Mode is on css = open(helpers.get_media_path('style_dark.css'), 'rb') css_data = css.read() css.close() self.style_provider.load_from_data(css_data) self.background_image = helpers.get_media_path('bg_dark.png') self.MarkupBuffer.dark_mode(True) else: # Dark mode off css = open(helpers.get_media_path('style.css'), 'rb') css_data = css.read() css.close() self.style_provider.load_from_data(css_data) self.background_image = helpers.get_media_path('bg_light.png') self.MarkupBuffer.dark_mode(False) surface = cairo.ImageSurface.create_from_png(self.background_image) self.background_pattern = cairo.SurfacePattern(surface) self.background_pattern.set_extend(cairo.EXTEND_REPEAT) Gtk.StyleContext.add_provider_for_screen( Gdk.Screen.get_default(), self.style_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION ) # Redraw contents of window (self) self.queue_draw() def load_file(self, filename = None): """Open File from command line or open / open recent etc.""" if filename: if filename.startswith('file://'): filename = filename[7:] filename = urllib.parse.unquote_plus(filename) try: self.preview_button.set_active(False) self.filename = filename f = codecs.open(filename, encoding="utf-8", mode='r') self.TextBuffer.set_text(f.read()) f.close() self.MarkupBuffer.markup_buffer(0) self.set_title(os.path.basename(filename) + self.title_end) self.TextEditor.undos = [] self.TextEditor.redos = [] except Exception as e: logger.warning("Error Reading File: %r" % e) self.did_change = False else: logger.warning("No File arg") def draw_bg(self, widget, context): context.set_source(self.background_pattern) context.paint() # Help Menu def open_launchpad_translation(self, widget, data = None): webbrowser.open("https://translations.launchpad.net/uberwriter") def open_launchpad_help(self, widget, data = None): webbrowser.open("https://answers.launchpad.net/uberwriter") def open_pandoc_markdown(self, widget, data=None): webbrowser.open("http://johnmacfarlane.net/pandoc/README.html#pandocs-markdown") def open_uberwriter_markdown(self, widget, data=None): self.load_file(helpers.get_media_file('uberwriter_markdown.md')) def open_advanced_export(self, widget, data=None): if self.UberwriterAdvancedExportDialog is not None: advexp = self.UberwriterAdvancedExportDialog() # pylint: disable= response = advexp.run() if response == 1: advexp.advanced_export(bytes(self.get_text(), "utf-8")) advexp.destroy() def open_recent(self, widget, data=None): if data: if self.check_change() == Gtk.ResponseType.CANCEL: return else: self.load_file(data) def generate_recent_files_menu(self, parent_menu): # Recent file filter self.recent_manager = Gtk.RecentManager.get_default() self.recent_files_menu = Gtk.RecentChooserMenu.new_for_manager(self.recent_manager) self.recent_files_menu.set_sort_type(Gtk.RecentSortType.MRU) recent_filter = Gtk.RecentFilter.new() recent_filter.add_mime_type('text/x-markdown') self.recent_files_menu.set_filter(recent_filter) menu = Gtk.Menu.new() for entry in self.recent_files_menu.get_items(): if entry.exists(): item = Gtk.MenuItem.new_with_label(entry.get_display_name()) item.connect('activate', self.open_recent, entry.get_uri()) menu.append(item) item.show() menu.show() parent_menu.set_submenu(menu) parent_menu.show() def poll_for_motion(self): if (self.was_motion == False and self.status_bar_visible and self.buffer_modified_for_status_bar): self.status_bar.set_state_flags(Gtk.StateFlags.INSENSITIVE, True) self.status_bar_visible = False self.buffer_modified_for_status_bar = False return False self.was_motion = False return True def on_motion_notify(self, widget, data=None): self.was_motion = True if self.status_bar_visible == False: 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) GObject.timeout_add(3000, self.poll_for_motion) def move_popup(self, widget, data=None): pass def finish_initializing(self, builder): # pylint: disable=E1002 """Set up the main window""" super(UberwriterWindow, self).finish_initializing(builder) self.AboutDialog = AboutUberwriterDialog self.UberwriterAdvancedExportDialog = UberwriterAdvancedExportDialog # Code for other initialization actions should be added here. # Texlive checker self.texlive_installed = False # Draw background self.background_image = helpers.get_media_path('bg_light.png') self.connect('draw', self.draw_bg) self.set_name('UberwriterWindow') self.title_end = " – UberWriter" self.set_title("New File" + self.title_end) # Drag and drop self.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.drag_dest_set_target_list(self.target_list) self.focusmode = False self.word_count = builder.get_object('word_count') self.char_count = builder.get_object('char_count') self.menubar = builder.get_object('menubar1') # Wire up buttons self.fullscreen_button = builder.get_object('fullscreen_toggle') self.focusmode_button = builder.get_object('focus_toggle') self.preview_button = builder.get_object('preview_toggle') self.fullscreen_button.set_name('fullscreen_toggle') self.focusmode_button.set_name('focus_toggle') self.preview_button.set_name('preview_toggle') # Setup status bar hide after 3 seconds self.status_bar = builder.get_object('status_bar_box') self.status_bar.set_name('status_bar_box') self.status_bar_visible = True self.was_motion = True self.buffer_modified_for_status_bar = False self.connect("motion-notify-event", self.on_motion_notify) GObject.timeout_add(3000, self.poll_for_motion) self.accel_group = Gtk.AccelGroup() self.add_accel_group(self.accel_group) # Setup light background surface = cairo.ImageSurface.create_from_png(self.background_image) self.background_pattern = cairo.SurfacePattern(surface) self.background_pattern.set_extend(cairo.EXTEND_REPEAT) self.TextEditor = TextEditor() base_leftmargin = 100 self.TextEditor.set_left_margin(base_leftmargin) self.TextEditor.set_left_margin(40) self.TextEditor.set_wrap_mode(Gtk.WrapMode.WORD) self.TextEditor.show() self.ScrolledWindow = builder.get_object('editor_scrolledwindow') self.ScrolledWindow.add(self.TextEditor) self.PreviewPane = builder.get_object('preview_scrolledwindow') pangoFont = Pango.FontDescription("Ubuntu Mono 15px") self.TextEditor.modify_font(pangoFont) self.TextEditor.set_margin_top(38) self.TextEditor.set_margin_bottom(16) self.TextEditor.set_pixels_above_lines(4) self.TextEditor.set_pixels_below_lines(4) self.TextEditor.set_pixels_inside_wrap(8) tab_array = Pango.TabArray.new(1, True) tab_array.set_tab(0, Pango.TabAlign.LEFT, 20) self.TextEditor.set_tabs(tab_array) self.TextBuffer = self.TextEditor.get_buffer() self.TextBuffer.set_text('') # Init Window height for top/bottom padding self.window_height = self.get_size()[1] self.text_change_event = self.TextBuffer.connect('changed', self.text_changed) self.TextEditor.connect('move-cursor', self.cursor_moved) # Init file name with None self.filename = None self.generate_recent_files_menu(self.builder.get_object('recent')) self.style_provider = Gtk.CssProvider() css = open(helpers.get_media_path('style.css'), 'rb') css_data = css.read() css.close() self.style_provider.load_from_data(css_data) Gtk.StyleContext.add_provider_for_screen( Gdk.Screen.get_default(), self.style_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION ) # Still needed. self.fflines = 0 # Markup and Shortcuts for the TextBuffer self.MarkupBuffer = MarkupBuffer(self, self.TextBuffer, base_leftmargin) self.MarkupBuffer.markup_buffer() self.FormatShortcuts = FormatShortcuts(self.TextBuffer, self.TextEditor) # Scrolling -> Dark or not? self.textchange = False self.scroll_count = 0 self.TextBuffer.connect('mark-set', self.mark_set) self.TextEditor.drag_dest_unset() # Events to preserve margin. (To be deleted.) self.TextEditor.connect('delete-from-cursor', self.delete_from_cursor) self.TextEditor.connect('backspace', self.backspace) self.TextBuffer.connect('paste-done', self.paste_done) # self.connect('key-press-event', self.alt_mod) # Events for Typewriter mode self.TextBuffer.connect_after('mark-set', self.after_mark_set) self.TextBuffer.connect_after('changed', self.after_modify_text) self.TextEditor.connect_after('move-cursor', self.after_cursor_moved) self.TextEditor.connect_after('insert-at-cursor', self.after_insert_at_cursor) # Setting up inline preview self.InlinePreview = UberwriterInlinePreview(self.TextEditor, self.TextBuffer) # Vertical scrolling self.vadjustment = self.TextEditor.get_vadjustment() self.vadjustment.connect('value-changed', self.scrolled) # Setting up spellcheck try: self.SpellChecker = SpellChecker(self.TextEditor, locale.getdefaultlocale()[0], collapse=False) self.spellcheck = True except: self.SpellChecker = None self.spellcheck = False builder.get_object("disable_spellcheck").set_active(False) if self.spellcheck: self.SpellChecker.append_filter('[#*]+', SpellChecker.FILTER_WORD) self.did_change = False # Window resize self.connect("configure-event", self.window_resize) self.connect("delete-event", self.on_delete_called) self.load_settings(builder) 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 else: 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. self.window_close(widget) self.save_settings() Gtk.main_quit() def save_settings(self): if not os.path.exists(CONFIG_PATH): try: os.makedirs(CONFIG_PATH) except Exception as e: log.debug("Failed to make uberwriter config path in ~/.config/uberwriter. Error: %r" % e) try: settings = dict() settings["dark_mode"] = self.dark_mode settings["spellcheck"] = self.SpellChecker.enabled f = open(CONFIG_PATH + "settings.pickle", "wb+") pickle.dump(settings, f) f.close() logger.debug("Saved settings: %r" % settings) except Exception as e: logger.debug("Error writing settings file to disk. Error: %r" % e) def load_settings(self, builder): dark_mode_button = builder.get_object("dark_mode") spellcheck_button = builder.get_object("disable_spellcheck") try: f = open(CONFIG_PATH + "settings.pickle", "rb") settings = pickle.load(f) f.close() self.dark_mode = settings['dark_mode'] dark_mode_button.set_active(settings['dark_mode']) spellcheck_button.set_active(settings['spellcheck']) logger.debug("loaded settings: %r" % settings) except Exception as e: logger.debug("(First Run?) Error loading settings from home dir. Error: %r", e) return 1
def finish_initializing(self, builder): # pylint: disable=E1002 """Set up the main window""" super(UberwriterWindow, self).finish_initializing(builder) self.AboutDialog = AboutUberwriterDialog self.UberwriterAdvancedExportDialog = UberwriterAdvancedExportDialog # Code for other initialization actions should be added here. # Texlive checker self.texlive_installed = False # Draw background self.background_image = helpers.get_media_path('bg_light.png') self.connect('draw', self.draw_bg) self.set_name('UberwriterWindow') self.title_end = " – UberWriter" self.set_title("New File" + self.title_end) # Drag and drop self.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.drag_dest_set_target_list(self.target_list) self.focusmode = False self.word_count = builder.get_object('word_count') self.char_count = builder.get_object('char_count') self.menubar = builder.get_object('menubar1') # Wire up buttons self.fullscreen_button = builder.get_object('fullscreen_toggle') self.focusmode_button = builder.get_object('focus_toggle') self.preview_button = builder.get_object('preview_toggle') self.fullscreen_button.set_name('fullscreen_toggle') self.focusmode_button.set_name('focus_toggle') self.preview_button.set_name('preview_toggle') # Setup status bar hide after 3 seconds self.status_bar = builder.get_object('status_bar_box') self.status_bar.set_name('status_bar_box') self.status_bar_visible = True self.was_motion = True self.buffer_modified_for_status_bar = False self.connect("motion-notify-event", self.on_motion_notify) GObject.timeout_add(3000, self.poll_for_motion) self.accel_group = Gtk.AccelGroup() self.add_accel_group(self.accel_group) # Setup light background surface = cairo.ImageSurface.create_from_png(self.background_image) self.background_pattern = cairo.SurfacePattern(surface) self.background_pattern.set_extend(cairo.EXTEND_REPEAT) self.TextEditor = TextEditor() base_leftmargin = 100 self.TextEditor.set_left_margin(base_leftmargin) self.TextEditor.set_left_margin(40) self.TextEditor.set_wrap_mode(Gtk.WrapMode.WORD) self.TextEditor.show() self.ScrolledWindow = builder.get_object('editor_scrolledwindow') self.ScrolledWindow.add(self.TextEditor) self.PreviewPane = builder.get_object('preview_scrolledwindow') pangoFont = Pango.FontDescription("Ubuntu Mono 15px") self.TextEditor.modify_font(pangoFont) self.TextEditor.set_margin_top(38) self.TextEditor.set_margin_bottom(16) self.TextEditor.set_pixels_above_lines(4) self.TextEditor.set_pixels_below_lines(4) self.TextEditor.set_pixels_inside_wrap(8) tab_array = Pango.TabArray.new(1, True) tab_array.set_tab(0, Pango.TabAlign.LEFT, 20) self.TextEditor.set_tabs(tab_array) self.TextBuffer = self.TextEditor.get_buffer() self.TextBuffer.set_text('') # Init Window height for top/bottom padding self.window_height = self.get_size()[1] self.text_change_event = self.TextBuffer.connect('changed', self.text_changed) self.TextEditor.connect('move-cursor', self.cursor_moved) # Init file name with None self.filename = None self.generate_recent_files_menu(self.builder.get_object('recent')) self.style_provider = Gtk.CssProvider() css = open(helpers.get_media_path('style.css'), 'rb') css_data = css.read() css.close() self.style_provider.load_from_data(css_data) Gtk.StyleContext.add_provider_for_screen( Gdk.Screen.get_default(), self.style_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION ) # Still needed. self.fflines = 0 # Markup and Shortcuts for the TextBuffer self.MarkupBuffer = MarkupBuffer(self, self.TextBuffer, base_leftmargin) self.MarkupBuffer.markup_buffer() self.FormatShortcuts = FormatShortcuts(self.TextBuffer, self.TextEditor) # Scrolling -> Dark or not? self.textchange = False self.scroll_count = 0 self.TextBuffer.connect('mark-set', self.mark_set) self.TextEditor.drag_dest_unset() # Events to preserve margin. (To be deleted.) self.TextEditor.connect('delete-from-cursor', self.delete_from_cursor) self.TextEditor.connect('backspace', self.backspace) self.TextBuffer.connect('paste-done', self.paste_done) # self.connect('key-press-event', self.alt_mod) # Events for Typewriter mode self.TextBuffer.connect_after('mark-set', self.after_mark_set) self.TextBuffer.connect_after('changed', self.after_modify_text) self.TextEditor.connect_after('move-cursor', self.after_cursor_moved) self.TextEditor.connect_after('insert-at-cursor', self.after_insert_at_cursor) # Setting up inline preview self.InlinePreview = UberwriterInlinePreview(self.TextEditor, self.TextBuffer) # Vertical scrolling self.vadjustment = self.TextEditor.get_vadjustment() self.vadjustment.connect('value-changed', self.scrolled) # Setting up spellcheck try: self.SpellChecker = SpellChecker(self.TextEditor, locale.getdefaultlocale()[0], collapse=False) self.spellcheck = True except: self.SpellChecker = None self.spellcheck = False builder.get_object("disable_spellcheck").set_active(False) if self.spellcheck: self.SpellChecker.append_filter('[#*]+', SpellChecker.FILTER_WORD) self.did_change = False # Window resize self.connect("configure-event", self.window_resize) self.connect("delete-event", self.on_delete_called) self.load_settings(builder)
import sys from os.path import join, dirname sys.path.append(join(dirname(__file__), '../src/')) import locale import gtk from gtkspellcheck import SpellChecker if __name__ == '__main__': def quit(*args): gtk.main_quit() window = gtk.Window(gtk.WINDOW_TOPLEVEL) window.set_title('PyGtkSpellCheck Example') view = gtk.TextView() spellchecker = SpellChecker(view, locale.getdefaultlocale()[0], collapse=False) for code, name in spellchecker.languages: print('code: %5s, language: %s' % (code, name)) window.set_default_size(600, 400) window.add(view) window.show_all() window.connect('delete-event', quit) gtk.main()