Welcome to Aduct’s documentation!¶
Introduction¶
Aduct is a toolkit to design graphical applications that can be dynamically changed with a little work as possible. It is designed by inheriting objects provided by Gtk and thus by following principles of Aduct with Gtk, one can make powerful applications that are easy for a developer to develop, third-party person to improve and end user to use.
The Need¶
Let us assume you make an amazing application. As its core developer, you design the interface in such a way that it looks awesome to you. In other words, it has an interface that reflects your ideas on how a graphical user interface should look. Apart from the code (which nobody cares when your application is closed-source), the where and how widgets are arranged in the user interface is also important (which everybody cares except you).
After designing the application, naturally you get more comfortable with the interface, so it doesn’t look strange to you in any way. But this isn’t the case with users. There are also certain challenges that you often need to overcome after publishing or improving the application :
- You need to add a feature New features become a nightmare when it involves a lot of rewrites. The problem with adding the new feature is the doubt if it will be accepted by users or whether they will work properly with other parts of application.
- Allowing third-party plugins The developer of the plugin may not have the same thinking you do. Sometimes by mistake, they completely spoil the working of your application. Also a plugin is not always cooperative with other plugins. It may inhibit the working of other plugins.
- Impressing the users Unfortunately there is also a good chance that your users may be totally against the interface of your application. For them, you need to have an interface that can be changed at their will. Some applications may even have a different layout of interface for each file. So you need to make an application whose interface can literally be saved and ported.
How can you tackle these hurdles? Let us try to describe the sudden solutions that comes to your mind.
For the first issue, the complexity depends on your code and interface. Depending on that, adding a new feature could be simple as adding a few lines of code or a complete rewrite of your entire application. But what if you want a solution that doesn’t depend on the complexity of your application?
The second issue can be avoided by dividing your application interface into loosely coupled parts. Then you allow the plugins to affect only certain parts of the application. Mostly it is some panel situated at one edge of the application, where the plugin gets its place. In other words, you place a restriction on the scope of a plugin, so that it doesn’t interfere with other blocks of application. If a plugin wants a central representation in your application, how can you achieve that? Can you design an approach that gives your plugin all the essential freedom?
The issue with users is the most important one to take care. To make an interface that can be easily changed by users is the most irritating. Sorting out the requirements, checking whether a feature will be useful or not is also difficult to resolve. For you, placing the toolbox at right seems plausible, but there could be a creepy user who wants it at bottom. Possibilities exist. Saving the interface is another headache. Which widget was modified ?, which should be saved ?, how to save ?, all questions makes you think.
There are more such issues which doesn’t go easy with a developer.
A Solution¶
Aduct can be used to kick-out the above mentioned issues. Some developers may be think that such issues can be avoided by writing a few more lines of code. And yes, Aduct is such a package made with a few lines of code. It is very small, but when you follow its approach, it saves your essential time and resources, which can be instead used in improving other concepts of your application.
Aduct’s design principle is very simple. You make widgets that are independent of each other. Make a basic layout of the interface, then sit back and relax, because Aduct now takes care of other requirements. We believe in “take care of small things and big things will automatically be taken care”. It should not be hidden that at first you may have trouble writing independent widgets, but once you are able to make one, then you hardly need to focus in its working with interface.
To The Point¶
Aduct is inspired by Blender’s interface. Blender has such an awesome one where it is possible for a user to customize it in any way they want. Aduct, which tries to mimic the behavior, is written in Python using Gtk. We will cover the working and making of interfaces with Aduct later. For now, what you need to understand is that in Aduct, we have two things that work together. A provider that can produce widgets and a view, that can hold and display them. Views come with various tweaks, which just need to be enabled. When the requirements of both are satisfied, you get a good interface that can suit all the use cases. The description so far may seem so absurd and you may not have even able to get a single point. It’s okay, continue reading, you will understand.
Installation¶
Simply put, to use Aduct, you need to install it. The following guide should be able help you in getting Aduct. For advanced users and more configuration, you are always welcome to grab the source code of Aduct and do literally whatever you want.
Requirements¶
Aduct is written in Python and requires Python. Apart from that you need Gtk, nothing more. So we directly follow the guidelines required to use Gtk.
- Python 3.5+ If not installed, get the latest version of Python.
- Gtk 3.0+ The latest version of Gtk is recommended. The instructions to install it are highlighted in the official website.
- PyGObject PyGObject provides Python bindings to GI modules (that is Gtk and its friends). If this is your first try with Gtk, you may have to install it. Directly install it using PIP.
Getting Aduct¶
After the requirements are satisfied, installing Aduct won’t be a trouble. The best way is to install it using PIP.
$ pip install Aduct
As already stated, for those not comfortable with PIP, you can always get the source code and do necessary things to get Aduct on your PYTHONPATH.
New to Gtk ?¶
It might be case, that you don’t even know what is Gtk and perhaps be wondering how to get started. The following is a quotation from Wikipedia’s page for Gtk.
GTK (formerly GTK+ GIMP Toolkit) is a free and open-source cross-platform widget toolkit for creating graphical user interfaces (GUIs). It is licensed under the terms of the GNU Lesser General Public License, allowing both free and proprietary software to use it. Along with Qt, it is one of the most popular toolkits for the Wayland and X11 windowing systems. (Source : Wikipedia)
Learning the basics of Gtk will be easy with Python. To get familiar with developing Gtk applications, please have a look at the official docs.
Overview¶
Let us now learn how to design a flexible interface using Aduct. It isn’t any difficult, so let’s get started. An interface designed with Aduct is backed up by three things; providers, views and elements. The following sections describe them in an elaborate manner.
Aduct.Provider¶
Provider is an object that can produce widgets. They can be taken as a factory that can assemble and give you widgets when required. The provider owns every information about the widget it produces, so it can easily produce duplicate widgets and also control them as required. Apart from assembling widgets, they can also dissemble a widget when required. A dissembling process ideally extracts information from a widget and then destroys it.
Widgets can however be stored (in memory), if making a widget is so tedious compared to storing them. So, you can also reuse a widget and make a new one only when you run out of stock. The process of dissembling a widget is called clearing. A widget produced by a provider is known as child. In fact, the word widget is rarely used and instead the term child is used.
Aduct.Element¶
Element is an container that can hold the child produced by a provider. They are the front-end in an application, so any sort of interaction happens via elements. An element has an action button. It is used to alter the child in the element, like changing, removing, clearing a child. Action buttons are recommended, but can be avoided if you have any other approach. On the side of an action button, you can also add widgets like quick tool buttons. Ideally you can attach only one widget, so if you want to add multiple widgets, put them inside a container like grid or box. Elements can not be directly attached to an interface, they need a container called views.
Aduct.View¶
View is a container of widgets. Aduct comes with three basic views, that are enough for most of the use cases. New views can also be made easily if they don’t satisfy your need. The three views are :
Aduct.Bin¶
A bin can contain only one child. The child can either be another view or element.
Aduct.Paned¶
A paned is a container that can hold two children, either in vertical or horizontal direction. Similar to a bin, paned can hold either a view or an element. Paned contains a movable handle between its children, which can change the space allocated to each child.
Aduct.Notebook¶
A notebook can contain an arbitrary number of children, but they all should be elements. In a notebook, only one child is visible at a time and the visible child can be changed using the tabs located on an edge. Notebook also has action buttons, attached at either side of tabs or at one side. They are optional, so can be avoided if not needed.
Framing an Application¶
Now we describe how to create a convenient interface using Aduct.
- Make all the basic things required to make the application, like collecting plugins, user data.
- From your plugins and own collection, make a list of providers which can produce child widgets.
- Design an interface that you and a lot of users find convenient. The interface designed should be using views and elements, with children from providers.
- Then connect the elements, views with tweak functions. Tweak functions are those that can modify properties of views and elements.
- Provide a way for the users to save and load interfaces using Aduct’s built-in functions.
The above points do not explain how they are done practically, so let’s get our hands wet in a short tutorial in next chapter.
Making an Application¶
No more theory, let us now get into the business of making applications. In this tutorial we will make a very basic application that helps you in understanding the logics. We have divided the tutorial into simpler parts so it is easy to follow.
Since the application should be easy, we will handle only a very few
widgets of Gtk
. The details of the widgets that are used in our providers
are given below :
- An entry widget takes single line input from user. It can also be used to display text that can only be copied but not modified. When the text in an entry is changed, it emits changed signal. To prevent editing the text of an entry, we set its editable property to False.
- To open, save, select files or folders we need a file chooser widget. After user selects a file in the file chooser dialog, a file-set signal is emitted.
- A grid allows packing widgets into a single widget in a tabular format. To attach a widget to grid, we use the gird’s
Gtk.Grid.attach()
method. - Text can be displayed using a label widget. In our demo application, we are using only basic features of a label, that is just display text.
- Menu is often a linear list of textual buttons. To break that rule and include other widgets in menu, we use model buttons.
- Popovers are used to display something for a short period of time. They are usually used for drop-down menus.
- If a widget is too large to be accommodated in given space, we use a scrolled window. This shows only the portion of the widget that could be displayed and rest can be scrolled.
- Text buffer is used to store the text for a text view widget. A single text buffer can be shared across multiple text views.
- To enable multi-line text compatibility a text view widget is used. Text view is a front-end and the text in it is controlled using a text buffer. As in the case of label, we are not going to use the full power of a text view.
- A toggle button is like a switch, it can have two states active and inactive. A toggled signal is emitted if the state of the button is changed.
- A window is the top-level widget that represents your application. We quit Gtk’s main loop when the main window is destroyed.
So let’s get started.
Making Providers¶
Providers are the core part of our application. Because of the design philosophy of Aduct, every part of an application behaves like a plugin. This makes an application modular in every way. To keep things simple, we make only three providers.
- Provider AIt gives a text entry and a toggle button. The toggle button allows editing the entry.
- Provider BIt gives a text view and a file chooser button. The file chooser button opens the file for displaying.
- Provider CIt gives a label with text Hello World.
Note
If the code to make the first two providers are difficult, then copy the code for Provider C and change the text for label, but the results will vary.
To make providers, we inherit Aduct.Provider
. Then we add the required
methods (please refer Aduct.Provider
for more details on required
methods).
In Aduct, you will often see variables named child_dict
. A child_dict
is of the following format.
child_dict = {
"child": Gtk.Widget,
"child_name": str,
"icon": Gtk.Image,
"header_child": Gtk.Widget or None,
"provider": Aduct.Provider
}
The source code for our providers are as follows. If you are lazy to copy-paste,
download
it.
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, GObject
import Aduct
class Provider_A(Aduct.Provider):
name = GObject.Property(type=str, default="Provider A", flags=GObject.ParamFlags.READABLE)
def __init__(self):
Aduct.Provider.__init__(self)
self.text = ""
self.toggles = []
self.entries = []
self.editable = False
def change_text(self, entry):
self.text = entry.get_text()
for entry in self.entries:
entry.set_text(self.text)
def clear_child(self, child_props):
self.entries.remove(child_props["child"])
self.toggles.remove(child_props["header_child"])
del child_props
def get_a_child(self, child_name):
entry = Gtk.Entry(margin=5, text=self.text, editable=self.editable)
entry.connect("changed", self.change_text)
icon = Gtk.Image.new_from_icon_name("terminal", 2)
# Choose whatever icon you want
switch = Gtk.ToggleButton(
label="Allow Edit", hexpand=True, halign=2, active=self.editable
)
switch.connect("toggled", self.toggle_editable)
self.entries.append(entry)
self.toggles.append(switch)
child_props = {
"child_name": "Entry",
"child": entry,
"icon": icon,
"header_child": switch,
}
return child_props
def get_child_props(self, child_name, child, header_child):
props = {"child_name": child_name, "text": self.text, "editable": self.editable}
return props
def get_child_from_props(self, props):
self.editable = props["editable"]
self.text = props["text"]
for toggle in self.toggles:
toggle.set_active(self.editable)
for entry in self.entries:
entry.set_editable(self.editable)
entry.set_text(self.text)
return self.get_a_child(props["child_name"])
def toggle_editable(self, toggle):
self.editable = toggle.get_active()
for toggle in self.toggles:
toggle.set_active(self.editable)
for entry in self.entries:
entry.set_editable(self.editable)
class Provider_B(Aduct.Provider):
name = GObject.Property(type=str, default="Provider B", flags=GObject.ParamFlags.READABLE)
def __init__(self):
Aduct.Provider.__init__(self)
self.file_choosers = []
self.buffer = Gtk.TextBuffer()
self.path = None
def clear_child(self, child_props):
self.file_choosers.remove(child_props["header_child"])
del child_props
def change_text_at_buffer(self, fp_but):
path = fp_but.get_filename()
self.path = path
fp = open(path)
text = fp.read()
self.buffer.set_text(text)
fp.close()
for fp_chooser in self.file_choosers:
fp_chooser.set_filename(self.path)
def get_a_child(self, child_name):
textview = Gtk.TextView(margin=5, buffer=self.buffer)
scrolled = Gtk.ScrolledWindow(expand=True)
scrolled.add(textview)
icon = Gtk.Image.new_from_icon_name("folder", 2)
fp_but = Gtk.FileChooserButton(title="Choose file", hexpand=True, halign=2)
if self.path:
fp_but.set_filename(self.path)
fp_but.connect("file-set", self.change_text_at_buffer)
self.file_choosers.append(fp_but)
child_props = {
"child_name": "TextView",
"child": scrolled,
"icon": icon,
"header_child": fp_but,
}
return child_props
def get_child_props(self, child_name, child, header_child):
props = {"child_name": child_name, "path": self.path}
return props
def get_child_from_props(self, props):
self.path = props["path"]
if self.path:
fp = open(self.path)
text = fp.read()
self.buffer.set_text(text)
fp.close()
for fp_chooser in self.file_choosers:
fp_chooser.set_filename(self.path)
return self.get_a_child(props["child_name"])
class Provider_C(Aduct.Provider):
name = GObject.Property(type=str, default="Provider C", flags=GObject.ParamFlags.READABLE)
def __init__(self):
Aduct.Provider.__init__(self)
def clear_child(self, child_props):
del child_props
def get_a_child(self, child_name):
label = Gtk.Label(margin=5, label="Hello world")
icon = Gtk.Image.new_from_icon_name("glade", 2)
child_props = {
"child_name": "Label",
"child": label,
"icon": icon,
"header_child": None,
}
return child_props
def get_child_props(self, child_name, child, header_child):
props = {"child_name": child_name}
return props
def get_child_from_props(self, props):
return self.get_a_child(props["child_name"])
A = Provider_A()
B = Provider_B()
C = Provider_C()
We prefer keeping the above code in a separate file (could be named providers.py), because in practical situations (while making real applications) it is better to isolate providers from the core application, this makes it easy to maintain. The reason we imported Gtk from Aduct is not so crucial. It was done to reduce typing, also it makes sure that we are using the same version of Gtk that Aduct is using.
Designing the Application¶
Now we have made providers, our next step is to frame the application. Open a new file (could be named app.py). To allow widgets in the application, we should put a way for users to view available widgets and select the required. This is done using action button of element and notebook. The convention is when users left-click an action button, it should show widgets (from providers) and when they right-click, it should show options to modify the interface.
There are two suitable ways to show the users the widgets to select from. It could be a popup window. But pop-ups are considered distracting. The second option is drop-down menu (also known as popover menu). Popovers are better as they cover only a small area and are not as annoying as popup windows. We populate the menu with model buttons.
Before adding providers, we should also spend time in a kind of
functions known as creator functions. As Aduct is an interface to
dynamically modify an interface, you need to be able to dynamically make
Aduct widgets. So we make small functions that, when called give the
required widget. An advantage of such functions is that they can be used
to add custom changes to widgets like changing border spacing,
connecting signals and automate other repeating tasks. The first few
lines of app.py is given below. (The complete file is also available for
download
.)
import Aduct
from Aduct import Gtk
from providers import A, B, C # providers from provider.py
last_widget = None # The last widget (element/notebook) where popover was shown.
def new_element():
element = Aduct.Element(margin=5)
element.connect("action-clicked", show_popover_element)
# show_popover_element is a function to show the popover for an element.
return element
def new_bin():
bin_ = Aduct.Bin()
return bin_
def new_paned(orientation=0):
paned = Aduct.Paned(orientation=orientation)
return paned
def new_notebook():
notebook = Aduct.Notebook()
button = Gtk.Button()
icon = Gtk.Image.new_from_icon_name("list-add", 2)
button.add(icon)
notebook.set_action_button(button, 1)
notebook.connect("action-clicked", show_popover_notebook)
# show_popover_notebook is a function like show_popover_element.
return notebook
The idea of the above code is simple. When action button of an element
or notebook is clicked, it emits a signal and popover is shown in
return. The popover contains model buttons for various purposes, when
they are clicked, they need to know for which element or notebook they
were clicked. To tackle this, when an action button is clicked, we
correspondingly set the value of last_widget
to that widget. With
that, let’s append the next lines of code.
def show_popover_element(ele, but, event):
global last_widget
last_widget = ele
if event == 1: # 1 -> left-click of mouse
prov_popover.set_relative_to(but)
prov_popover.popup()
elif event == 3: # 3 -> right-click of mouse
for modbs in tweaks.values():
for modb in modbs:
modb.set_sensitive(True)
tweak_popover.set_relative_to(but)
tweak_popover.popup()
def show_popover_notebook(nb, but, event):
global last_widget
last_widget = nb
if event == 1:
prov_popover.set_relative_to(but)
prov_popover.popup()
elif event == 3:
for modb in tweaks["Element"]:
modb.set_sensitive(False)
for modb in tweaks["Notebook"]:
modb.set_sensitive(False)
tweak_popover.set_relative_to(but)
tweak_popover.popup()
Both the above functions are same but the difference between them is
that first one is for an element and second is for a notebook.
prov_popover
is for displaying widgets from providers and
tweak_popover
is for showing options to modify the interface. As per
the convention mentioned earlier, we show prov_popover
when users
left-click and tweak_popover
when users right-click an action button. We will cover
later why we are changing sensitivities of model buttons.
Now let us make some more functions that can modify the interface.
def remove_element(wid):
global last_widget
Aduct.remove_element(last_widget, last_widget.get_parent())
def add_to_paned(wid, position):
global last_widget
element = new_element()
paned = new_paned()
if position == 0:
paned.set_orientation(0)
Aduct.add_to_paned(last_widget, element, paned, 1)
elif position == 1:
paned.set_orientation(0)
Aduct.add_to_paned(last_widget, element, paned, 2)
elif position == 2:
paned.set_orientation(1)
Aduct.add_to_paned(last_widget, element, paned, 1)
elif position == 3:
paned.set_orientation(1)
Aduct.add_to_paned(last_widget, element, paned, 2)
def add_to_notebook(wid, position):
global last_widget
notebook = new_notebook()
notebook.set_tab_pos(position)
Aduct.add_to_notebook(last_widget, notebook)
Please read Functions to know the details of the functions used from Aduct. Next we add more functions for changing child at an element, saving and loading interfaces.
def change_child_at_element(wid, prov, child_name):
global last_widget
if last_widget.get_type() == "element":
Aduct.change_child_at_element(last_widget, prov, child_name)
elif last_widget.get_type() == "notebook":
element = new_element()
Aduct.change_child_at_element(element, prov, child_name)
Aduct.add_to_notebook(element, last_widget)
element.show_all()
def save_interface(wid):
from json import dump
with open("aduct.ui", "w") as fp:
ui_dict = Aduct.get_interface(top_level)
dump(ui_dict, fp, indent=2)
def load_interface(wid):
from json import load
with open("aduct.ui") as fp:
ui_dict = load(fp)
creator_maps = {
"type": {
"element": (new_element, (), {}),
"bin": (new_bin, (), {}),
"notebook": (new_notebook, (), {}),
"paned": (new_paned, (), {}),
}
}
init_maps = {
"provider": {"Provider A": A, "Provider B": B, "Provider C": C, None: None}
}
Aduct.set_interface(ui_dict, top_level, creator_maps, init_maps)
The first function does some straight-forward tasks. It changes a child at element when called from element. In case it is called from a notebook, we make a new element, get a child from provider and add it to the element. Then we append the element to the notebook.
The second function gets the interface; it is a dictionary with strings,
numbers and None. So it can be dumped using json in human-readable
format. We are using a file named aduct.ui for saving and loading
interfaces. top_level
(declared later) is the view or element from
which the interface should be fetched. It is usually the root widget.
The third function surely deserves a mention. After completing this tutorial, you can run the script (app.py), try playing with the interface. Then save the interface. After that open the file named aduct.ui, you will see a JSON-styled data with keys like type, provider etc. Now when you ask Aduct to create the interface from the same file, it replaces all the required values with objects (or widgets here). The convention is that key type states the type of Aduct widget and provider states the name of provider.
The dictionaries whose name ends with maps, does the job of replacing
strings or numbers with an object. They are nested-dictionaries
of depth two. It is like what key to replace? If found replace the value
of that key with the value from maps. For example, from init_maps
we
have the key provider. So first the set_interface
function will look
for any key named provider in ui_dict
. If found it will look at its
value, say it is Provider A, now it will go back to init_maps
and
look for the value of key Provider A in the dictionary which is the value
of key named provider. From the above it is provider A
, then the function
replaces the value Provider A in ui_dict
with the actual object;
provider A
. So you can consider it as a mapping of strings to
objects.
The purpose of creator_maps
and init_maps
are pretty same. Their
difference lies in values they are replacing. init_maps
maps to
object already created, that is initialized objects, like providers,
plugins, file objects. It is to replace object that are already
available and should not created again. On the other hand,
creator_maps
, dynamically creates objects as needed. It is for the
purpose where each object has to be unique or can be created multiple
times for multiple usage. The values of creator_maps
are of this
order (function, args, kwargs)
. While replacing
strings with objects, the object is created by calling the function like
this : object = function(*args, **kwargs)
.
Pretty simple as that, however if you are confused, remember them as mappings. That’s it.
The third function needs a root widget. It will remove whatever child it holds and replaces it with the children from the JSON file. However it returns the old child for recovering data, something not so necessary in our application.
The finishing parts of our application is just connecting everything, creating a new window and adding a top level view.
provs = [
(
A,
Gtk.ModelButton(text="Entry"),
"Entry",
), # Making a model-button for each provider.
(B, Gtk.ModelButton(text="TextView"), "TextView"),
(C, Gtk.ModelButton(text="Label"), "Label"),
]
prov_grid = Gtk.Grid() # A grid to store them
for y, (prov, modb, child_name) in enumerate(provs):
prov_grid.attach(modb, 0, y, 1, 1)
modb.connect("clicked", change_child_at_element, prov, child_name)
prov_popover = Gtk.PopoverMenu()
prov_popover.add(prov_grid)
prov_grid.show_all()
# Pretty same as providers, but for tweak functions.
tweaks = {
"Element": (Gtk.ModelButton(text="Remove"),),
"Notebook": (
Gtk.ModelButton(text="Add to top notebook"),
Gtk.ModelButton(text="Add to side notebook"),
),
"Paned": (
Gtk.ModelButton(text="Split left"),
Gtk.ModelButton(text="Split right"),
Gtk.ModelButton(text="Split up"),
Gtk.ModelButton(text="Split down"),
),
"Interface": (
Gtk.ModelButton(text="Load interface"),
Gtk.ModelButton(text="Save interface"),
),
}
tweak_grid = Gtk.Grid()
for x, title in enumerate(tweaks):
label = Gtk.Label(label=title)
tweak_grid.attach(label, x, 0, 1, 1)
modbs = tweaks[title]
for y, modb in enumerate(modbs):
tweak_grid.attach(modb, x, y + 1, 1, 1)
tweak_popover = Gtk.PopoverMenu()
tweak_popover.add(tweak_grid)
tweak_grid.show_all()
def connect_tweaks():
# Connecting model-buttons to required functions.
elem_modb = tweaks["Element"][0]
elem_modb.connect("clicked", remove_element)
top_nb_modb = tweaks["Notebook"][0]
top_nb_modb.connect("clicked", add_to_notebook, 2)
side_nb_modb = tweaks["Notebook"][1]
side_nb_modb.connect("clicked", add_to_notebook, 0)
l_paned_modb = tweaks["Paned"][0]
r_paned_modb = tweaks["Paned"][1]
u_paned_modb = tweaks["Paned"][2]
d_paned_modb = tweaks["Paned"][3]
# 0, 1, 2, 3 are integer values of Gtk.PositionType.
l_paned_modb.connect("clicked", add_to_paned, 0)
r_paned_modb.connect("clicked", add_to_paned, 1)
u_paned_modb.connect("clicked", add_to_paned, 2)
d_paned_modb.connect("clicked", add_to_paned, 3)
load_modb = tweaks["Interface"][0]
save_modb = tweaks["Interface"][1]
load_modb.connect("clicked", load_interface)
save_modb.connect("clicked", save_interface)
connect_tweaks()
top_level = new_bin()
element = new_element()
top_level.add_child(element) # Making a single element and adding it.
win = Gtk.Window(default_height=500, default_width=750)
win.add(top_level)
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()
Phew… we completed making the application! You might not have understood some parts, but still, run the application (run app.py) and see how it looks. Well just an empty screen with an empty button, right? Click on the action button of element and add new child to the element. Next try right clicking the action button to split it or add it to a notebook. Have fun removing the views and adding new one. When you are comfortable with the interface, save the interface and close the application. Now open it again and load the interface. You should see the interface you saved.
Let us discuss something we promised earlier. Run the application and add an element to notebook. Now right-click the element’s action button and click on Add to side notebook, you should see an error in your terminal or console that a Aduct notebook can only have a Aduct element as child. It is not a bug, it is a feature! Aduct notebook can only attach a named child and the only named child in Aduct is element. So you will get error when you try to add a child of irrelevant type to a notebook. This is the reason we changed the sensitivities of a few menu items. Because we don’t want to allow users to do something not permitted. We could have made separate popovers for notebook and element, but to avoid repeating codes with small difference, we omitted it. You might wonder why we didn’t change sensitivities of menu items of popovers shown for elements inside a notebook, the answer is we want you to try!
Note a few more things which might look absurd. When you remove an element from a paned, it collapses to a bin. When you remove an element from notebook with three or more pages, nothing goes wrong. But when you remove an element from a notebook with only two pages, the notebook drops to a bin. In case of a bin, when you try to remove its child element, instead of removing, it only clears the element.
This also has some reasons behind it. A paned is meant to hold two child, so when you remove its one child, the purpose of a paned is destroyed, so it becomes a bin. Similarly, a notebook is meant to hold a number of elements and show only one of them at a time. A notebook with only page is against its purpose, so it becomes a bin. For bin, the same logic is applied. A Aduct bin is, by convention, used as a top-level for holding other view. When you remove the element from it, the complete interface link is broken and you get a blank space, where no kind of interaction is possible. To avoid this, bin always clears the element instead of removing it.
However, if these behaviors are not acceptable to you, you are always free to create your own functions and use them.
While using the widgets like entry, text view in our application, you might have noticed that an entry is just a copy of another entry, with same text and mode, synchronized between them. This is to symbolize that widgets with same name are basically the same. But this is not enforced. It depends on the provider, it may produce a new widget or just a copy.
So here we reach the end of tutorial. There are some lines, paragraphs or entire section that doesn’t even make any sense to you. Feel free to discuss it with other developers to get help. Also if you think, the same matter could be presented in a better manner, you are always welcome to suggest your edits.
At last, we would like to say, designing an application is like painting. Everyone has a brush and seven basic colors. It depends on the painter how great he/she is going to make his/her art look. He/she may have a different ideology and style, its unique and can’t be duplicated.
Same in case of an application, Aduct is like brush and paint, it lies in your method, how well you are going to utilize it. Sometimes it could come out worse, where you should surely retry. Sometimes it could come great, where you should share the method with others (including us!). Also beauty lies in the eyes of the viewer, not in the painting… cheers!
API Reference¶
Classes¶
Element¶
Element represents an individual block. It can hold a widget and handle operations with it.
While setting and removing, a child_dict
named dictionary is taken and returned.
The format of child_dict
is as follows
child_dict = {
"child": Gtk.Widget,
"child_name": str,
"icon": Gtk.Image,
"header_child": Gtk.Widget or None,
"provider": Aduct.Provider
}
Here only header_child
key is optional.
-
class
Aduct.Element.
Element
(child_dict=None, use_action_button=True, pack_type=0, **kwargs)¶ Makes an element based on given properties. Its CSS name is aduct-element.
Parameters: - child_dict (
dict
) – A dictionary object containing properties of child. Given a valid dictionary, the child is added toself
while initializing. It isNone
, when not given. - use_action_button (
bool
) – States whether to use an action button. Default isTrue
. - pack_type (
Gtk.PackType
) – Specfies the position of action button. It can be an integer of value either 0 or 1, which representsGtk.PackType.START
orGtk.PackType.END
respectively. The default value isGtk.PackType.START
. - **kwargs – The values to be passed to
Gtk.Grid
, from whichElement
is derived.
Action button that is used to handle interactions with user. Its default name is aduct-element-action_button.
Type: Gtk.Button
-
pack_type
¶ The position of action button in
self
.Type: Gtk.PackType
-
Signals
¶ - action-clicked
- Emitted with an integer when action button of
self
is clicked. The integer is 1, 2, 3 for LMB, MMB, RMB respectively. - child-added
- Emitted when a child is added to
self
. - child-cleared
- Emitted when the child of
self
is cleared. - child-removed
- Emitted when the child of
self
is removed.
Note
Incase having
use_action_button
asFalse
, an action button is still created, but is not attached to theself
.-
clear_child
()¶ Clears the child at
self
. After clearing,child-cleared
signal is emitted.
Removes the action button of
self
. Nothing is done if it is already disabled.
Adds the action button of
self
. Nothing is done if it is already enabled.
-
get_child
()¶ Gets the child held by
self
.Returns: The child of self
orNone
ifself
has no child.Return type: Gtk.Widget
orNone
-
get_child_name
()¶ Gets the name of child held by
self
.Returns: The child name of self
orNone
ifself
has no child.Return type: str
orNone
-
get_header_child
()¶ Gets the child packed at the header of
self
.Returns: The header child of self
orNone
ifself
has no header child.Return type: Gtk.Widget
orNone
-
get_icon
()¶ Gets the icon representing child held by
self
.Returns: The icon of action_button
orNone
ifself
has no icon for child.Return type: Gtk.Image
orNone
-
get_props
()¶ Gets the properties of child held by
self
.Returns: The dictionary that can be later used to build the same interface. Return type: dict
-
get_provider
()¶ Gets the provider for child held by
self
.Returns: The provider of self
orNone
ifself
has no child.Return type: Provider
orNone
-
remove_child
()¶ Removes the child held by
self
.By removing a child, all its associated properties like icon, header child are also removed. A
child-removed
signal is emitted byself
after removal.Raises: ValueError
– Raised whenself
has no child.Returns: A dictionary with child properties. Return type: dict
-
set_child
(child_dict)¶ Sets the child in
self
from given properties.If
self
already has a child, then its cleared before adding this new child. Achild-added
signal is emitted after addition.Parameters: child_dict ( dict
) – A valid dictionary with properties of child.
-
set_child_name
(child_name)¶ Sets the name of child held by
self
.Parameters: child_name ( str
) – The new name of child.
-
set_from_props
(props)¶ Sets the interface of
self
from given properties.If
self
already has a child, then its cleared before adding this new child.Parameters: props ( dict
) – The dictionary from which properties are set.
-
set_header_child
(header_child)¶ Sets the header child of
self
.Parameters: header_child ( Gtk.Widget
) – The new header child ofself
.
-
set_icon
(icon)¶ Sets the icon of child held by
self
.Parameters: icon ( Gtk.Image
) – The new icon of child.
-
set_provider
(provider)¶ Sets the provider of child held by
self
.Parameters: provider ( Provider
) – The new provider of child.
-
type
¶ Used by autodoc_mock_imports.
- child_dict (
Views¶
Bin¶
Bin is a view that can hold only one child. The child can be either an View
or
Element
.
-
class
Aduct.Views.Bin.
Bin
(**kwargs)¶ Makes a bin based on given properties. Its CSS name is aduct-bin.
Parameters: **kwargs – The keyword arguments to be passed to Gtk.Bin
from whichBin
is made.-
add_child
(child)¶ Adds the child
self
.Parameters: child ( View
orElement
) – The child to be added toself
.Raises: ValueError
– Raised whenself
already has a child.
-
get_props
()¶ Gets the interface properties.
Returns: A dictionary with interface properties. Return type: dict
-
remove_child
(child)¶ Removes the given child.
Parameters: child ( View
orElement
) – The child which has to be removed fromself
.Raises: ValueError
– Raised whenchild
is not present inself
.
-
replace_child
(old_child, new_child)¶ Replaces the existing child with a new child.
Parameters:
-
set_from_props
(props)¶ Sets the interface from given properties.
Parameters: props ( dict
) – The dictionary containig properties of interface.
-
type
¶ Used by autodoc_mock_imports.
-
Notebook¶
Notebook is a view that can hold only children of type Element
. With this
restriction, there is no limitation in number of children it can hold.
-
class
Aduct.Views.Notebook.
Notebook
(**kwargs)¶ Makes a notebook based on given properties. Its CSS name is aduct-notebook.
Parameters: - **kwargs – The values to be passed to
Gtk.Notebook
from whichNotebook
is made. - Signals –
- action-clicked
- Emitted with an integer when action button of
self
is clicked. The integer is 1, 2, 3 for LMB, MMB, RMB respectively.
-
add_child
(child, position=-1)¶ Adds the child
self
.Parameters: child ( Element
) – The child to be added toself
.Raises: TypeError
– Raised whenchild
is not aElement
.
-
change_child_label
(child)¶ Changes the tab label for existing child.
The text for new label is taken as the name of child of
child
(Aduct.Element.child_name
). It is set to No child when child has no name for its child (Aduct.Element.child_name is None
).Parameters: child ( Element
) – The child whose tab label has to be changed.
Gets the action button at given position.
Parameters: pack_type ( Gtk.PackType
) – The position from which action button has to retrieved.Returns: The action button of self
orNone
ifself
has no action button.Return type: Gtk.Button
orNone
Gets the number of action buttons present.
Returns: The number of action buttons. Return type: int
-
get_props
()¶ Gets the interface properties.
Returns: A dictionary with interface properties. Return type: dict
-
get_tab
(child)¶ Gets a tab label for child.
The text for new label is taken as the name of child of
child
(Aduct.Element.child_name
). It is set to No child when child has no name for its child (Aduct.Element.child_name is None
). Based on position of tabs inself
, the orientation of text in the label also varies.Parameters: child ( Element
) – The child which requires a tab label.Returns: Label with text determined from child
.Return type: Gtk.Label
-
remove_child
(child)¶ Removes the given child from
self
.Parameters: child ( Element
) – The child which has to be removed fromself
.Raises: ValueError
– Raised whenchild
is not present inself
.
-
replace_child
(old_child, new_child)¶ Replaces the existing child with a new child.
Parameters: Raises:
Sets the action button to notebook.
Parameters: - action_button (
Gtk.Button
) – The button to be added to notebook. It need not be aGtk.Button
actually, it could be any widget that can handle button-press-event. - pack_type (
Gtk.PackType
) – The position of the action button.
- action_button (
-
set_from_props
(props)¶ Sets the interface from given properties.
Parameters: props ( dict
) – The dictionary containig properties of interface.Raises: ValueError
– Raised when there is a mismatch of number of action buttons in properties andself
.
-
type
¶ Used by autodoc_mock_imports.
- **kwargs – The values to be passed to
Paned¶
Paned is a view that can hold two children. The two children can either be View
or
Element
.
-
class
Aduct.Views.Paned.
Paned
(**kwargs)¶ Makes a paned based on given properties. Its default name is aduct-paned
Parameters: **kwargs – The keyword arguments to be passed to Gtk.Paned
from whichPaned
is made.-
add_child
(child, position=0)¶ Adds the child to paned.
When
position
is 1 or 2,child
is added at panel 1 or 2 ofself
respectively. When it is 0,child
is added to the first available panel. When it is neither of specified values, nothing is done.Parameters: Raises: ValueError
– Raised whenself
already has a child.
-
get_props
()¶ Gets the interface properties.
Returns: A dictionary with interface properties. Return type: dict
-
remove_child
(child)¶ Removes the given child from
self
.Parameters: child ( View
orElement
) – The child which has to be removed fromself
.Raises: ValueError
– Raised whenchild
is not present inself
.
-
replace_child
(old_child, new_child)¶ Replaces the existing child with a new child.
Parameters: Raises: ValueError
– Raised whenold_child
is not inself
.
-
set_from_props
(props)¶ Sets the interface from given properties.
Parameters: props ( dict
) – The dictionary containig properties of interface.
-
type
¶ Used by autodoc_mock_imports.
-
View¶
View can hold children of type Element
. In some case, there is a
restriction on number of children it can hold.
-
class
Aduct.Views.View.
View
(**kwargs)¶ This an abstract class, that gives an idea of methods a
View
must have. Unless otherwise stated, all the description of methods are generalised expected behavior ofView
. Depending upon the nature of view, the type of child it can hold also varies.-
add_child
(child)¶ Adds the child
self
.Parameters: Raises: ValueError
– Raised when there is insufficient information to addchild
toself
.TypeError
– Raised whenchild
is of invalid type.
Note
When there is a lack of information to add
child
,self
may try its best to addchild
in suitable position.
-
get_props
()¶ Gets the interface properties.
Returns: A dictionary with interface properties. Return type: dict
-
get_type
()¶ Gets the interface properties.
Returns: A dictionary with interface properties. Return type: dict
-
remove_child
(child)¶ Removes the given child.
Parameters: child ( View
orElement
) – The child which has to be removed fromself
.Raises: ValueError
– Raises whenchild
is not present inself
.
-
replace_child
(old_child, new_child)¶ Replaces the existing child with new child.
Parameters:
-
Provider¶
Provider acts as a producer of widgets that are placed as child in Element
.
-
class
Aduct.Provider.
Provider
(*args, **kwargs)¶ This a template that gives an idea of methods a
Provider
must have. Unless otherwise stated, all the description of methods are generalised expected behavior of aProvider
.-
clear_child
(child_dict)¶ Clears the given child.
Parameters: child_dict ( dict
) – A dictionary with properties of the child.
-
get_a_child
(child_name)¶ Gets a child with given name.
Parameters: child_name ( str
) – The name of child to be retrieved.Returns: A dictionary with properties of child. Return type: dict
-
get_child_from_props
(props)¶ Gets a child based on given interface properties.
Parameters: props ( dict
) – The interface properties for child.Returns: A dictionary with properties of child. Return type: dict
-
get_child_props
(child_name, child, header_child)¶ Gets the interface properties from given values.
Parameters: - child_name (
str
) – The name of child. - child (
Gtk.Widget
) – The child produced byself
. - header_child (
Gtk.Widget
) – The header child produced byself
.
Returns: A dictionary with interface properties of child.
Return type: - child_name (
-
Functions¶
-
Aduct.
add_to_notebook
(element, notebook, position=-1)¶ Adds an element to the notebook at given position.
When the given
element
is already a child of some container, the function removes it from the parent and adds thenotebook
to parent. Then the orphanelement
is added tonotebook
atposition
.Parameters: Raises:
-
Aduct.
add_to_paned
(child1, child2, paned, position)¶ Adds the children to given paned determined by position.
The main child
child1
is added at first panel ifposition
is 1 or second panel ifposition
is 2. With respect to position ofchild1
,child2
is added at the complement panel. Whenposition
is neither 1 nor 2, nothing is done. Ifchild1
is already a child of parent, it is removed from the parent andpaned
is added back in its position. Then, the orphanschild1
andchild2
are added at requred places.Parameters: - child1 (
Gtk.Widget
) – The main child which has to be added at givenposition
. - child2 (
Gtk.Widget
) – The other child which has to be added at the complement of givenposition
. It has to be an orphan. - paned (
Paned
) – The paned to which the children has to be added. - position (
int
) – An integer value that is either 1 or 2. The complement of 1 is 2 and vice-versa.
- child1 (
-
Aduct.
add_to_view
(child, view)¶ Adds a child to the view.
If
child
is not an orphan, it is removed from its parent andview
is added back in its place. Thenchild
is added toview
.Parameters: - child (
Gtk.Widget
or Aduct.Element.Element) – The child that has to be added toview
. It has to be an orphan. - view (
View
) – The view to whichchild
has to be added.
Warning
This function is given as a fall-back case and should never be used blindly without knowing the properties of
child
andview
. When theview
already has a child or requires more information about adding it, exceptions are raised. Still, theview
may try its best to add thechild
at the possible place, only when it can.Incase of
view
being aNotebook
, it appends thechild
to the last position. But it requireschild
to be aElement
. So at either case, you still have limitations that may end up in a weird result. For the same reasons,child
has to be an orphan.- child (
-
Aduct.
change_child_at_element
(element, provider, child_name)¶ Changes the child at given element with a child of given name provided by provider.
The previous child at
element
is cleared, after which the new child is added.Parameters:
-
Aduct.
get_interface
(top_level)¶ Gets the interface starting from the given top level.
Parameters: top_level ( View
) – A view which acts as the root widget.Returns: A dictionary with properties to build interface. Return type: dict
-
Aduct.
remove_element
(element, view)¶ Removes the given element from view.
When
view
is aBin
, it clearselement
. For other types of views, it removeselement
. Then, if the number of children inview
is one, it replacesview
with the other child ofview
.Parameters:
-
Aduct.
replace_child
(view, child1, child2)¶ Replaces the given child of a view with another child.
Parameters: - view (
View
) – The view whose child has to be replaced. - child1 (
Gtk.Widget
) – The child of given view which has to be replaced. - child2 (
Gtk.Widget
) – The child which replaces child1 inview
.
Raises: - view (
-
Aduct.
set_interface
(interface_dict, top_level, creator_maps, init_maps)¶ Sets the interface starting from given the top level.
Parameters: - interface_dict (
dict
) – A dictionary that can be used to set interface. - top_level (
View
) – The root widget from which the interface has to be set. - creator_maps (
dict
) – A dictionary of format{key: (func, args, kwargs)}
, that is used to create the required object. The object is then created usingfunc(*args, **kwargs)
and is substitued as value ininterface_dict
which has keykey
. - init_maps (
dict
) – A dictionary of format{key: object}
already initialized objects. The occurences ofkey
ininerface_dict
is then replaced withobject
.
Returns: The widget that was previous child of
top_level
,None
iftop_level
has no child.Return type: - interface_dict (