I wrote an IPython Notebook-like Gtk wrapper [Python]

The other day

I wrote a Tkinter wrapper like IPython Notebook [Python]

I wrote that. This time, as I mentioned, I will introduce the same thing ported to Gtk. Please refer to the above article for how to call. I don't really see Gtk's Japanese tutorials, so I hope you understand that it's surprisingly easy to use.

gtk_wrapper.png

specification

This time, it is fully compatible with Python3. I brought the print_function from \ _ \ _ future \ _ \ _ at the beginning of the code, but I don't need it when calling it as a module. Even in the test, it just works with 3 systems.

The last or major change compared to the IPython Notebook is the third argument when calling the scale bar. In the past, it was a form to set the step size by yourself (example: x = (0., 10., 0.5) creates a scale bar in 0.5 step from 0.0 to 10.0), but as a function of Gtk There is something called set_digits, and we are using this. In other words, this part has been changed to a format that specifies how many digits after the decimal point should be taken. Of course, this part can be omitted as before, and if an integer value is given as the first argument, it can be specified as an integer, and if a decimal number is given to this, it should take up to two decimal places. There is. Regarding the rounding of the values here, you can see that it is set more finely by reading the reference, but I have not tampered with it this time.

Therefore, the content of the keyword argument given to interactive is converted into a widget as follows.

Keyword argumentWidget
`True` or `False`SwitchWiget
`value` or `(min,max)` or `(min,max,digit)` if integers are passedIntSliderWidget
`value` or `(min,max)` or `(min,max,digit)` if floats are passedFloatSliderWidget
`('orange','apple')` or `{'one':1,'two':2}`ComboBoxWidget

Usability

I took a screenshot of how I actually moved it, so I will introduce some. After all, it is displayed much more beautifully than Tk. I'm also happy that the Gtk theme has been applied.

When displayed in Gtk using tk_wrapper,

Selection_014.png

The widget is generated like this.

When you move the slider or turn the switch on and off, the contents of the function are executed. (Now just display the contents of the variable) When you press the button, you can execute another function at that timing, and you can refer to the value of the variable at that time.

Selection_015.png

Whole code

Finally, expose the entire code. I'm sorry that there are still few comments, but I hope it helps.

gist

gtk_wrapper.py



#! /usr/bin/env python
# -*- coding:utf-8 -*-
#
# written by ssh0, October 2014.

from __future__ import print_function

__doc__ = '''IPython-like Gtk wrapper class.

You can create Scalebar, Switch, ComboBox via simple interface.

usage example:

>>> from gtk_wrapper import interactive
>>> def f(a,b=20):
...     return a + b
...
>>> w = interactive(f, a=(0, 20), b=20)

(you can add buttons and label here manually)

(then, you should add the next line)
>>> w.display()

(and you can get the result for f(a,b) by)
>>> w.result
32

(or get the arguments with a dictionary)
>>> w.kwargs
{'a':8, 'b':24}
'''

from gi.repository import Gtk
import inspect


class interactive(Gtk.Window):

    def __init__(self, func, title='title', **kwargs):
        self.__doc__ = __doc__
        self.func = func
        self.kwargs = dict()
        args, varargs, keywords, defaults = inspect.getargspec(self.func)
        d = []
        for default in defaults:
            d.append(default)
        self.kwdefaults = dict(zip(args[len(args) - len(defaults):], d))

        Gtk.Window.__init__(self, title=title)
        hbox = Gtk.Box(spacing=6)
        self.add(hbox)

        self.listbox = Gtk.ListBox()
        self.listbox.set_selection_mode(Gtk.SelectionMode.NONE)
        hbox.pack_start(self.listbox, True, True, 0)
        self.status = Gtk.Label()

        for kw, arg in kwargs.items():
            kw = str(kw)
            arg_type = type(arg)
            if arg_type == tuple:
                # type check for elements in tuple
                argtype = self.type_check(arg)
                if argtype == str:
                    self.combobox_str(kw, arg)
                else:
                    self.scale_bar(kw, arg, argtype)
            elif arg_type == int or arg_type == float:
                self.scale_bar(kw, [arg], arg_type)
            elif arg_type == bool:
                self.switch(kw, arg)
            elif arg_type == str:
                self.label(arg)
                self.kwargs[kw] = arg
            elif arg_type == dict:
                self.combobox_dict(kw, arg)
            else:
                raise TypeError

        row = Gtk.ListBoxRow()
        hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=50)
        row.add(hbox)
        hbox.pack_start(self.status, True, True, 10)
        self.status.set_text(str(self.kwargs))
        self.listbox.add(row)

    def display(self):
        self.connect("delete-event", Gtk.main_quit)
        self.show_all()
        Gtk.main()

    def status_change(self):
        self.status.set_text(str(self.kwargs))
        self.kwargs_for_function = self.kwdefaults
        for k, v in self.kwargs.items():
            self.kwargs_for_function[k] = v
        self.result = self.func(**self.kwargs_for_function)

    def set_value(self, kw, new_value):
        # if argument is already given in func, use it for a default one
        if kw in self.kwdefaults:
            self.kwargs[kw] = self.kwdefaults[kw]
            return self.kwdefaults[kw]
        else:
            self.kwargs[kw] = new_value
            return new_value

    def type_check(self, arg):
        argtype = type(arg[0])
        if not all([type(a) == argtype for a in arg]):
            raise TypeError("""types in a tuple must be the same.
            int or float: Scalebar
            str         : Combobox""")
        return argtype

    def add_label(self, kw, parent):
        label = Gtk.Label(kw, xalign=0)
        parent.pack_start(label, True, True, 10)

    def scale_bar(self, kw, arg, argtype):

        def scale_interact(scale, _type):
            if _type == int:
                self.kwargs[kw] = int(scale.get_value())
            else:
                self.kwargs[kw] = float(scale.get_value())
            self.status_change()

        # length check for tuple
        len_arg = len(arg)
        if len_arg > 3 or len_arg == 0:
            raise IndexError("tuple must be 1 or 2 or 3 element(s)")

        if argtype == int:
            scale_digit = 0
        elif argtype == float:
            scale_digit = 2
        else:
            raise TypeError("arg must be int or float")

        # set the values
        if len_arg == 3:
            scale_from = arg[0]
            scale_to = arg[1]
            scale_digit = arg[2]
        elif len_arg == 2:
            scale_from = arg[0]
            scale_to = arg[1]
        else:
            scale_from = arg[0] * (-1)
            scale_to = arg[0] * 3

        # create scale widget in listbox
        row = Gtk.ListBoxRow()
        hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=50)
        row.add(hbox)

        self.add_label(kw, hbox)
        scale = Gtk.Scale()
        scale.set_range(scale_from, scale_to)
        scale.set_digits(scale_digit)
        scale.set_value(self.set_value(kw, arg[0]))
        scale.set_draw_value(True)
        scale.connect('value-changed', scale_interact, argtype)
        hbox.pack_start(scale, True, True, 10)

        self.listbox.add(row)

    def switch(self, kw, arg):

        def on_switch_activated(switch, gparam):
            self.kwargs[kw] = switch.get_active()
            self.status_change()

        # create switch widget in listbox
        row = Gtk.ListBoxRow()
        hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=50)
        row.add(hbox)
        self.add_label(kw, hbox)
        switch = Gtk.Switch()
        switch.connect("notify::active", on_switch_activated)
        switch.set_active(self.set_value(kw, arg))
        hbox.pack_start(switch, False, False, 10)
        self.listbox.add(row)

    def combobox_str(self, kw, arg):

        def on_combo_changed(combo):
            tree_iter = combo.get_active()
            if tree_iter is not None:
                self.kwargs[kw] = arg[tree_iter]
                self.status_change()

        argstore = Gtk.ListStore(str)
        for a in arg:
            argstore.append([a])

        # create combobox widget in listbox
        row = Gtk.ListBoxRow()
        hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=50)
        row.add(hbox)
        self.add_label(kw, hbox)
        combo = Gtk.ComboBox.new_with_model(argstore)
        combo.connect("changed", on_combo_changed)
        renderer_text = Gtk.CellRendererText()
        combo.pack_start(renderer_text, True)
        combo.add_attribute(renderer_text, "text", 0)
        combo.set_active(arg.index(self.set_value(kw, arg)))
        hbox.pack_start(combo, False, False, True)
        self.listbox.add(row)

    def combobox_dict(self, kw, arg):

        def on_combo_changed(combo):
            tree_iter = combo.get_active()
            if tree_iter is not None:
                self.kwargs[kw] = values[tree_iter]
                self.status_change()

        argstore = Gtk.ListStore(str)
        keys = list(arg.keys())
        values = list(arg.values())
        for a in keys:
            argstore.append([a])

        # create combobox widget in listbox
        row = Gtk.ListBoxRow()
        hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=50)
        row.add(hbox)

        self.add_label(kw, hbox)
        combo = Gtk.ComboBox.new_with_model(argstore)
        combo.connect("changed", on_combo_changed)
        renderer_text = Gtk.CellRendererText()
        combo.pack_start(renderer_text, True)
        combo.add_attribute(renderer_text, "text", 0)
        combo.set_active(values.index(self.set_value(kw, arg)))
        hbox.pack_start(combo, False, False, True)

        self.listbox.add(row)


if __name__ == '__main__':
    from gi.repository import Gtk

    def f(x=12, y=20, z='III', o=False, i=20):
        print("x: {0}, y: {1}, z: {2}, o: {3}, i: {4}".format(x, y, z, o, i))

    def b1(button):
        print(w.kwargs)

    buttons = [('b1', b1), ('exit', Gtk.main_quit)]
    w = interactive(f, x=10, y=(1., 100.),
                    z=("ZZZ", "III", "foo", "bar"),
                    i={'0': 0, '10': 10, '20': 20},
                    o=True
                    )
    row = Gtk.ListBoxRow()
    hbox = Gtk.HBox(spacing=10)
    row.add(hbox)
    for b in buttons:
        button = Gtk.Button(b[0])
        button.connect('clicked', b[1])
        hbox.pack_start(button, True, True, 0)
    w.listbox.add(row)

    w.display()

Challenges and prospects

I wanted to use Gtk in the main, so this is one paragraph. However, as usual, the function is executed as many times as the number of slide bars (that is, it is not a problem only for Tk), and the display order is also out of order. Are the points to be improved in the future and the enhancement of the menu bar? I think you can easily read files. Qt is on hold because I don't have much chance to use it personally (Ubuntu ...).

Recommended Posts

I wrote an IPython Notebook-like Gtk wrapper [Python]
I wrote an IPython Notebook-like Tkinter wrapper [Python]
I wrote python in Japanese
I wrote an empty directory automatic creation script in Python
I sent an SMS with Python
I wrote Fizz Buzz in Python
I wrote the queue in Python
I wrote the stack in Python
I tried sending an email with python.
[Python beginner] I collected the articles I wrote
A memo that I wrote a quicksort in Python
I wrote "Introduction to Effect Verification" in Python
I tried sending an email with SendGrid + Python
I made a Python Qiita API wrapper "qiipy"
I started python
I wrote matplotlib
I got an error that Python couldn't read settings.ini
I wrote an automatic kitting script for OpenBlocks IoT
I get an Import Error in Python Beautiful Soup
I tried to implement an artificial perceptron with python
[Python, ObsPy] I wrote a beach ball with Matplotlib + ObsPy
I made an action to automatically format python code
Python: I tried a liar and an honest tribe
[Fundamental Information Technology Engineer Examination] I wrote an algorithm for determining leap years in Python.