コード例 #1
0
def setup() -> None:
    # load_filetypes() got already called in setup_argument_parser()
    get_tab_manager().add_tab_callback(on_new_tab)

    settings.add_option("default_filetype", "Python")
    settings.add_combobox(
        "default_filetype",
        "Default filetype for new files:",
        values=sorted(filetypes.keys(), key=str.casefold),
    )
    menubar.add_config_file_button(
        pathlib.Path(dirs.user_config_dir) / "filetypes.toml")
    set_filedialog_kwargs()

    for name, filetype in filetypes.items():
        safe_name = name.replace("/", "\N{division slash}")  # lol
        assert "/" not in safe_name
        menubar.get_menu("Filetypes").add_command(label=safe_name,
                                                  command=partial(
                                                      menu_callback, filetype))
        menubar.set_enabled_based_on_tab(
            f"Filetypes/{safe_name}",
            (lambda tab: isinstance(tab, tabs.FileTab)))

    new_file_filetypes = get_parsed_args().new_file or [
    ]  # argparse can give None
    for filetype in new_file_filetypes:
        tab = tabs.FileTab(get_tab_manager())
        get_tab_manager().add_tab(tab)  # sets default filetype
        apply_filetype_to_tab(tab, filetype)  # sets correct filetype
コード例 #2
0
    def check_if_it_finished() -> None:
        if thread.is_alive():
            get_main_window().after(200, check_if_it_finished)
            return

        var = tkinter.StringVar(value=settings.get("pygments_style", str))

        def settings2var(event: tkinter.Event[tkinter.Misc]) -> None:
            var.set(settings.get("pygments_style", str))

        def var2settings(*junk: str) -> None:
            settings.set_("pygments_style", var.get())

        # this doesn't recurse infinitely because <<SettingChanged:bla>>
        # gets generated only when the setting actually changes
        get_tab_manager().bind("<<SettingChanged:pygments_style>>",
                               settings2var,
                               add=True)
        var.trace_add("write", var2settings)

        for style_name in style_names:
            fg, bg = get_colors(style_name)
            menubar.get_menu("Color Styles").add_radiobutton(
                label=style_name,
                value=style_name,
                variable=var,
                foreground=fg,
                background=bg,
                # swapped colors
                activeforeground=bg,
                activebackground=fg,
            )
コード例 #3
0
ファイル: pastebin.py プロジェクト: bvadebruna/porcupine
def setup() -> None:
    for name in sorted(pastebins, key=str.casefold):
        menubar.get_menu("Share").add_command(label=name,
                                              command=functools.partial(
                                                  start_pasting, name))
        assert '/' not in name
        menubar.set_enabled_based_on_tab(
            f"Share/{name}", (lambda tab: isinstance(tab, tabs.FileTab)))
コード例 #4
0
def setup() -> None:
    # the action's binding feature cannot be used because then typing
    # a '#' outside the main text widget inserts a # to the main widget
    menubar.get_menu("Edit").add_command(
        label="Comment Block", command=comment_or_uncomment_in_current_tab)
    menubar.set_enabled_based_on_tab(
        "Edit/Comment Block", (lambda tab: isinstance(tab, tabs.FileTab)))
    utils.bind_with_data(get_tab_manager(), '<<NewTab>>', on_new_tab, add=True)
コード例 #5
0
ファイル: filetypes.py プロジェクト: bvadebruna/porcupine
def create_filetypes_menu() -> None:
    for name, filetype in filetypes.items():
        safe_name = name.replace('/', '\\')  # TODO: unicode slash character
        menubar.get_menu("Filetypes").add_command(label=safe_name,
                                                  command=partial(
                                                      menu_callback, filetype))
        menubar.set_enabled_based_on_tab(
            f"Filetypes/{safe_name}",
            (lambda tab: isinstance(tab, tabs.FileTab)))
コード例 #6
0
def setup() -> None:
    for klass in [DPaste, Termbin]:
        menubar.get_menu("Pastebin").add_command(label=klass.name,
                                                 command=partial(
                                                     start_pasting, klass))
        assert "/" not in klass.name
        menubar.set_enabled_based_on_tab(
            f"Pastebin/{klass.name}",
            (lambda tab: isinstance(tab, tabs.FileTab)))
コード例 #7
0
def setup() -> None:
    # FIXME: i think it's possible to run xterm in aqua? would that work here?
    if get_tab_manager().tk.call("tk", "windowingsystem") != "x11":
        # TODO: more noob-friendly "u have the wrong os lel" message?
        messagebox.showerror(
            "Unsupported windowing system", "Sorry, the terminal plugin only works on X11 :("
        )
        return

    menubar.get_menu("Tools").add_command(label="Terminal", command=start_xterm)
コード例 #8
0
ファイル: test_menubar.py プロジェクト: ThePhilgrim/porcupine
def test_virtual_events_calling_menu_callbacks():
    called = []
    menubar.get_menu("Foo").add_command(label="Bar", command=(lambda: called.append("bar")))
    menubar.get_menu("Foo").add_command(
        label="Baz", command=(lambda: called.append("baz")), state="disabled"
    )
    menubar.update_keyboard_shortcuts()
    get_main_window().update()
    get_main_window().event_generate("<<Menubar:Foo/Bar>>")
    get_main_window().event_generate("<<Menubar:Foo/Baz>>")
    assert called == ["bar"]
コード例 #9
0
ファイル: poppingtabs.py プロジェクト: ThePhilgrim/porcupine
def setup() -> None:
    manager = PopManager()
    get_tab_manager().bind("<Button1-Motion>", manager.on_drag, add=True)
    get_tab_manager().bind("<ButtonRelease-1>", manager.on_drop, add=True)
    menubar.get_menu("View").add_command(
        label="Pop Tab", command=manager.pop_next_to_current_window
    )
    menubar.set_enabled_based_on_tab(
        "View/Pop Tab", (lambda tab: tab is not None and tab.get_state() is not None)
    )

    open_tab_from_state_file()
コード例 #10
0
def test_virtual_events_calling_menu_callbacks(porcusession):
    called = []
    menubar.get_menu("Foo").add_command(label="Bar",
                                        command=(lambda: called.append('bar')))
    menubar.get_menu("Foo").add_command(label="Baz",
                                        command=(lambda: called.append('baz')),
                                        state='disabled')
    menubar.update_keyboard_shortcuts()
    get_main_window().update()
    get_main_window().event_generate('<<Menubar:Foo/Bar>>')
    get_main_window().event_generate('<<Menubar:Foo/Baz>>')
    assert called == ['bar']
コード例 #11
0
def setup() -> None:
    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')
    settings.add_option('ttk_theme', default_theme)

    var = tkinter.StringVar()
    var.trace_add('write', functools.partial(on_theme_changed, style, var))
    var.set(settings.get('ttk_theme', str))
    for name in sorted(style.get_themes()):
        menubar.get_menu("Ttk Themes").add_radiobutton(label=name, value=name, variable=var)
コード例 #12
0
def setup() -> None:
    # it's possible to run full X11 on a Mac, so this is better than
    # e.g. platform.system()
    # FIXME: i think it's possible to run xterm in aqua? would that
    # work here?
    if get_tab_manager().tk.call('tk', 'windowingsystem') != 'x11':
        # TODO: more noob-friendly "u have the wrong os lel" message?
        messagebox.showerror(
            "Unsupported windowing system",
            "Sorry, the terminal plugin only works on X11 :(")
        return

    menubar.get_menu("Tools").add_command(label="Terminal",
                                          command=start_xterm)
コード例 #13
0
ファイル: ttk_themes.py プロジェクト: ThePhilgrim/porcupine
def setup() -> None:
    style = ttkthemes.ThemedStyle()
    settings.add_option("ttk_theme", style.theme_use())

    var = tkinter.StringVar()
    for name in sorted(style.get_themes()):
        menubar.get_menu("Ttk Themes").add_radiobutton(label=name, value=name, variable=var)

    # Connect style and var
    var.trace_add("write", lambda *junk: style.set_theme(var.get()))

    # Connect var and settings
    get_main_window().bind(
        "<<SettingChanged:ttk_theme>>",
        lambda event: var.set(settings.get("ttk_theme", str)),
        add=True,
    )
    var.set(settings.get("ttk_theme", str))
    var.trace_add("write", lambda *junk: settings.set_("ttk_theme", var.get()))
コード例 #14
0
def add_action(callback,
               menupath=None,
               keyboard_shortcut=(None, None),
               tabtypes=(None, tabs.Tab)):
    """Add a keyboard binding and/or a menu item.

    If *menupath* is given, it will be split by the last ``/``. The
    first part will be used to get a menu with
    :meth:`porcupine.menubar.get_menu`, and the end will be used as the
    text of the menu item.

    Keyboard shortcuts can be added too. If given, *keyboard_shortcut*
    should be a two-tuple of a user-readable string and a tkinter
    keyboard binding; for example, ``('Ctrl+S', '<Control-s>')``.

    If defined, the *tabtypes* argument should be an iterable of
    :class:`porcupine.tabs.Tab` subclasses that item can work
    with. If you want to allow no tabs at all, add None to this list.
    The menuitem will be disabled and the binding won't do anything when
    the current tab is not of a compatible type.
    """
    tabtypes = tuple((
        # isinstance(None, type(None)) is True
        type(None) if cls is None else cls for cls in tabtypes))
    accelerator, binding = keyboard_shortcut

    if menupath is not None:
        menupath, menulabel = menupath.rsplit('/', 1)
        menu = menubar.get_menu(menupath)
        menu.add_command(label=menulabel,
                         command=callback,
                         accelerator=accelerator)
        menuindex = menu.index('end')

        def tab_changed(event):
            enable = isinstance(_tab_manager.current_tab, tabtypes)
            menu.entryconfig(menuindex,
                             state=('normal' if enable else 'disabled'))

        tab_changed(_tab_manager.current_tab)
        _tab_manager.bind('<<CurrentTabChanged>>', tab_changed, add=True)

    if binding is not None:
        # TODO: check if it's already bound
        def bind_callback(event):
            if isinstance(_tab_manager.current_tab, tabtypes):
                callback()
                # try to allow binding keys that are used for other
                # things by default
                return 'break'

        _main_window.bind(binding, bind_callback)
コード例 #15
0
ファイル: __init__.py プロジェクト: ThePhilgrim/porcupine
def setup() -> None:
    get_tab_manager().add_tab_callback(on_new_tab)

    menubar.get_menu("Run").add_command(label="Compile", command=partial(do_something, "compile"))
    menubar.get_menu("Run").add_command(label="Run", command=partial(do_something, "run"))
    menubar.get_menu("Run").add_command(
        label="Compile and Run", command=partial(do_something, "compilerun")
    )
    menubar.get_menu("Run").add_command(label="Lint", command=partial(do_something, "compilerun"))

    # TODO: disable the menu items when they don't correspond to actual commands
    for label in {"Compile", "Run", "Compile and Run", "Lint"}:
        menubar.set_enabled_based_on_tab(
            f"Run/{label}", (lambda tab: isinstance(tab, tabs.FileTab))
        )
コード例 #16
0
def setup() -> None:
    # TODO: add something for finding a file by typing its name?
    container = ttk.Frame(get_paned_window())

    # Packing order matters. The widget packed first is always visible.
    scrollbar = ttk.Scrollbar(container)
    scrollbar.pack(side="right", fill="y")
    tree = DirectoryTree(container)
    tree.pack(side="left", fill="both", expand=True)
    get_main_window().bind("<FocusIn>",
                           partial(on_any_widget_focused, tree),
                           add=True)

    tree.config(yscrollcommand=scrollbar.set)
    scrollbar.config(command=tree.yview)

    # Insert directory tree before tab manager
    get_paned_window().insert(get_tab_manager(),
                              container)  # type: ignore[no-untyped-call]

    get_tab_manager().add_tab_callback(partial(on_new_tab, tree))
    get_tab_manager().bind("<<NotebookTabChanged>>",
                           partial(select_current_file, tree),
                           add=True)

    menubar.get_menu("View").add_command(label="Focus directory tree",
                                         command=partial(focus_treeview, tree))

    settings.add_option("directory_tree_projects", [], List[str])
    string_paths = settings.get("directory_tree_projects", List[str])

    # Must reverse because last added project goes first
    for path in map(pathlib.Path, string_paths[:MAX_PROJECTS][::-1]):
        if path.is_absolute() and path.is_dir():
            tree.add_project(path, refresh=False)
    tree.refresh_everything()
コード例 #17
0
def setup() -> None:
    utils.bind_with_data(get_tab_manager(), '<<NewTab>>', on_new_tab, add=True)

    menubar.get_menu("Run").add_command(label="Compile",
                                        command=partial(
                                            do_something, 'compile'))
    menubar.get_menu("Run").add_command(label="Run",
                                        command=partial(do_something, 'run'))
    menubar.get_menu("Run").add_command(label="Compile and Run",
                                        command=partial(
                                            do_something, 'compilerun'))
    menubar.get_menu("Run").add_command(label="Lint",
                                        command=partial(
                                            do_something, 'compilerun'))

    # TODO: disable the menu items when they don't correspond to actual commands
    for label in {"Compile", "Run", "Compile and Run", "Lint"}:
        menubar.set_enabled_based_on_tab(
            f"Run/{label}", (lambda tab: isinstance(tab, tabs.FileTab)))
コード例 #18
0
def init(window):
    """Set up Porcupine.

    Usually :source:`porcupine/__main__.py` calls this function and you
    don't need to call it yourself. This function can still be useful if
    you want to run Porcupine minimally from another Python program for
    some reason.

    The *window* argument can be a tkinter root window or a ``Toplevel``
    widget.

    Example::

        import tkinter
        import porcupine

        root = tkinter.Tk()
        porcupine.init(root)
        root.protocol('WM_DELETE_WINDOW', porcupine.quit)
        root.mainloop()
    """
    global _main_window
    global _tab_manager

    assert [_main_window, _tab_manager
            ].count(None) != 1, ("porcupine seems to be partially initialized")
    if _main_window is not None:
        raise RuntimeError("porcupine.init() was called twice")
    _main_window = window  # get_main_window() works from now on

    utils._init_images()
    filetypes.init()
    dirs.makedirs()

    _tab_manager = tabs.TabManager(window)
    _tab_manager.pack(fill='both', expand=True)
    for binding, callback in _tab_manager.bindings:
        window.bind(binding, callback, add=True)

    menubar.init()
    window['menu'] = menubar.get_menu(None)
    _setup_actions()
コード例 #19
0
ファイル: test_menubar.py プロジェクト: ThePhilgrim/porcupine
def test_set_enabled_based_on_tab(tabmanager):
    tab1 = tabs.Tab(tabmanager)
    tab2 = tabs.Tab(tabmanager)

    menubar.get_menu("Foo").add_command(label="Spam")
    menubar.set_enabled_based_on_tab("Foo/Spam", (lambda tab: tab is tab2))
    assert menubar.get_menu("Foo").entrycget("end", "state") == "disabled"

    tabmanager.add_tab(tab1)
    assert menubar.get_menu("Foo").entrycget("end", "state") == "disabled"

    tabmanager.add_tab(tab2)
    assert menubar.get_menu("Foo").entrycget("end", "state") == "normal"

    tabmanager.select(tab1)
    tabmanager.update()
    assert menubar.get_menu("Foo").entrycget("end", "state") == "disabled"

    tabmanager.close_tab(tab1)
    tabmanager.close_tab(tab2)
    assert menubar.get_menu("Foo").entrycget("end", "state") == "disabled"
コード例 #20
0
def test_set_enabled_based_on_tab(porcusession, tabmanager):
    tab1 = tabs.Tab(tabmanager)
    tab2 = tabs.Tab(tabmanager)

    menubar.get_menu("Foo").add_command(label="Spam")
    menubar.set_enabled_based_on_tab("Foo/Spam", (lambda tab: tab is tab2))
    assert menubar.get_menu("Foo").entrycget('end', 'state') == 'disabled'

    tabmanager.add_tab(tab1)
    assert menubar.get_menu("Foo").entrycget('end', 'state') == 'disabled'

    tabmanager.add_tab(tab2)
    assert menubar.get_menu("Foo").entrycget('end', 'state') == 'normal'

    tabmanager.select(tab1)
    tabmanager.update()
    assert menubar.get_menu("Foo").entrycget('end', 'state') == 'disabled'

    tabmanager.close_tab(tab1)
    tabmanager.close_tab(tab2)
    assert menubar.get_menu("Foo").entrycget('end', 'state') == 'disabled'
コード例 #21
0
ファイル: tetris.py プロジェクト: ThePhilgrim/porcupine
def setup() -> None:
    menubar.get_menu("Games").add_command(label="Tetris", command=play_tetris)
コード例 #22
0
def setup() -> None:
    var = tkinter.BooleanVar()
    var.trace_add("write", (lambda *junk: get_main_window().attributes("-fullscreen", var.get())))
    menubar.get_menu("View").add_checkbutton(label="Full Screen", variable=var)
コード例 #23
0
ファイル: pluginmanager.py プロジェクト: bvadebruna/porcupine
def setup() -> None:
    menubar.get_menu("Settings").add_command(label="Plugin Manager",
                                             command=show_dialog)
コード例 #24
0
ファイル: pythonprompt.py プロジェクト: ThePhilgrim/porcupine
def setup() -> None:
    menubar.get_menu("Run").add_command(label="Interactive Python prompt",
                                        command=start_prompt)
コード例 #25
0
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)
コード例 #26
0
def setup() -> None:
    wrap_var = tkinter.BooleanVar()
    wrap_var.trace_add("write", partial(on_menu_toggled, wrap_var))
    menubar.get_menu("View").add_checkbutton(label="Wrap Long Lines", variable=wrap_var)
    menubar.set_enabled_based_on_tab("View/Wrap Long Lines", partial(on_tab_changed, wrap_var))
コード例 #27
0
ファイル: aboutdialog.py プロジェクト: ThePhilgrim/porcupine
def setup() -> None:
    menubar.get_menu("Help").add_command(label="About Porcupine",
                                         command=show_about_dialog)
コード例 #28
0
def setup() -> None:
    menubar.get_menu("Edit").add_command(label="Find and Replace",
                                         command=find)
    menubar.set_enabled_based_on_tab(
        "Edit/Find and Replace", (lambda tab: isinstance(tab, tabs.FileTab)))
コード例 #29
0
ファイル: black.py プロジェクト: ThePhilgrim/porcupine
def setup() -> None:
    menubar.get_menu("Tools/Python").add_command(label="black",
                                                 command=callback)
    menubar.set_enabled_based_on_tab(
        "Tools/Python/black", (lambda tab: isinstance(tab, tabs.FileTab)))
コード例 #30
0
def setup() -> None:
    menubar.get_menu("Edit").add_command(label="Go to Line", command=gotoline)
    menubar.set_enabled_based_on_tab(
        "Edit/Go to Line", (lambda tab: isinstance(tab, tabs.FileTab)))