Skip to content

arnobaer/qutie

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

79 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Qutie

Yet another pythonic UI library for rapid prototyping using PyQt5.

Quick start

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()

Documentation

Qutie (pronounced as cutie) provides a simple and easy to use pythonic interface to PyQt5.

Install

pip install qutie

Application

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()

Widgets

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

Layouts

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)
)

Inputs

# 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

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)

Timers

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.

Settings

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'

Menus

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

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)

Workers

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()

Example

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()

Something missing?

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())

License

Qutie is licensed under the GNU General Public License Version 3.