def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) settings.get_section('General').connect('pygments_style', self._set_style, run_now=True) def on_destroy(event): settings.get_section('General').disconnect('pygments_style', self._set_style) self.bind('<Destroy>', on_destroy, add=True)
def save(self): """Save the file to the current :attr:`path`. This calls :meth:`save_as` if :attr:`path` is None, and returns False if the user cancels the save as dialog. None is returned on errors, and True is returned in all other cases. In other words, this returns True if saving succeeded. .. seealso:: The :virtevt:`Save` event. """ if self.path is None: return self.save_as() self.event_generate('<<Save>>') encoding = settings.get_section('General')['encoding'] try: with utils.backup_open(self.path, 'w', encoding=encoding) as f: for chunk in self.textwidget.iter_chunks(): f.write(chunk) except (OSError, UnicodeError) as e: log.exception("saving '%s' failed", self.path) utils.errordialog( type(e).__name__, "Saving failed!", traceback.format_exc()) return None self.mark_saved() return True
def setup(): # TODO: loading the styles takes a long time on startup... try to # make it asynchronous without writing too complicated code? config = settings.get_section('General') actions.add_choice("Color Styles", sorted(pygments.styles.get_all_styles()), var=config.get_var('pygments_style'))
def change_font_size(how): config = settings.get_section('General') if how == 'reset': config.reset('font_size') else: try: config['font_size'] += (1 if how == 'bigger' else -1) except settings.InvalidValue: pass
def _guess_filetype(self): if self.path is None: name = settings.get_section('File Types')['default_filetype'] self.filetype = filetypes.get_filetype_by_name(name) else: # FIXME: this may read the shebang from the file, but the file # might not be saved yet because save_as() sets self.path # before saving, and that's when this runs self.filetype = filetypes.guess_filetype(self.path)
def on_wheel(self, direction): config = settings.get_section('General') if direction == 'reset': config.reset('font_size') return try: config['font_size'] += (1 if direction == 'up' else -1) except settings.InvalidValue: pass
def change_font_size(how): # TODO: i think there is similar code in a couple other places too config = settings.get_section('General') if how == 'reset': config.reset('font_size') else: try: config['font_size'] += (1 if how == 'bigger' else -1) except settings.InvalidValue: pass
def _get_hash(self): # superstitious omg-optimization config = settings.get_section('General') encoding = config['encoding'] result = hashlib.md5() for chunk in self.textwidget.iter_chunks(): chunk = chunk.encode(encoding, errors='replace') result.update(chunk) # hash objects don't define an __eq__ so we need to use a string # representation of the hash return result.hexdigest()
def open_file(cls, manager, path): """Read a file and return a new FileTab object. Use this constructor if you want to open an existing file from a path and let the user edit it. :exc:`UnicodeError` or :exc:`OSError` is raised if reading the file fails. """ config = settings.get_section('General') with open(path, 'r', encoding=config['encoding']) as file: content = file.read() return cls(manager, content, path)
def setup(): config = settings.get_section('General') styles = [] thread = threading.Thread(target=load_styles_to_list, args=[styles]) thread.daemon = True # i don't care wtf happens to this thread.start() def check_if_it_finished(): if thread.is_alive(): get_main_window().after(200, check_if_it_finished) else: actions.add_choice("Color Styles", styles, var=config.get_var('pygments_style')) get_main_window().after(200, check_if_it_finished)
# see also: https://github.com/RedFantom/ttkthemes # TODO: capitalize theme names in menu items? import functools import tkinter from porcupine import actions, settings import ttkthemes config = settings.get_section('General') def on_theme_changed(style, theme_name): try: style.set_theme(theme_name) except tkinter.TclError as e: raise settings.InvalidValue(str(e)) from None def setup(): style = ttkthemes.ThemedStyle() # https://github.com/RedFantom/ttkthemes/issues/6 # this does what style.theme_use() should do default_theme = style.tk.eval('return $ttk::currentTheme') config.add_option('ttk_theme', default_theme, reset=False) config.connect('ttk_theme', functools.partial(on_theme_changed, style), run_now=True) actions.add_choice("Ttk Themes", sorted(style.get_themes()), var=config.get_var('ttk_theme'))
def _setup_actions(): def new_file(): _tab_manager.add_tab(tabs.FileTab(_tab_manager)) def open_files(): for path in _dialogs.open_files(): try: tab = tabs.FileTab.open_file(_tab_manager, path) except (UnicodeError, OSError) as e: log.exception("opening '%s' failed", path) utils.errordialog( type(e).__name__, "Opening failed!", traceback.format_exc()) continue _tab_manager.add_tab(tab) def close_current_tab(): if _tab_manager.current_tab.can_be_closed(): _tab_manager.close_tab(_tab_manager.current_tab) add_action(new_file, "File/New File", ("Ctrl+N", '<Control-n>')) add_action(open_files, "File/Open", ("Ctrl+O", '<Control-o>')) add_action((lambda: _tab_manager.current_tab.save()), "File/Save", ("Ctrl+S", '<Control-s>'), tabtypes=[tabs.FileTab]) add_action((lambda: _tab_manager.current_tab.save_as()), "File/Save As...", ("Ctrl+Shift+S", '<Control-S>'), tabtypes=[tabs.FileTab]) menubar.get_menu("File").add_separator() # TODO: disable File/Quit when there are tabs, it's too easy to hit # Ctrl+Q accidentally add_action(close_current_tab, "File/Close", ("Ctrl+W", '<Control-w>'), tabtypes=[tabs.Tab]) add_action(quit, "File/Quit", ("Ctrl+Q", '<Control-q>')) def textmethod(attribute): def result(): textwidget = _tab_manager.current_tab.textwidget method = getattr(textwidget, attribute) method() return result # FIXME: bind these in a text widget only, not globally add_action(textmethod('undo'), "Edit/Undo", ("Ctrl+Z", '<Control-z>'), tabtypes=[tabs.FileTab]) add_action(textmethod('redo'), "Edit/Redo", ("Ctrl+Y", '<Control-y>'), tabtypes=[tabs.FileTab]) add_action(textmethod('cut'), "Edit/Cut", ("Ctrl+X", '<Control-x>'), tabtypes=[tabs.FileTab]) add_action(textmethod('copy'), "Edit/Copy", ("Ctrl+C", '<Control-c>'), tabtypes=[tabs.FileTab]) add_action(textmethod('paste'), "Edit/Paste", ("Ctrl+V", '<Control-v>'), tabtypes=[tabs.FileTab]) add_action(textmethod('select_all'), "Edit/Select All", ("Ctrl+A", '<Control-a>'), tabtypes=[tabs.FileTab]) menubar.get_menu("Edit").add_separator() # FIXME: this separator thing is a mess :( menubar.get_menu("Edit").add_separator() add_action( functools.partial(settings.show_dialog, porcupine.get_main_window()), "Edit/Porcupine Settings...") menubar.get_menu("Color Themes") # this goes between Edit and View # the font size stuff are bound by the textwidget itself, that's why # there are Nones everywhere add_action((lambda: _tab_manager.current_tab.textwidget.on_wheel('up')), "View/Bigger Font", ("Ctrl+Plus", None), tabtypes=[tabs.FileTab]) add_action((lambda: _tab_manager.current_tab.textwidget.on_wheel('down')), "View/Smaller Font", ("Ctrl+Minus", None), tabtypes=[tabs.FileTab]) add_action((lambda: _tab_manager.current_tab.textwidget.on_wheel('reset')), "View/Reset Font Size", ("Ctrl+Zero", None), tabtypes=[tabs.FileTab]) menubar.get_menu("View").add_separator() def add_link(menupath, url): add_action(functools.partial(webbrowser.open, url), menupath) # TODO: an about dialog that shows porcupine version, Python version # and where porcupine is installed # TODO: porcupine starring button add_link("Help/Free help chat", "http://webchat.freenode.net/?channels=%23%23learnpython") add_link("Help/My Python tutorial", "https://github.com/Akuli/python-tutorial/blob/master/README.md") add_link("Help/Official Python documentation", "https://docs.python.org/") menubar.get_menu("Help").add_separator() add_link("Help/Porcupine Wiki", "https://github.com/Akuli/porcupine/wiki") add_link("Help/Report a problem or request a feature", "https://github.com/Akuli/porcupine/issues/new") add_link("Help/Read Porcupine's code on GitHub", "https://github.com/Akuli/porcupine/tree/master/porcupine") # TODO: loading the styles takes a long time on startup... try to # make it asynchronous without writing too complicated code? config = settings.get_section('General') for name in sorted(pygments.styles.get_all_styles()): if name.islower(): label = name.replace('-', ' ').replace('_', ' ').title() else: label = name options = { 'label': label, 'value': name, 'variable': config.get_var('pygments_style'), } style = pygments.styles.get_style_by_name(name) bg = style.background_color # styles have a style_for_token() method, but only iterating # is documented :( http://pygments.org/docs/formatterdevelopment/ # i'm using iter() to make sure that dict() really treats # the style as an iterable of pairs instead of some other # metaprogramming fanciness fg = None style_infos = dict(iter(style)) for token in [pygments.token.String, pygments.token.Text]: if style_infos[token]['color'] is not None: fg = '#' + style_infos[token]['color'] break if fg is None: # do like textwidget.ThemedText._set_style does fg = (getattr(style, 'default_style', '') or utils.invert_color(bg)) options['foreground'] = options['activebackground'] = fg options['background'] = options['activeforeground'] = bg menubar.get_menu("Color Themes").add_radiobutton(**options)
def on_destroy(event): settings.get_section('General').disconnect('pygments_style', self._set_style)