Yet another pythonic UI library for rapid prototyping using PyQt5.
import qutie as ui
app = ui.Application()
window = ui.Widget(
title="Example",
icon='orange',
width=320,
height=240,
layout=ui.Column(
ui.Label("Hello world!"),
ui.Row(
ui.Button("Go!", clicked=lambda: ui.show_info(text="Hello world!")),
ui.Button("Quit", clicked=app.quit)
)
)
)
window.show()
app.run()
Qutie (pronounced as cutie) provides a simple and easy to use pythonic interface to PyQt5.
pip install qutie
A single Application
object must be created before other widgets. To make use
of the event system the application event loop must be executed.
import qutie as ui
# Create an application object.
app = ui.Application(name='app', version='1.0')
# Create a window.
window = ui.MainWindow()
window.resize(800, 600)
window.show()
# Run the event loop.
app.run()
Any widget can be a top level window or part of another widget using the
layout
property. All properties can be assigned using the constructor.
window = ui.Widget(title="Example", width=320, height=240)
To make a top level window visible use property visible
or call method
show()
.
window.show()
window.visible = True # equivalent to show
The simplified layout system provides a horizontal Row
and a vertical Column
box. Items can be added while constructing the layout or using list like methods
append
and insert
. The consumed space of every child widget can be adjusted
using the stretch
attribute.
window.layout = ui.Row(
ui.Column(
...
),
ui.Column(
ui.Row(...),
ui.Row(...),
ui.Row(...),
stretch=(1, 0, 0)
),
stretch=(2, 3)
)
# Single line text input
text = ui.Text(value="spam")
# Numeric input
number = ui.Number(value=4, minimum=0, maximum=10, step=1.0, decimals=1)
# A multi line text area
textarea = ui.TextArea(value="Lorem ipsum et dolor.")
Events provide a simplified interface to Qt's signal and slot system. Events can
be emitted from any class inheriting from Object
by calling method emit()
.
# Use any callable class attribute as event callback.
window.issue_call = lambda: print("Call to action!")
# Emit an event executing attribute `issue_call` (if callable).
window.emit('issue_call')
Events can also propagate positional and keyword arguments.
# Use any callable class attribute as event callback.
window.update_progress = lambda a, b: print(f"Progress: {a} of {b}")
# Emit an event executing attribute `update_progress` (if callable).
window.emit('update_progress', 42, 100)
Many widgets provide predefined events.
# Assigning callback functions
ui.Number(value=4, changed=on_change, editing_finished=on_edited)
Call repeating or delayed events using timers.
timer = ui.Timer(interval=1.0, timeout=lambda: print("Done!"))
timer.start()
Function single_shot
exposes a convenient single shot timer.
ui.single_shot(interval=1.0, timeout=lambda: print("Done!"))
Note that timer events are only processed when running the application event loop.
Persistent settings can be stored/restored using a Settings
object as context
manager. It provides application wide settings as a JSON dictionary.
with ui.Settings() as settings:
value = settings.get('key', 'default')
settings['key'] = value
Use attribute filename
to inspect the persistent JSON data.
>>> ui.Settings().filename
'/home/user/.config/app.qutie'
Menu bars and menus behave like python lists.
window = ui.MainWindow()
file_menu = window.menubar.append("&File")
quit_action = file_menu.append("&Quit")
quit_action.triggered = window.close
foo_menu = window.menubar.insert(window.menubar.index(file_menu), "&Foo")
file_menu = window.menubar.remove(file_menu)
Toolbars also behave like python lists, the main window toolbars property behaves like a set.
window = ui.MainWindow()
toolbar = window.toolbars.add("toolbar")
toolbar.append(quit_action)
toolbar.insert(quit_action)
window.toolbars.remove(toolbar)
The Worker
class provides a convenient way to work with background threads.
Use attribute target
to assign the function to be executed in the background.
def calculate(worker):
for i in range(100):
...
worker = ui.Worker(target=calculate)
worker.start()
Important: use only the event system to propagate information from inside the worker. Do not access widgets from within the worker function.
def calculate(worker):
for i in range(100):
# Emit custom events.
worker.emit('progress', i, 100)
worker.emit('message', "All ok...")
worker = ui.Worker(target=calculate)
# Assign custom event callbacks.
worker.progress = lambda step, max: print(f"progress: {step}/{max}")
worker.message = lambda msg: print(f"message: {msg}")
worker.start()
To control worker lifetime use method stop()
and attribute stopping
.
def calculate(worker):
while not worker.stopping:
...
worker = ui.Worker(target=calculate)
worker.start()
...
worker.stop()
To wait for a worker to actually stop use method join()
.
worker.stop()
worker.join()
A simple dialog with progress bar running a calculation in the background.
import random
import time
import qutie as ui
class Dialog(ui.Dialog):
def __init__(self, **kwargs):
super().__init__(**kwargs)
# Create worker
self.worker = ui.Worker(target=self.calculate)
self.worker.finished = self.close
self.worker.update_progress = self.update_progress
# Create layout
self.progress_bar = ui.ProgressBar()
self.layout = self.progress_bar
def run(self):
# Start, stop and join worker
self.worker.start()
super().run()
self.worker.stop()
self.worker.join()
def update_progress(self, value, maximum):
self.progress_bar.maximum = maximum
self.progress_bar.value = value
def calculate(self, worker):
n = 32
for i in range(n):
if worker.stopping:
break
# Emit custom event
worker.emit('update_progress', i, n)
time.sleep(random.random())
app = ui.Application()
dialog = Dialog(title="Worker")
dialog.run()
Any underlying PyQt5 instance can be accessed directly using property qt
.
This also enables to mix in custom PyQt5 classes and instances.
widget.qt.setWindowTitle("Spam!")
widget.qt.customContextMenuRequested.connect(lambda pos: None)
widget.qt.layout().addWidget(QtWidgets.QPusbButton())
Qutie is licensed under the GNU General Public License Version 3.