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)
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 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().encode("utf-8") #text = unicode(text) text = text.decode("utf-8") 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 f = codecs.open(filename, encoding="utf-8", mode='w') f.write(self.get_text().decode("utf-8") ) 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().decode("utf-8") ) 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().decode("utf-8") ) 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 text = self.get_text() text = 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: 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 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() output = p.communicate(text)[0] cb = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) cb.set_text(output, -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') # 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.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.unquote_plus(filename) self.filename = filename try: 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: logger.warning("Error Reading File") 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()