GUI v Pythonu: PyQt5

Způsobů, jak dělat v Pythonu aplikace s GUI, je mnoho. Dá se použít zabudovaný, ale ošklivý Tkinter, nebo nějaký externí framework.

V tomto cvičení budeme používat framework Qt, protože je multiplatformní, používá se i v jiných oblastech, než je Python, je dostatečně robustní a dá se na většinu systémů nainstalovat bez větších problémů.

Pomocí aplikace Qt Designer se dá navíc základní kostra GUI poměrně jednoduše naklikat, takže není nutné psát layout aplikace v kódu.

Instalace

Na tomto cvičení budete potřebovat balíček PyQt5 a aplikaci Qt5 Designer. Pokud budete používat svůj počítač, prosíme vás o instalaci již předem, na cvičení toho bude opravdu hodně a nemůžeme si dovolit plýtvat časem.

PyQt5

Pokud máte Python 3.5 a jednu z platforem, pro které je připraven wheel na PyPI, stačí udělat:

(__venv__) $ python -m pip install --upgrade pip
(__venv__) $ python -m pip install PyQt5

Pro starší verzi Pythonu nebo 32bitový Linux to ale nebude fungovat. V takovém případě můžete PyQt5 zkusit najít v balíčkovacím systému vaší distribuce (např. balíček python3-qt5 ve Fedoře nebo python3-pyqt5 v Debianu). Virtualenv pak může vytvořit s přepínačem --system-site-packages, který zajistí, že i z virtualenvu uvidíte PyQt5 nainstalované z distribučního balíčku.

$ python3 -m venv --system-site-packages __venv__

Pokud nic z toho nepomůže, můžete zkusit přeložit PyQt5 ze zdrojových souborů (návod).

První aplikace níže by vám měla fungovat.

Pokud narazíte na chybu Could not find or load the Qt platform plugin "xcb", podívejte se do naší issue.

Qt5 Designer

Na Linuxu najdete Qt5 Designer v balíčkách, třeba qt5-designer na Fedoře nebo qttools5-dev-tools na Debianu.

Na Windows (i na Macu) si můžete stáhnout instalátor Qt 5, který (doufáme) nainstaluje i Designer.

Pokud používáte na Macu homebrew, můžete to udělat i takto:

$ brew install qt5
$ brew linkapps qt5

Existují i Python wheely pyqt5-tools pro Windows obsahující Qt5 Designer. Ten je pak potřeba pro spuštění dohledat v nainstalované lokaci.

NumPy

Do virtuálního prostředí s PyQt5 si nainstalujte i NumPy:

$ python -m pip install numpy

První aplikace

Napište si první aplikaci, ať vidíte, jak kód v PyQt vypadá. Detaily toho, jak to funguje, si ukážeme později.

from PyQt5 import QtWidgets

app = QtWidgets.QApplication([])

button = QtWidgets.QPushButton("Click to Exit")
button.setWindowTitle("Goodbye World")
button.clicked.connect(app.quit)

button.show()

app.exec()

O Qt, PyQt a PySide

Qt je aplikační framework napsaný v C++, který zjednodušuje psaní multiplatformních aplikací (od počítačů s Linuxem, Mac OS či Windows po různá vestavěná zařízení).

PyQt je knihovna, která umožňuje použít Qt z Pythonu. Na rozdíl od samotného Qt je licencovaná pod GNU GPL v3, která (stručně řečeno) vyžaduje, aby programy napsané s použitím PyQt byly šířeny pod stejnou licencí a se zdrojovým kódem. Tedy: kdokoliv, kdo dostane kopii programu, musí mít možnost dostat odpovídající zdrojový kód a má možnost tento kód dál šířit pod stejnou licencí.

Pokud by se vám tato licence nelíbila, je možnost použít PySide, které má permisivnější licenci a téměř stejné API jako PyQt, ale není tak stabilní.

Moduly Qt

Qt je rozděleno na několik tzv. modulů. Pro grafická uživatelská rozhraní (GUI), kterými se budeme zabývat, použijeme hlavně QtGui a QtWidgets.

Dále je tu modul QtCore, který obsahuje mj. základní datové typy jako QString a QList (které PyQt automaticky převádí na pythonní ekvivalenty a zpět) nebo třeba QRect – abstraktní obdélník.

Další moduly jsou nadstavby od vykreslování SVG nebo práci s multimédii (které se můžou hodit) po třeba práci s SQL a XML nebo síťovou komunikaci, kde je pro Python pohodlnější použít jiné knihovny.

Specifika PyQt

Ačkoli se Qt dá použít z Pythonu, bohužel zjistíte, že ne všechno funguje a vypadá tak, jako kdyby to byla knihovna od základů napsaná pro Python. Tady jsou některé zvláštnosti, na které se můžete připravit.

Jména a dokumentace

Qt pojmenovává funkce, metody a atributy konvencí camelCase, místo pythonistického snake_case. PyQt tuto konvenci nemění: je užitečnější používat identická jména, a kromě toho knihovna PyQt vznikla ještě před PEP 8.

Hledáte-li dokumentaci, doporučuji zadat do vyhledávače qt5 <hledaný objekt>. Dostanete se tak na dokumentaci pro C++ (např. QObject). Hledáte-li pyqt5 <hledaný objekt>, dostanete se k dokumentaci pro Python, která ale většinou jen odkazuje na verzi pro C++ (např. pro QObject).

Rozdíly mezi C a pythonní verzí jsou většinou intuitivní (např. None místo NULL), ale jsou popsány v dokumentaci PyQt.

Atributy

Qt zásadně používá pro přístup k atributům objektů funkce. Funkce pro čtení se typicky jmenuje podle atributu, funkce pro nastavení má předponu set. Namísto pythonního c = obj.color a obj.color = ... tedy použijeme c = obj.color() a obj.setColor(...).

Správa paměti

Python a C++/Qt mají, bohužel, rozdílný přístup ke správě paměti. Python používá reference counting a garbage collection. C++ má objekty s destruktory, což Qt zjednodušuje (alespoň pro C++) stromem vlastnictví.

Základní třída v Qt, ze které dědí téměř všechny ostatní, je QObject. Ten má seznam potomků (children), o které se „stará“, a když uvolníme rodiče, uvolní se rekurzivně i všichni potomci. Z Pythonu pak můžeme dostat chybu wrapped C/C++ object has been deleted. Jinak ale kombinace QObject a pythonních objektů funguje dobře.

Větší problémy můžou nastat s pomocnými objekty, které nedědí z QObject a nemají potřebné „dynamické“ vlastnosti. Takový objekt doporučujeme používat jen v rámci jedné funkce (t.j. neukládat si ho jinde), pokud si nejste jistí že ho „nevlastníte“ i ve smyslu C++/Qt.

Občas se stane, že program spadne pro chybu jako nepovolený přístup do paměti. Bez hlubší znalosti Qt a PyQt se taková chyba odstraňuje poměrně těžko, ale vaše znalosti C++ (z jiných kurzů) a CPython C API (z minula) vám v tom pomůžou. Doporučujeme dělat malé commity a psát jednoduchý kód.

Smyčka událostí, signály a sloty

Qt funguje na principu smyčky událostí (event loop). Metoda QApplication.exec obsahuje v podstatě nekonečnou smyčku, která čeká na externí události (klik myši, žádost OS o vykreslení okna atd.) a na jejich základě volá příslušné funkce – ať už interní nebo námi definované.

Pro komunikaci mezi objekty v rámci aplikace pak Qt používá mechanismus signálů a slotů (variantu observer pattern). Signál je vyslán (emitted) při události jako kliknutí na tlačítko, výběr položky z menu, zavření okna atp. K signálu může být připojeno několik slotů, což jsou funkce, které se po vyslání signálu zavolají. Kód, který vysílá signál, obecně neví o tom, kolik slotů je připojeno (a jsou-li nějaké).

V C++ jsou signály a sloty vždy staticky nadefinované na nějaké třídě, která dědí z QObject. V PyQt takto musí být nadefinovány jen signály; za slot poslouží jakákoli pythonní funkce.

V příkladu výše jsme připojili signál clicked tlačítka na slot quit aplikace. Stejně bychom mohli připojit jakoukoli funkci/metodu, která bere správný počet argumentů – v následujícím případě nula:

    button.clicked.connect(lambda: print('Exiting program'))

V C++ je časté přetěžování funkcí (včetně signálů), což Pythonistům občas ztěžuje život. PyQt většinou automaticky vybere variantu signálu podle připojené funkce, ale ne vždy je to možné.

Ukažme si to na následujícím kódu, který napojuje funkci print na dvě varianty signálu QComboBox.activated. Ten se vyšle při výběru položky ze seznamu buď jako QComboBox.activated[int], kdy předává index vybrané položky, nebo jako QComboBox.activated[str], kdy předává text položky:

from PyQt5 import QtWidgets

app = QtWidgets.QApplication([])

# QComboBox - políčko pro výběr z několika možností
box = QtWidgets.QComboBox()
box.addItem('First Option')
box.addItem('Second Option')

# Základní varianta napojí na activated[int]
box.activated.connect(print)

# Výběr varianty signálu pomocí hranatých závorek
box.activated[str].connect(print)
box.activated[int].connect(print)

box.show()

app.exec()

Skládání GUI

Základní způsob, jak v Qt vytvářet grafické rozhraní, je skládání funkčních prvků (widgets) do hierarchie oken, skupin a panelů.

Základní třída pro funkční prvky, QWidget, dědí z už zmíněného QObject. Každý QObject může obsahovat potomky (children), a v případě QWidget se potomci vykreslují jako součást svého rodiče. Navíc může mít každý widget tzv. layout, který určuje pozici a velikost widgetů, které jsou do něj přidané.

Ukažme to v kódu:

from PyQt5 import QtWidgets

app = QtWidgets.QApplication([])

# Hlavní okno
main = QtWidgets.QWidget()
main.setWindowTitle('Hello Qt')

# Layout pro hlavní okno
layout = QtWidgets.QHBoxLayout()
main.setLayout(layout)

# Nápis
label = QtWidgets.QLabel('Click the button to change me')
# Přidáním do layoutu se nápis automaticky stane potomkem hlavního okna
layout.addWidget(label)

# Tlačítko
button = QtWidgets.QPushButton('Click me')
layout.addWidget(button)

# Funkcionalita
def change_label():
    label.setText('Good job. +100 points.')

button.clicked.connect(change_label)

# Spuštění
main.show()
app.exec()

Zabudovaných layoutů i widgetů existuje spousta, jednodušší programy stačí „poskládat“ z nich a napojit je na logiku. Pro složitější programy jsou pak možnosti, jak si widgety přizpůsobit.

Qt Designer

Na tomto cvičení si připravíme aplikaci pro editaci dlaždicových map (tile maps) – obrázků složených z omezené nabídky čtverečků („dlaždic“), známých mj. ze starých her.

Aplikace bude mít část s mapou, paletu pro výběr dlaždice ke kreslení a navíc menu a panel nástrojů pro akce jako ukládání a otevírání souborů:

Obrázek aplikace

Vytvářet GUI v kódu je poměrně neefektivní, a tak existuje nástroj, kde si okna můžeme „naklikat“. Jmenuje se Qt Designer a měli byste ho mít nainstalovaný. Na školních počítačích se spouští příkazem designer -qt=5.

Spustíme Designer a vytvoříme v něm nové Main Window. Do něj si z palety přidáme Scroll Area a doprava vedle něj List Widget. Poté aplikujeme layout: na volnou plochu okna klikneme pravým tlačítkem a vybereme Lay Out ‣ Horizontally. (Dá se to udělat i tlačítkem v liště.)

Pomocí Ctrl+R lze zkontrolovat, jak okno vypadá a jak reaguje na změny velikosti.

Potom přidáme položku do menu: místo Type Here napíšeme Map a pod něj podobně přidáme položky New a Quit.

V panelu Property Editor jde měnit vlastnosti jednotlivých prvků. U skrolovacího okna nastavíme objectName na scrollArea. U ListWidget nastavíme objectName na palette a sizePolicy ‣ Horizontal na Preferred. V panelu ActionEditor najdeme položky pro New a Quit a nastavíme jim objectName na actionNew, resp. actionQuit.

Potom přes pravé tlačítko na nevyužité ploše okna přidáme lištu nástrojů (Add Toolbar) a z panelu Action Editor do něj akci actionQuit přetáhneme.

Pomocí Ctrl+R opět zkontrolujeme, jak okno vypadá a jak po nastavení sizePolicy reaguje na změny velikosti.

V Designeru jde i napojovat signály. V panelu Signal/Slot Editor přidáme tento řádek:

  • Sender: actionQuit
  • Signal: triggered()
  • Receiver: MainWindow
  • Slot: close()

Pomocí Ctrl+R jde ověřit, že zavírání okna funguje.

Návrh okna uložíme do souboru mainwindow.ui.

Soubor s návrhem jde převést na pythonní zdrojový soubor pomocí programu pyuic5 nebo ho vždy načíst přímo z programu. My použijeme druhou variantu, je však dobré o pyuic5 vědět, kdybyste někdy potřebovali základ pro vytváření UI v kódu (např. na vytvoření sady několika podobných tlačítek v cyklu).

Načíst .ui soubor z programu do předpřipraveného okna QMainWindow lze pomocí funkce uic.loadUi:

from PyQt5 import QtWidgets, uic

def main():
    app = QtWidgets.QApplication([])

    window = QtWidgets.QMainWindow()

    with open('mainwindow.ui') as f:
        uic.loadUi(f, window)

    window.show()

    return app.exec()

main()

Vlastní widget - Grid

Qt neobsahuje předpřipravený widget na dlaždicové mapy. Musíme si tedy vyrobit vlastní.

Mapu budeme reprezentovat jako NumPy matici (viz lekce o NumPy). Zatím budeme používat dva druhy dlaždic: trávu (v matici reprezentovanou jako 0) a zeď (-1).

Velikost widgetu se zadává v pixelech. Musíme ho udělat dostatečně velký, aby se do něj vešla všechna políčka mapy. Velikost jednoho políčka v pixelech zvolíme pro jednoduchost konstantou.

Souřadnice v Qt jsou v pixelech ve formě (x, y) – klasicky jak jsme zvyklí, x je horizontální souřadnice – kdežto matice je uložená po políčkách (řádek, sloupec). Abychom se v tom neztratili, je dobré hned ze začátku udělat funkce pro převod mezi souřadnými systémy a důsledně rozlišovat (x, y) vs. (row, column).

CELL_SIZE = 32


def pixels_to_logical(x, y):
    return y // CELL_SIZE, x // CELL_SIZE


def logical_to_pixels(row, column):
    return column * CELL_SIZE, row * CELL_SIZE


class GridWidget(QtWidgets.QWidget):
    def __init__(self, array):
        super().__init__()  # musíme zavolat konstruktor předka
        self.array = array
        # nastavíme velikost podle velikosti matice, jinak je náš widget příliš malý
        size = logical_to_pixels(*array.shape)
        self.setMinimumSize(*size)
        self.setMaximumSize(*size)
        self.resize(*size)

GridWidget vložíme do QScrollArea, kterou jsme si vytvořili v Qt Designeru:

import numpy

    ...

    # mapa zatím nadefinovaná rovnou v kódu
    array = numpy.zeros((15, 20), dtype=numpy.int8)
    array[:, 5] = -1  # nějaká zeď

    # získáme oblast s posuvníky z Qt Designeru
    scroll_area = window.findChild(QtWidgets.QScrollArea, 'scrollArea')

    # dáme do ní náš grid
    grid = GridWidget(array)
    scroll_area.setWidget(grid)

    ...

Po spuštění aplikace zatím nic nového neuvidíte, maximálně se trochu změní posuvníky. Potřebujeme ještě zařídit, aby se data z matice vykreslovala do gridu. Nejlepší je vykreslovat, kdykoliv nás OS (nebo Qt) vyzve, že potřebuje kus okna překreslit: při prvním zobrazení, odminimalizování okna, ukázání nové části mapy přes scrollování. Také je zbytečné vykreslovat obrázky mimo oblast, kterou je vidět na obrazovce.

K tomuto účelu nám poslouží událost (event). Jak bylo řečeno v úvodu, na rozdíl od signálů a slotů, které zajišťují komunikaci v rámci aplikace, události vznikají mimo aplikaci. Jde například o kliknutí myší (mouse*Event), vstup z klávesnice (key*Event) nebo žádost OS o překreslení okna (paintEvent). Na poslední jmenovanou událost, paintEvent, teď budeme reagovat.

Události se obsluhují předefinováním příslušné metody, která jako argument bere objekt popisující danou událost.

V rámci reakce na událost paintEvent můžeme používat QPainter, objekt, který generalizuje kreslení na různé „povrchy“ jako widgety, obrázky, nebo i instrukce pro tiskárnu.

from PyQt5 import QtWidgets, QtGui, QtCore, uic


class GridWidget(QtWidgets.QWidget):

    ...

    def paintEvent(self, event):
        rect = event.rect()  # získáme informace o překreslované oblasti

        # zjistíme, jakou oblast naší matice to představuje
        # nesmíme se přitom dostat z matice ven
        row_min, col_min = pixels_to_logical(rect.left(), rect.top())
        row_min = max(row_min, 0)
        col_min = max(col_min, 0)
        row_max, col_max = pixels_to_logical(rect.right(), rect.bottom())
        row_max = min(row_max + 1, self.array.shape[0])
        col_max = min(col_max + 1, self.array.shape[1])

        painter = QtGui.QPainter(self)  # budeme kreslit

        for row in range(row_min, row_max):
            for column in range(col_min, col_max):
                # získáme čtvereček, který budeme vybarvovat
                x, y = logical_to_pixels(row, column)
                rect = QtCore.QRectF(x, y, CELL_SIZE, CELL_SIZE)

                # šedá pro zdi, zelená pro trávu
                if self.array[row, column] < 0:
                    color = QtGui.QColor(115, 115, 115)
                else:
                    color = QtGui.QColor(0, 255, 0)

                # vyplníme čtvereček barvou
                painter.fillRect(rect, QtGui.QBrush(color))

Nyní by již mapa měla být v okně vidět barevně.

Obrázky

Protože barvičky jsou příliš nudné, přidáme do mapového widgetu obrázky.

Veškerou, ke cvičení i k úkolu potřebnou, grafiku najdete na GitHubu. Je k dispozici pod public domain (tj. „dělej si s tím, co chceš“), pochází ze studia Kenney a je (společně se další volně licencovanou grafikou) ke stažení z OpenGameArt.org.

Zatím budeme potřebovat jen dva obrázky:

Nejprve si načteme SVG soubory jako objekty QSvgRenderer:

from PyQt5 import QtWidgets, QtCore, QtGui, QtSvg, uic

SVG_GRASS = QtSvg.QSvgRenderer('grass.svg')
SVG_WALL = QtSvg.QSvgRenderer('wall.svg')

A poté je na správných místech vyrendrujeme:

                ...
                rect = QtCore.QRectF(x, y, CELL_SIZE, CELL_SIZE)

                # podkladová barva pod poloprůhledné obrázky
                white = QtGui.QColor(255, 255, 255)
                painter.fillRect(rect, QtGui.QBrush(white))

                # trávu dáme všude, protože i zdi stojí na trávě
                SVG_GRASS.render(painter, rect)

                # zdi dáme jen tam, kam patří
                if self.array[row, column] < 0:
                    SVG_WALL.render(painter, rect)

Model/View

Nyní trochu odbočíme a povíme si krátce o dalším podsystému Qt: o modelech.

Qt obsahuje framework, který mapuje informace do podoby tabulek, seznamů nebo obecných stromů. Vzniklé modely se potom dají zobrazit ve specializovaných widgetech. Samotná data můžou být uložena kdekoli – v paměti, SQL databázi, souborech a podobně. Dokonce nemusí být všechna dostupná: existuje vestavěný model pro souborový systém, který se dá zobrazit aniž by se procházely všechny soubory. Když je informace potřeba, model se postará o její načtení. Pomocí modelů a modelových widgetů lze informace i měnit, a pokud je model zobrazen ve více widgetech zároveň, změny se projeví ve všech.

Obecné modely je bohužel relativně obtížné implementovat v Pythonu, protože používají třídy, které nedědí z QObject, takže je potřeba sledovat, jestli je „vlastní“ Python nebo C++. Naštěstí ale existují widgety se zabudovanými modely, které obsahují i samotná data. Tyto modely je složitější napojit na existující aplikační logiku, ale pro většinu účelů postačí.

O obecných modelech si můžete přečíst v dokumentaci.

QListWidget - Paleta

Jeden z widgetů se zabudovaným modelem je QListWidget, který umí spravovat a zobrazovat nějaký seznam. My jsme si v Qt Designeru připravili QListWidget s názvem palette, který použijeme jako paletu jednotlivých dílků, které budeme moci vkládat do mapy. Položky se do tohoto modelu přidávají následovně:

def main():
    ...

    # získáme paletu vytvořenou v Qt Designeru
    palette = window.findChild(QtWidgets.QListWidget, 'palette')

    item = QtWidgets.QListWidgetItem('Grass')  # vytvoříme položku
    icon = QtGui.QIcon('grass.svg')  # ikonu
    item.setIcon(icon)  # přiřadíme ikonu položce
    palette.addItem(item)  # přidáme položku do palety

Stejným způsobem lze do palety přidat další položky: kromě trávy budeme na toto cvičení potřebovat i stěnu. Protože v úkolu bude položek více, je lepší si na to vytvořit funkci či metodu. To necháme na vás.

Zatím jsme vytvořili paletu, ve které uživatel může položky vybírat. Výběr položky aktivuje signál itemSelectionChanged, na který můžeme navázat volání funkce. (Pokud bychom měli pod kontrolou třídu widgetu, jako tomu je u třídy Grid, mohli bychom místo toho i předefinovat metodu itemSelectionChanged().)

def main():
    ...

    def item_activated():
        """Tato funkce se zavolá, když uživatel zvolí položku"""

        # Položek může obecně být vybráno víc, ale v našem seznamu je to
        # zakázáno (v Designeru selectionMode=SingleSelection).
        # Projdeme "všechny vybrané položky", i když víme že bude max. jedna.
        for item in palette.selectedItems():
            row_num = palette.indexFromItem(item).row()
            print(row_num)

    palette.itemSelectionChanged.connect(item_activated)

Nyní, když uživatel zvolí položku, vypíše se do konzole její pořadí. Nás by ale spíš zajímalo, jak bude tato položka reprezentována v matici s mapou. K položce v paletě můžeme uložit informace pomocí item.setData(<role>, <data>). Rolí pro informace je spousta a několik z nich Qt používá pro vykreslování. Pro vlastní data můžeme použít QtCore.Qt.UserRole. V případě potřeby ukládat více dat můžeme dále zvolit QtCore.Qt.UserRole + 1 atd. Pro případ, že budeme potřebovat rolí víc, je dobré si je vhodně pojmenovat.

VALUE_ROLE = QtCore.Qt.UserRole

def main():
    ...
    self.palette.addItem(item)  # přidáme položku do palety
    item.setData(VALUE_ROLE, -1)  # přiřadíme jí data
    ...

    def item_activated():
        for item in palette.selectedItems():
            print(item.data(VALUE_ROLE))  # čteme data stejné role z položky

Nyní byste měli mít v paletě trávu a stěnu s patřičnými čísly (0 a -1), které se vypisují do konzole při zvolení položky.

Nakonec si číslo místo vypisování uložíme do gridu, abychom ho mohli později použít.

    def item_activated():
        for item in palette.selectedItems():
            grid.selected = item.data(VALUE_ROLE)

Klikání do gridu

Nyní nezbývá nic jiného, než pomocí klikání nanášet zvolené dílky do mapy. K tomu opět použijeme událost, tentokrát událost kliknutí, tedy mousePressEvent.

class GridWidget(QtWidgets.QWidget):
    ...

    def mousePressEvent(self, event):
        # převedeme klik na souřadnice matice
        row, column = pixels_to_logical(event.x(), event.y())

        # Pokud jsme v matici, aktualizujeme data
        if 0 <= row < self.array.shape[0] and 0 <= column < self.array.shape[1]:
            self.array[row, column] = self.selected

            # tímto zajistíme překreslení widgetu v místě změny:
            # (pro Python 3.4 a nižší volejte jen self.update() bez argumentů)
            self.update(*logical_to_pixels(row, column), CELL_SIZE, CELL_SIZE)

Zde víme, že kliknutí může změnit vykreslenou mapu pouze v místě kliknutí. Pokud by ale kliknutí na určité políčko mohlo změnit obsah mapy někde jinde, je lepší zavolat self.update() bez argumentů a říct tak systému, že se má překreslit celý widget.

Protože po spuštění aplikace není zvolena žádná položka a self.selected není definován, je rozumné prostě nějakou položku zvolit:

# Za přidáním položek do palety a napojení signálu
palette.setCurrentRow(1)

Více tlačítek myši

Můžete si vyzkoušet, že se mapa mění při použití jakéhokoliv tlačítka myši. Je to proto, že mousePressEvent se stane, kdykoli na widgetu stiskneme libovolné tlačítko. Pokud bychom chtěli řešit pouze levé (primární) tlačítko, můžeme zjistit, které tlačítko událost vyvolalo:

            if event.button() == QtCore.Qt.LeftButton:
                self.array[row, column] = self.selected
            else:
                return
            self.update(*logical_to_pixels(row, column), CELL_SIZE, CELL_SIZE)

Na pravé tlačítko myši můžeme namapovat funkci mazání:

            elif event.button() == QtCore.Qt.RightButton:
                self.array[row, column] = 0

Tažení myši

Pro splnění úkolu bude stačit objekty mazat a klást pomocí klikání na jednotlivá políčka. Pokud však chcete poskytnout uživateli větší komfort, prozkoumejte další události a můžete políčka nanášet/mazat i při kliknutí a táhnutí. (Možná tu narazíte na problém, kdy se při příliš rychlém pohybu myši generují události pro body příliš daleko od sebe. Když program nestíhá, OS nebo Qt spojuje víc událostí pohybu myši dohromady a posílá jen jednu poslední. Jednotlivé body můžete spojit čárou pomocí knihovny bresenham.)

Menu a modální dialog

Naše aplikace bude umět vytvořit novou, prázdnou mapu. Ukážeme si, jak vytvořit modální dialog pro volby (šířka a výška nové mapy). „Modální dialog“ znamená okno, které musí uživatel zavřít, než může pracovat se zbytkem aplikace.

Layout okna nejprve naklikáme v Qt Designeru:

  1. Po spuštění zvolíme Dialog with Buttons Bottom a Create.
  2. Přes pravé tlačítko pro dialog zvolíme Lay Out ‣ Vertically.
  3. Nad tlačítka Cancel a OK přetáhneme z Widet Box Form Layout (layouty lze takto přímo vnořovat).
  4. Do něj přetáhneme postupně dvakrát Label a Spin Box, abychom vytvořili formulář.
  5. Přejmenujeme v panelu Property Editor jednotlivé přidané položky tak, aby dávaly v kódu smysl (widthBox, heightBox).
  6. Poklikáním na Labely změníme jejich text.
  7. Nastavíme v panelu Property Editor rozumné limity a výchozí hodnoty pro Spin Boxy.
  8. Okno případně zmenšíme, aby nebylo zbytečně velké.
  9. V menu zvolíme Edit ‣ Edit Buddies a táhnutím z Labelu na Spin Box nastavíme, ke kterému prvku se Label vztahuje.
  10. V menu zvolíme Edit ‣ Edit Tab Order a zkontrolujeme, že pořadí, ve kterém bude prvky vybírat klávesa Tab, je rozumné.
  11. Můžeme se vrátit zpět na Edit ‣ Edit Widgets.
  12. Dialog uložíme jako newmaze.ui.

Poté připravíme funkci pro zobrazení dialogu a pro jeho vyhodnocení:

def new_dialog(window, grid):
    # Vytvoříme nový dialog.
    # V dokumentaci mají dialogy jako argument `this`;
    # jde o "nadřazené" okno.
    dialog = QtWidgets.QDialog(window)

    # Načteme layout z Qt Designeru.
    with open('newmaze.ui') as f:
        uic.loadUi(f, dialog)

    # Zobrazíme dialog.
    # Funkce exec zajistí modalitu (tzn. nejde ovládat zbytek aplikace,
    # dokud je dialog zobrazen) a vrátí se až potom, co uživatel dialog zavře.
    result = dialog.exec()

    # Výsledná hodnota odpovídá tlačítku/způsobu, kterým uživatel dialog zavřel.
    if result == QtWidgets.QDialog.Rejected:
        # Dialog uživatel zavřel nebo klikl na Cancel.
        return

    # Načtení hodnot ze SpinBoxů
    cols = dialog.findChild(QtWidgets.QSpinBox, 'widthBox').value()
    rows = dialog.findChild(QtWidgets.QSpinBox, 'heightBox').value()

    # Vytvoření nové mapy
    grid.array = numpy.zeros((rows, cols), dtype=numpy.int8)

    # Mapa může být jinak velká, tak musíme změnit velikost Gridu;
    # (tento kód používáme i jinde, měli bychom si na to udělat funkci!)
    size = logical_to_pixels(rows, cols)
    grid.setMinimumSize(*size)
    grid.setMaximumSize(*size)
    grid.resize(*size)

    # Překreslení celého Gridu
    grid.update()


def main():
    ...


     # Napojení signálu actionNew.triggered

    action = window.findChild(QtWidgets.QAction, 'actionNew')
    action.triggered.connect(lambda: new_dialog(window, grid))

Další dialogy, které budeme potřebovat, jsou tak rozšířené (a mezi jednotlivými platformami tak různé), že je Qt má předpřipravené. Jsou to dialogy pro ukázání hlášky, výběr souboru, barvy nebo fontu nebo pro nastavení tisku.

Tyto předpřipravené dialogy mají typicky statické metody, které dialog vytvoří a přímo zavolají exec() a vrátí výsledek.

Pro splnění úkolu (dalších položek v menu) se vám můžou hodit tyto dialogy:

Třída pro GUI aplikace

Funkce main se nám pomalu rozrůstá a další funkce, které volá, musí být buď definované v ní (jako item_activated) nebo musí brát relativně hodně argumentů (jako new_dialog). Abychom si zjednodušili práci, můžeme logiku místo do funkce dát do třídy, ve které si důležité prvky uložíme do atributů (self.grid, self.window, self.app atd.). Doporučujeme udělat přípravu v __init__ a volání window.show() a return app.exec() dát do metody run.

A to je zatím vše! Další vylepšení budete mít za úkol – nebo si aplikaci přetvořte podle svého uvážení.

{
  "data": {
    "sessionMaterial": {
      "id": "session-material:2017/pyknihovny-brno:qt:0",
      "title": "GUI v Pythonu",
      "html": "\n          \n    \n\n    <h1>GUI v Pythonu: PyQt5</h1>\n<p>Zp&#x16F;sob&#x16F;, jak d&#x11B;lat v Pythonu aplikace s GUI, je mnoho. D&#xE1; se pou&#x17E;&#xED;t zabudovan&#xFD;, ale o&#x161;kliv&#xFD; Tkinter, nebo n&#x11B;jak&#xFD; extern&#xED; framework.</p>\n<p>V tomto cvi&#x10D;en&#xED; budeme pou&#x17E;&#xED;vat framework Qt, proto&#x17E;e je multiplatformn&#xED;, pou&#x17E;&#xED;v&#xE1; se i v jin&#xFD;ch oblastech, ne&#x17E; je Python,\nje dostate&#x10D;n&#x11B; robustn&#xED; a d&#xE1; se na v&#x11B;t&#x161;inu syst&#xE9;m&#x16F; nainstalovat bez v&#x11B;t&#x161;&#xED;ch probl&#xE9;m&#x16F;.</p>\n<p>Pomoc&#xED; aplikace Qt Designer se d&#xE1; nav&#xED;c z&#xE1;kladn&#xED; kostra GUI pom&#x11B;rn&#x11B; jednodu&#x161;e <em>naklikat</em>, tak&#x17E;e nen&#xED; nutn&#xE9; ps&#xE1;t layout aplikace v k&#xF3;du.</p>\n<h2>Instalace</h2>\n<p>Na tomto cvi&#x10D;en&#xED; budete pot&#x159;ebovat bal&#xED;&#x10D;ek PyQt5 a aplikaci Qt5 Designer.\nPokud budete pou&#x17E;&#xED;vat sv&#x16F;j po&#x10D;&#xED;ta&#x10D;, pros&#xED;me v&#xE1;s o instalaci ji&#x17E; p&#x159;edem, na cvi&#x10D;en&#xED; toho bude opravdu hodn&#x11B; a nem&#x16F;&#x17E;eme si dovolit pl&#xFD;tvat &#x10D;asem.</p>\n<h3>PyQt5</h3>\n<p>Pokud m&#xE1;te Python 3.5 a jednu z platforem, pro kter&#xE9; je p&#x159;ipraven <a href=\"https://pypi.org/project/PyQt5/\">wheel na PyPI</a>, sta&#x10D;&#xED; ud&#x11B;lat:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"gp\">(__venv__) $ </span>python -m pip install --upgrade pip\n<span class=\"gp\">(__venv__) $ </span>python -m pip install PyQt5\n</pre></div><p>Pro star&#x161;&#xED; verzi Pythonu nebo 32bitov&#xFD; Linux to ale nebude fungovat.\nV takov&#xE9;m p&#x159;&#xED;pad&#x11B; m&#x16F;&#x17E;ete PyQt5 zkusit naj&#xED;t v bal&#xED;&#x10D;kovac&#xED;m syst&#xE9;mu va&#x161;&#xED; distribuce (nap&#x159;. bal&#xED;&#x10D;ek <code>python3-qt5</code> ve Fedo&#x159;e nebo <code>python3-pyqt5</code> v Debianu).\nVirtualenv pak m&#x16F;&#x17E;e vytvo&#x159;it s p&#x159;ep&#xED;na&#x10D;em <code>--system-site-packages</code>, kter&#xFD; zajist&#xED;, &#x17E;e i z virtualenvu uvid&#xED;te PyQt5 nainstalovan&#xE9; z distribu&#x10D;n&#xED;ho bal&#xED;&#x10D;ku.</p>\n<div class=\"highlight\"><pre><span></span><span class=\"gp\">$ </span>python3 -m venv --system-site-packages __venv__\n</pre></div><p>Pokud nic z toho nepom&#x16F;&#x17E;e, m&#x16F;&#x17E;ete zkusit p&#x159;elo&#x17E;it PyQt5 ze <a href=\"https://www.riverbankcomputing.com/software/pyqt/download5\">zdrojov&#xFD;ch soubor&#x16F;</a>\n(<a href=\"http://pyqt.sourceforge.net/Docs/PyQt5/installation.html#building-and-installing-from-source\">n&#xE1;vod</a>).</p>\n<p><em>Prvn&#xED; aplikace</em> n&#xED;&#x17E;e by v&#xE1;m m&#x11B;la fungovat.</p>\n<p>Pokud naraz&#xED;te na chybu <code>Could not find or load the Qt platform plugin &quot;xcb&quot;</code>, pod&#xED;vejte se do <a href=\"https://github.com/cvut/MI-PYT/issues/57\">na&#x161;&#xED; issue</a>.</p>\n<h3>Qt5 Designer</h3>\n<p>Na Linuxu najdete Qt5 Designer v bal&#xED;&#x10D;k&#xE1;ch, t&#x159;eba <code>qt5-designer</code> na Fedo&#x159;e nebo <code>qttools5-dev-tools</code> na Debianu.</p>\n<p>Na Windows (i na Macu) si m&#x16F;&#x17E;ete <a href=\"https://www.qt.io/download-open-source/#section-2\">st&#xE1;hnout</a> instal&#xE1;tor Qt 5, kter&#xFD; (douf&#xE1;me) nainstaluje i Designer.</p>\n<p>Pokud pou&#x17E;&#xED;v&#xE1;te na Macu <code>homebrew</code>, m&#x16F;&#x17E;ete to ud&#x11B;lat i takto:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"gp\">$ </span>brew install qt5\n<span class=\"gp\">$ </span>brew linkapps qt5\n</pre></div><p>Existuj&#xED; i Python wheely <a href=\"https://pypi.python.org/pypi/pyqt5-tools\">pyqt5-tools</a> pro Windows obsahuj&#xED;c&#xED; Qt5 Designer.\nTen je pak pot&#x159;eba pro spu&#x161;t&#x11B;n&#xED; dohledat v nainstalovan&#xE9; lokaci.</p>\n<h3>NumPy</h3>\n<p>Do virtu&#xE1;ln&#xED;ho prost&#x159;ed&#xED; s PyQt5 si nainstalujte i NumPy:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"gp\">$ </span>python -m pip install numpy\n</pre></div><h2>Prvn&#xED; aplikace</h2>\n<p>Napi&#x161;te si prvn&#xED; aplikaci, a&#x165; vid&#xED;te, jak k&#xF3;d v PyQt vypad&#xE1;.\nDetaily toho, jak to funguje, si uk&#xE1;&#x17E;eme pozd&#x11B;ji.</p>\n<div class=\"highlight\"><pre><span></span><span class=\"kn\">from</span> <span class=\"nn\">PyQt5</span> <span class=\"kn\">import</span> <span class=\"n\">QtWidgets</span>\n\n<span class=\"n\">app</span> <span class=\"o\">=</span> <span class=\"n\">QtWidgets</span><span class=\"o\">.</span><span class=\"n\">QApplication</span><span class=\"p\">([])</span>\n\n<span class=\"n\">button</span> <span class=\"o\">=</span> <span class=\"n\">QtWidgets</span><span class=\"o\">.</span><span class=\"n\">QPushButton</span><span class=\"p\">(</span><span class=\"s2\">&quot;Click to Exit&quot;</span><span class=\"p\">)</span>\n<span class=\"n\">button</span><span class=\"o\">.</span><span class=\"n\">setWindowTitle</span><span class=\"p\">(</span><span class=\"s2\">&quot;Goodbye World&quot;</span><span class=\"p\">)</span>\n<span class=\"n\">button</span><span class=\"o\">.</span><span class=\"n\">clicked</span><span class=\"o\">.</span><span class=\"n\">connect</span><span class=\"p\">(</span><span class=\"n\">app</span><span class=\"o\">.</span><span class=\"n\">quit</span><span class=\"p\">)</span>\n\n<span class=\"n\">button</span><span class=\"o\">.</span><span class=\"n\">show</span><span class=\"p\">()</span>\n\n<span class=\"n\">app</span><span class=\"o\">.</span><span class=\"k\">exec</span><span class=\"p\">()</span>\n</pre></div><h2>O Qt, PyQt a PySide</h2>\n<p><a href=\"https://www.qt.io/\">Qt</a> je aplika&#x10D;n&#xED; framework napsan&#xFD; v C++, kter&#xFD; zjednodu&#x161;uje psan&#xED; multiplatformn&#xED;ch aplikac&#xED; (od po&#x10D;&#xED;ta&#x10D;&#x16F; s Linuxem, Mac OS &#x10D;i Windows po r&#x16F;zn&#xE1; vestav&#x11B;n&#xE1; za&#x159;&#xED;zen&#xED;).</p>\n<p><a href=\"https://riverbankcomputing.com/software/pyqt\">PyQt</a> je knihovna, kter&#xE1; umo&#x17E;&#x148;uje pou&#x17E;&#xED;t Qt z Pythonu.\nNa rozd&#xED;l od samotn&#xE9;ho Qt je licencovan&#xE1; pod <a href=\"https://www.gnu.org/licenses/gpl-3.0.en.html\">GNU GPL v3</a>, kter&#xE1; (stru&#x10D;n&#x11B; &#x159;e&#x10D;eno) vy&#x17E;aduje, aby programy napsan&#xE9; s pou&#x17E;it&#xED;m PyQt byly &#x161;&#xED;&#x159;eny pod stejnou licenc&#xED; a se zdrojov&#xFD;m k&#xF3;dem.\nTedy: kdokoliv, kdo dostane kopii programu, mus&#xED; m&#xED;t mo&#x17E;nost dostat odpov&#xED;daj&#xED;c&#xED; zdrojov&#xFD; k&#xF3;d a m&#xE1; mo&#x17E;nost tento k&#xF3;d d&#xE1;l &#x161;&#xED;&#x159;it pod stejnou licenc&#xED;.</p>\n<p>Pokud by se v&#xE1;m tato licence nel&#xED;bila, je mo&#x17E;nost pou&#x17E;&#xED;t <a href=\"https://wiki.qt.io/PySide\">PySide</a>, kter&#xE9; m&#xE1; permisivn&#x11B;j&#x161;&#xED; licenci a t&#xE9;m&#x11B;&#x159; stejn&#xE9; API jako PyQt, ale nen&#xED; tak stabiln&#xED;.</p>\n<h3>Moduly Qt</h3>\n<p>Qt je rozd&#x11B;leno na n&#x11B;kolik tzv. <a href=\"http://doc.qt.io/qt-5/qtmodules.html\">modul&#x16F;</a>.\nPro grafick&#xE1; u&#x17E;ivatelsk&#xE1; rozhran&#xED; (GUI), kter&#xFD;mi se budeme zab&#xFD;vat, pou&#x17E;ijeme hlavn&#x11B; <a href=\"http://doc.qt.io/qt-5/qtgui-index.html\">QtGui</a> a <a href=\"http://doc.qt.io/qt-5/qtwidgets-index.html\">QtWidgets</a>.</p>\n<p>D&#xE1;le je tu modul <a href=\"http://doc.qt.io/qt-5/qtcore-index.html\">QtCore</a>, kter&#xFD; obsahuje mj. z&#xE1;kladn&#xED; datov&#xE9; typy jako QString a QList (kter&#xE9; PyQt\nautomaticky p&#x159;ev&#xE1;d&#xED; na pythonn&#xED; ekvivalenty a zp&#x11B;t) nebo t&#x159;eba <a href=\"http://doc.qt.io/qt-5/qrect.html\">QRect</a> &#x2013; abstraktn&#xED; obd&#xE9;ln&#xED;k.</p>\n<p>Dal&#x161;&#xED; moduly jsou nadstavby od <a href=\"http://doc.qt.io/qt-5/qtsvg-index.html\">vykreslov&#xE1;n&#xED; SVG</a> nebo <a href=\"http://doc.qt.io/qt-5/qtmultimedia-index.html\">pr&#xE1;ci s multim&#xE9;dii</a> (kter&#xE9; se m&#x16F;&#x17E;ou hodit) po\nt&#x159;eba pr&#xE1;ci s <a href=\"http://doc.qt.io/qt-5/qtsql-index.html\">SQL</a> a <a href=\"http://doc.qt.io/qt-5/qtxml-index.html\">XML</a> nebo <a href=\"http://doc.qt.io/qt-5/qtnetwork-index.html\">s&#xED;&#x165;ovou komunikaci</a>, kde je pro Python pohodln&#x11B;j&#x161;&#xED; pou&#x17E;&#xED;t jin&#xE9; knihovny.</p>\n<h2>Specifika PyQt</h2>\n<p>A&#x10D;koli se Qt d&#xE1; pou&#x17E;&#xED;t z Pythonu, bohu&#x17E;el zjist&#xED;te, &#x17E;e ne v&#x161;echno funguje a vypad&#xE1; tak, jako kdyby to byla knihovna od z&#xE1;klad&#x16F; napsan&#xE1; pro Python.\nTady jsou n&#x11B;kter&#xE9; zvl&#xE1;&#x161;tnosti, na kter&#xE9; se m&#x16F;&#x17E;ete p&#x159;ipravit.</p>\n<h3>Jm&#xE9;na a dokumentace</h3>\n<p>Qt pojmenov&#xE1;v&#xE1; funkce, metody a atributy konvenc&#xED; <code>camelCase</code>, m&#xED;sto pythonistick&#xE9;ho <code>snake_case</code>.\nPyQt tuto konvenci nem&#x11B;n&#xED;: je u&#x17E;ite&#x10D;n&#x11B;j&#x161;&#xED; pou&#x17E;&#xED;vat identick&#xE1; jm&#xE9;na, a krom&#x11B; toho knihovna PyQt vznikla je&#x161;t&#x11B; p&#x159;ed PEP 8.</p>\n<p>Hled&#xE1;te-li dokumentaci, doporu&#x10D;uji zadat do vyhled&#xE1;va&#x10D;e <code>qt5 &lt;hledan&#xFD; objekt&gt;</code>.\nDostanete se tak na dokumentaci pro C++ (nap&#x159;. <a href=\"http://doc.qt.io/qt-5/qobject.html\">QObject</a>).\nHled&#xE1;te-li <code>pyqt5 &lt;hledan&#xFD; objekt&gt;</code>, dostanete se k dokumentaci pro Python, kter&#xE1; ale v&#x11B;t&#x161;inou jen odkazuje\nna verzi pro C++ (nap&#x159;. <a href=\"http://pyqt.sourceforge.net/Docs/PyQt5/api/qobject.html\">pro QObject</a>).</p>\n<p>Rozd&#xED;ly mezi C a pythonn&#xED; verz&#xED; jsou v&#x11B;t&#x161;inou intuitivn&#xED; (nap&#x159;. None m&#xED;sto NULL), ale jsou pops&#xE1;ny\nv <a href=\"http://pyqt.sourceforge.net/Docs/PyQt5/index.html\">dokumentaci PyQt</a>.</p>\n<h3>Atributy</h3>\n<p>Qt z&#xE1;sadn&#x11B; pou&#x17E;&#xED;v&#xE1; pro p&#x159;&#xED;stup k atribut&#x16F;m objekt&#x16F; funkce.\nFunkce pro &#x10D;ten&#xED; se typicky jmenuje podle atributu, funkce pro nastaven&#xED; m&#xE1; p&#x159;edponu <code>set</code>.\nNam&#xED;sto pythonn&#xED;ho <code>c = obj.color</code> a <code>obj.color = ...</code> tedy pou&#x17E;ijeme <code>c = obj.color()</code> a <code>obj.setColor(...)</code>.</p>\n<h3>Spr&#xE1;va pam&#x11B;ti</h3>\n<p>Python a C++/Qt maj&#xED;, bohu&#x17E;el, rozd&#xED;ln&#xFD; p&#x159;&#xED;stup ke spr&#xE1;v&#x11B; pam&#x11B;ti.\nPython pou&#x17E;&#xED;v&#xE1; <em>reference counting</em> a <em>garbage collection</em>.\nC++ m&#xE1; objekty s destruktory, co&#x17E; Qt zjednodu&#x161;uje (alespo&#x148; pro C++) <em>stromem vlastnictv&#xED;</em>.</p>\n<p>Z&#xE1;kladn&#xED; t&#x159;&#xED;da v Qt, ze kter&#xE9; d&#x11B;d&#xED; t&#xE9;m&#x11B;&#x159; v&#x161;echny ostatn&#xED;, je QObject.\nTen m&#xE1; seznam potomk&#x16F; (children), o kter&#xE9; se &#x201E;star&#xE1;&#x201C;, a kdy&#x17E; uvoln&#xED;me rodi&#x10D;e, uvoln&#xED; se rekurzivn&#x11B; i v&#x161;ichni potomci.\nZ Pythonu pak m&#x16F;&#x17E;eme dostat chybu <code>wrapped C/C++ object has been deleted</code>.\nJinak ale kombinace QObject a pythonn&#xED;ch objekt&#x16F; funguje dob&#x159;e.</p>\n<p>V&#x11B;t&#x161;&#xED; probl&#xE9;my m&#x16F;&#x17E;ou nastat s pomocn&#xFD;mi objekty, kter&#xE9; ned&#x11B;d&#xED; z QObject a nemaj&#xED; pot&#x159;ebn&#xE9; &#x201E;dynamick&#xE9;&#x201C; vlastnosti.\nTakov&#xFD; objekt doporu&#x10D;ujeme pou&#x17E;&#xED;vat jen v r&#xE1;mci jedn&#xE9; funkce (t.j. neukl&#xE1;dat si ho jinde), pokud si nejste jist&#xED; &#x17E;e\nho &#x201E;nevlastn&#xED;te&#x201C; i ve smyslu C++/Qt.</p>\n<p>Ob&#x10D;as se stane, &#x17E;e program spadne pro chybu jako nepovolen&#xFD; p&#x159;&#xED;stup do pam&#x11B;ti.\nBez hlub&#x161;&#xED; znalosti Qt a PyQt se takov&#xE1; chyba odstra&#x148;uje pom&#x11B;rn&#x11B; t&#x11B;&#x17E;ko, ale va&#x161;e znalosti C++ (z jin&#xFD;ch kurz&#x16F;)\na CPython C API (z&#xA0;<a href=\"/2017/pyknihovny-brno/intro/cython/\">minula</a>) v&#xE1;m v tom pom&#x16F;&#x17E;ou.\nDoporu&#x10D;ujeme d&#x11B;lat mal&#xE9; commity a ps&#xE1;t jednoduch&#xFD; k&#xF3;d.</p>\n<h3>Smy&#x10D;ka ud&#xE1;lost&#xED;, sign&#xE1;ly a sloty</h3>\n<p>Qt funguje na principu smy&#x10D;ky ud&#xE1;lost&#xED; (event loop).\nMetoda <code>QApplication.exec</code> obsahuje v podstat&#x11B; nekone&#x10D;nou smy&#x10D;ku, kter&#xE1; &#x10D;ek&#xE1; na extern&#xED; <em>ud&#xE1;losti</em> (klik my&#x161;i,\n&#x17E;&#xE1;dost OS o vykreslen&#xED; okna atd.) a na jejich z&#xE1;klad&#x11B; vol&#xE1; p&#x159;&#xED;slu&#x161;n&#xE9; funkce &#x2013; a&#x165; u&#x17E; intern&#xED;\nnebo n&#xE1;mi definovan&#xE9;.</p>\n<p>Pro komunikaci mezi objekty v r&#xE1;mci aplikace pak Qt pou&#x17E;&#xED;v&#xE1; mechanismus <em>sign&#xE1;l&#x16F; a slot&#x16F;</em> (variantu <em>observer pattern</em>).\nSign&#xE1;l je vysl&#xE1;n (<em>emitted</em>) p&#x159;i ud&#xE1;losti jako kliknut&#xED; na tla&#x10D;&#xED;tko, v&#xFD;b&#x11B;r polo&#x17E;ky z menu, zav&#x159;en&#xED; okna atp.\nK sign&#xE1;lu m&#x16F;&#x17E;e b&#xFD;t p&#x159;ipojeno n&#x11B;kolik slot&#x16F;, co&#x17E; jsou funkce, kter&#xE9; se po vysl&#xE1;n&#xED; sign&#xE1;lu zavolaj&#xED;.\nK&#xF3;d, kter&#xFD; vys&#xED;l&#xE1; sign&#xE1;l, obecn&#x11B; nev&#xED; o tom, kolik slot&#x16F; je p&#x159;ipojeno (a jsou-li n&#x11B;jak&#xE9;).</p>\n<p>V C++ jsou sign&#xE1;ly a sloty v&#x17E;dy staticky nadefinovan&#xE9; na n&#x11B;jak&#xE9; t&#x159;&#xED;d&#x11B;, kter&#xE1; d&#x11B;d&#xED; z <code>QObject</code>.\nV PyQt takto mus&#xED; b&#xFD;t nadefinov&#xE1;ny jen sign&#xE1;ly; za slot poslou&#x17E;&#xED; jak&#xE1;koli pythonn&#xED; funkce.</p>\n<p>V p&#x159;&#xED;kladu v&#xFD;&#x161;e jsme p&#x159;ipojili sign&#xE1;l <code>clicked</code> tla&#x10D;&#xED;tka na slot <code>quit</code> aplikace.\nStejn&#x11B; bychom mohli p&#x159;ipojit jakoukoli funkci/metodu, kter&#xE1; bere spr&#xE1;vn&#xFD; po&#x10D;et argument&#x16F; &#x2013; v n&#xE1;sleduj&#xED;c&#xED;m p&#x159;&#xED;pad&#x11B; nula:</p>\n<div class=\"highlight\"><pre><span></span>    <span class=\"n\">button</span><span class=\"o\">.</span><span class=\"n\">clicked</span><span class=\"o\">.</span><span class=\"n\">connect</span><span class=\"p\">(</span><span class=\"k\">lambda</span><span class=\"p\">:</span> <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s1\">&apos;Exiting program&apos;</span><span class=\"p\">))</span>\n</pre></div><p>V C++ je &#x10D;ast&#xE9; p&#x159;et&#x11B;&#x17E;ov&#xE1;n&#xED; funkc&#xED; (v&#x10D;etn&#x11B; sign&#xE1;l&#x16F;), co&#x17E; Pythonist&#x16F;m ob&#x10D;as zt&#x11B;&#x17E;uje &#x17E;ivot.\nPyQt v&#x11B;t&#x161;inou automaticky vybere variantu sign&#xE1;lu podle p&#x159;ipojen&#xE9; funkce, ale ne v&#x17E;dy je to mo&#x17E;n&#xE9;.</p>\n<p>Uka&#x17E;me si to na n&#xE1;sleduj&#xED;c&#xED;m k&#xF3;du, kter&#xFD; napojuje funkci <code>print</code> na dv&#x11B; varianty sign&#xE1;lu <a href=\"http://doc.qt.io/qt-5/qcombobox.html#activated\">QComboBox.activated</a>.\nTen se vy&#x161;le p&#x159;i v&#xFD;b&#x11B;ru polo&#x17E;ky ze seznamu bu&#x10F; jako <code>QComboBox.activated[int]</code>, kdy p&#x159;ed&#xE1;v&#xE1; index vybran&#xE9; polo&#x17E;ky,\nnebo jako <code>QComboBox.activated[str]</code>, kdy p&#x159;ed&#xE1;v&#xE1; text polo&#x17E;ky:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"kn\">from</span> <span class=\"nn\">PyQt5</span> <span class=\"kn\">import</span> <span class=\"n\">QtWidgets</span>\n\n<span class=\"n\">app</span> <span class=\"o\">=</span> <span class=\"n\">QtWidgets</span><span class=\"o\">.</span><span class=\"n\">QApplication</span><span class=\"p\">([])</span>\n\n<span class=\"c1\"># QComboBox - pol&#xED;&#x10D;ko pro v&#xFD;b&#x11B;r z n&#x11B;kolika mo&#x17E;nost&#xED;</span>\n<span class=\"n\">box</span> <span class=\"o\">=</span> <span class=\"n\">QtWidgets</span><span class=\"o\">.</span><span class=\"n\">QComboBox</span><span class=\"p\">()</span>\n<span class=\"n\">box</span><span class=\"o\">.</span><span class=\"n\">addItem</span><span class=\"p\">(</span><span class=\"s1\">&apos;First Option&apos;</span><span class=\"p\">)</span>\n<span class=\"n\">box</span><span class=\"o\">.</span><span class=\"n\">addItem</span><span class=\"p\">(</span><span class=\"s1\">&apos;Second Option&apos;</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># Z&#xE1;kladn&#xED; varianta napoj&#xED; na activated[int]</span>\n<span class=\"n\">box</span><span class=\"o\">.</span><span class=\"n\">activated</span><span class=\"o\">.</span><span class=\"n\">connect</span><span class=\"p\">(</span><span class=\"k\">print</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># V&#xFD;b&#x11B;r varianty sign&#xE1;lu pomoc&#xED; hranat&#xFD;ch z&#xE1;vorek</span>\n<span class=\"n\">box</span><span class=\"o\">.</span><span class=\"n\">activated</span><span class=\"p\">[</span><span class=\"nb\">str</span><span class=\"p\">]</span><span class=\"o\">.</span><span class=\"n\">connect</span><span class=\"p\">(</span><span class=\"k\">print</span><span class=\"p\">)</span>\n<span class=\"n\">box</span><span class=\"o\">.</span><span class=\"n\">activated</span><span class=\"p\">[</span><span class=\"nb\">int</span><span class=\"p\">]</span><span class=\"o\">.</span><span class=\"n\">connect</span><span class=\"p\">(</span><span class=\"k\">print</span><span class=\"p\">)</span>\n\n<span class=\"n\">box</span><span class=\"o\">.</span><span class=\"n\">show</span><span class=\"p\">()</span>\n\n<span class=\"n\">app</span><span class=\"o\">.</span><span class=\"k\">exec</span><span class=\"p\">()</span>\n</pre></div><h2>Skl&#xE1;d&#xE1;n&#xED; GUI</h2>\n<p>Z&#xE1;kladn&#xED; zp&#x16F;sob, jak v Qt vytv&#xE1;&#x159;et grafick&#xE9; rozhran&#xED;, je skl&#xE1;d&#xE1;n&#xED; funk&#x10D;n&#xED;ch prvk&#x16F; (<em>widgets</em>) do\nhierarchie oken, skupin a panel&#x16F;.</p>\n<p>Z&#xE1;kladn&#xED; t&#x159;&#xED;da pro funk&#x10D;n&#xED; prvky, <a href=\"http://doc.qt.io/qt-5/qwidget.html\">QWidget</a>, d&#x11B;d&#xED; z u&#x17E; zm&#xED;n&#x11B;n&#xE9;ho <code>QObject</code>.\nKa&#x17E;d&#xFD; QObject m&#x16F;&#x17E;e obsahovat potomky (children), a v p&#x159;&#xED;pad&#x11B; QWidget se potomci vykresluj&#xED;\njako sou&#x10D;&#xE1;st sv&#xE9;ho rodi&#x10D;e.\nNav&#xED;c m&#x16F;&#x17E;e m&#xED;t ka&#x17E;d&#xFD; widget tzv. <em>layout</em>, kter&#xFD; ur&#x10D;uje pozici a velikost widget&#x16F;, kter&#xE9; jsou\ndo n&#x11B;j p&#x159;idan&#xE9;.</p>\n<p>Uka&#x17E;me to v k&#xF3;du:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"kn\">from</span> <span class=\"nn\">PyQt5</span> <span class=\"kn\">import</span> <span class=\"n\">QtWidgets</span>\n\n<span class=\"n\">app</span> <span class=\"o\">=</span> <span class=\"n\">QtWidgets</span><span class=\"o\">.</span><span class=\"n\">QApplication</span><span class=\"p\">([])</span>\n\n<span class=\"c1\"># Hlavn&#xED; okno</span>\n<span class=\"n\">main</span> <span class=\"o\">=</span> <span class=\"n\">QtWidgets</span><span class=\"o\">.</span><span class=\"n\">QWidget</span><span class=\"p\">()</span>\n<span class=\"n\">main</span><span class=\"o\">.</span><span class=\"n\">setWindowTitle</span><span class=\"p\">(</span><span class=\"s1\">&apos;Hello Qt&apos;</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># Layout pro hlavn&#xED; okno</span>\n<span class=\"n\">layout</span> <span class=\"o\">=</span> <span class=\"n\">QtWidgets</span><span class=\"o\">.</span><span class=\"n\">QHBoxLayout</span><span class=\"p\">()</span>\n<span class=\"n\">main</span><span class=\"o\">.</span><span class=\"n\">setLayout</span><span class=\"p\">(</span><span class=\"n\">layout</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># N&#xE1;pis</span>\n<span class=\"n\">label</span> <span class=\"o\">=</span> <span class=\"n\">QtWidgets</span><span class=\"o\">.</span><span class=\"n\">QLabel</span><span class=\"p\">(</span><span class=\"s1\">&apos;Click the button to change me&apos;</span><span class=\"p\">)</span>\n<span class=\"c1\"># P&#x159;id&#xE1;n&#xED;m do layoutu se n&#xE1;pis automaticky stane potomkem hlavn&#xED;ho okna</span>\n<span class=\"n\">layout</span><span class=\"o\">.</span><span class=\"n\">addWidget</span><span class=\"p\">(</span><span class=\"n\">label</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># Tla&#x10D;&#xED;tko</span>\n<span class=\"n\">button</span> <span class=\"o\">=</span> <span class=\"n\">QtWidgets</span><span class=\"o\">.</span><span class=\"n\">QPushButton</span><span class=\"p\">(</span><span class=\"s1\">&apos;Click me&apos;</span><span class=\"p\">)</span>\n<span class=\"n\">layout</span><span class=\"o\">.</span><span class=\"n\">addWidget</span><span class=\"p\">(</span><span class=\"n\">button</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># Funkcionalita</span>\n<span class=\"k\">def</span> <span class=\"nf\">change_label</span><span class=\"p\">():</span>\n    <span class=\"n\">label</span><span class=\"o\">.</span><span class=\"n\">setText</span><span class=\"p\">(</span><span class=\"s1\">&apos;Good job. +100 points.&apos;</span><span class=\"p\">)</span>\n\n<span class=\"n\">button</span><span class=\"o\">.</span><span class=\"n\">clicked</span><span class=\"o\">.</span><span class=\"n\">connect</span><span class=\"p\">(</span><span class=\"n\">change_label</span><span class=\"p\">)</span>\n\n<span class=\"c1\"># Spu&#x161;t&#x11B;n&#xED;</span>\n<span class=\"n\">main</span><span class=\"o\">.</span><span class=\"n\">show</span><span class=\"p\">()</span>\n<span class=\"n\">app</span><span class=\"o\">.</span><span class=\"k\">exec</span><span class=\"p\">()</span>\n</pre></div><p>Zabudovan&#xFD;ch <a href=\"http://doc.qt.io/qt-5/layout.html#horizontal-vertical-grid-and-form-layouts\">layout&#x16F;</a> i <a href=\"https://doc.qt.io/qt-5/gallery.html\">widget&#x16F;</a> existuje spousta, jednodu&#x161;&#x161;&#xED;\nprogramy sta&#x10D;&#xED; &#x201E;poskl&#xE1;dat&#x201C; z nich a napojit je na logiku.\nPro slo&#x17E;it&#x11B;j&#x161;&#xED; programy jsou pak mo&#x17E;nosti, jak si widgety p&#x159;izp&#x16F;sobit.</p>\n<h2>Qt Designer</h2>\n<p>Na tomto cvi&#x10D;en&#xED; si p&#x159;iprav&#xED;me aplikaci pro editaci dla&#x17E;dicov&#xFD;ch map (<em>tile maps</em>)\n&#x2013; obr&#xE1;zk&#x16F; slo&#x17E;en&#xFD;ch z&#xA0;omezen&#xE9; nab&#xED;dky &#x10D;tvere&#x10D;k&#x16F; (&#x201E;dla&#x17E;dic&#x201C;),\nzn&#xE1;m&#xFD;ch mj. ze <a href=\"https://www.google.cz/search?q=game+boy+overworld+map&amp;tbm=isch\">star&#xFD;ch her</a>.</p>\n<p>Aplikace bude m&#xED;t &#x10D;&#xE1;st s&#xA0;mapou, paletu pro v&#xFD;b&#x11B;r dla&#x17E;dice ke kreslen&#xED; a nav&#xED;c\nmenu a&#xA0;panel n&#xE1;stroj&#x16F; pro akce jako ukl&#xE1;d&#xE1;n&#xED; a otev&#xED;r&#xE1;n&#xED; soubor&#x16F;:</p>\n<p><img src=\"/2017/pyknihovny-brno/intro/pyqt/static/basic-screenshot.png\" alt=\"Obr&#xE1;zek aplikace\"></p>\n<p>Vytv&#xE1;&#x159;et GUI v k&#xF3;du je pom&#x11B;rn&#x11B; neefektivn&#xED;, a tak existuje n&#xE1;stroj, kde si okna m&#x16F;&#x17E;eme &#x201E;naklikat&#x201C;.\nJmenuje se Qt Designer a m&#x11B;li byste ho m&#xED;t nainstalovan&#xFD;.\nNa &#x161;koln&#xED;ch po&#x10D;&#xED;ta&#x10D;&#xED;ch se spou&#x161;t&#xED; p&#x159;&#xED;kazem <code>designer -qt=5</code>.</p>\n<p>Spust&#xED;me Designer a vytvo&#x159;&#xED;me v n&#x11B;m nov&#xE9; <em>Main Window</em>.\nDo n&#x11B;j si z palety p&#x159;id&#xE1;me <em>Scroll Area</em> a doprava vedle n&#x11B;j <em>List Widget</em>.\nPot&#xE9; aplikujeme layout: na volnou plochu okna klikneme prav&#xFD;m tla&#x10D;&#xED;tkem a vybereme <em>Lay Out &#x2023; Horizontally</em>.\n(D&#xE1; se to ud&#x11B;lat i tla&#x10D;&#xED;tkem v li&#x161;t&#x11B;.)</p>\n<p>Pomoc&#xED; <kbd>Ctrl</kbd>+<kbd>R</kbd> lze zkontrolovat, jak okno vypad&#xE1; a jak reaguje na zm&#x11B;ny velikosti.</p>\n<p>Potom p&#x159;id&#xE1;me polo&#x17E;ku do menu: m&#xED;sto <em>Type Here</em> nap&#xED;&#x161;eme <em>Map</em> a pod n&#x11B;j podobn&#x11B; p&#x159;id&#xE1;me polo&#x17E;ky <em>New</em> a <em>Quit</em>.</p>\n<p>V panelu <em>Property Editor</em> jde m&#x11B;nit vlastnosti jednotliv&#xFD;ch prvk&#x16F;.\nU skrolovac&#xED;ho okna nastav&#xED;me <em>objectName</em> na <em>scrollArea</em>.\nU <em>ListWidget</em> nastav&#xED;me <em>objectName</em> na <em>palette</em> a <em>sizePolicy &#x2023; Horizontal</em> na <em>Preferred</em>.\nV panelu <em>ActionEditor</em> najdeme polo&#x17E;ky pro <em>New</em> a <em>Quit</em> a nastav&#xED;me jim <em>objectName</em> na <em>actionNew</em>, resp. <em>actionQuit</em>.</p>\n<p>Potom p&#x159;es prav&#xE9; tla&#x10D;&#xED;tko na nevyu&#x17E;it&#xE9; plo&#x161;e okna p&#x159;id&#xE1;me li&#x161;tu n&#xE1;stroj&#x16F; (<em>Add Toolbar</em>) a z panelu\n<em>Action Editor</em> do n&#x11B;j akci <em>actionQuit</em> p&#x159;et&#xE1;hneme.</p>\n<p>Pomoc&#xED; <kbd>Ctrl</kbd>+<kbd>R</kbd> op&#x11B;t zkontrolujeme, jak okno vypad&#xE1; a jak po nastaven&#xED; <em>sizePolicy</em> reaguje na zm&#x11B;ny velikosti.</p>\n<p>V Designeru jde i napojovat sign&#xE1;ly. V panelu <em>Signal/Slot Editor</em> p&#x159;id&#xE1;me tento &#x159;&#xE1;dek:</p>\n<ul>\n<li>Sender: <code>actionQuit</code></li>\n<li>Signal: <code>triggered()</code></li>\n<li>Receiver: <code>MainWindow</code></li>\n<li>Slot: <code>close()</code></li>\n</ul>\n<p>Pomoc&#xED; <kbd>Ctrl</kbd>+<kbd>R</kbd> jde ov&#x11B;&#x159;it, &#x17E;e zav&#xED;r&#xE1;n&#xED; okna funguje.</p>\n<p>N&#xE1;vrh okna ulo&#x17E;&#xED;me do souboru <code>mainwindow.ui</code>.</p>\n<p>Soubor s n&#xE1;vrhem jde p&#x159;ev&#xE9;st na pythonn&#xED; zdrojov&#xFD; soubor pomoc&#xED; programu <code>pyuic5</code> nebo\nho v&#x17E;dy na&#x10D;&#xED;st p&#x159;&#xED;mo z programu.\nMy pou&#x17E;ijeme druhou variantu, je v&#x161;ak dobr&#xE9; o <code>pyuic5</code> v&#x11B;d&#x11B;t, kdybyste n&#x11B;kdy pot&#x159;ebovali\nz&#xE1;klad pro vytv&#xE1;&#x159;en&#xED; UI v k&#xF3;du (nap&#x159;. na vytvo&#x159;en&#xED; sady n&#x11B;kolika podobn&#xFD;ch tla&#x10D;&#xED;tek v cyklu).</p>\n<p>Na&#x10D;&#xED;st <code>.ui</code> soubor z programu do p&#x159;edp&#x159;ipraven&#xE9;ho okna <code>QMainWindow</code> lze pomoc&#xED; funkce <a href=\"http://pyqt.sourceforge.net/Docs/PyQt5/designer.html#the-uic-module\">uic.loadUi</a>:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"kn\">from</span> <span class=\"nn\">PyQt5</span> <span class=\"kn\">import</span> <span class=\"n\">QtWidgets</span><span class=\"p\">,</span> <span class=\"n\">uic</span>\n\n<span class=\"k\">def</span> <span class=\"nf\">main</span><span class=\"p\">():</span>\n    <span class=\"n\">app</span> <span class=\"o\">=</span> <span class=\"n\">QtWidgets</span><span class=\"o\">.</span><span class=\"n\">QApplication</span><span class=\"p\">([])</span>\n\n    <span class=\"n\">window</span> <span class=\"o\">=</span> <span class=\"n\">QtWidgets</span><span class=\"o\">.</span><span class=\"n\">QMainWindow</span><span class=\"p\">()</span>\n\n    <span class=\"k\">with</span> <span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s1\">&apos;mainwindow.ui&apos;</span><span class=\"p\">)</span> <span class=\"k\">as</span> <span class=\"n\">f</span><span class=\"p\">:</span>\n        <span class=\"n\">uic</span><span class=\"o\">.</span><span class=\"n\">loadUi</span><span class=\"p\">(</span><span class=\"n\">f</span><span class=\"p\">,</span> <span class=\"n\">window</span><span class=\"p\">)</span>\n\n    <span class=\"n\">window</span><span class=\"o\">.</span><span class=\"n\">show</span><span class=\"p\">()</span>\n\n    <span class=\"k\">return</span> <span class=\"n\">app</span><span class=\"o\">.</span><span class=\"k\">exec</span><span class=\"p\">()</span>\n\n<span class=\"n\">main</span><span class=\"p\">()</span>\n</pre></div><h2>Vlastn&#xED; widget - Grid</h2>\n<p>Qt neobsahuje p&#x159;edp&#x159;ipraven&#xFD; widget na dla&#x17E;dicov&#xE9; mapy. Mus&#xED;me si tedy vyrobit vlastn&#xED;.</p>\n<p>Mapu budeme reprezentovat jako NumPy matici (viz <a href=\"/2017/pyknihovny-brno/intro/numpy/\">lekce o NumPy</a>).\nZat&#xED;m budeme pou&#x17E;&#xED;vat dva druhy dla&#x17E;dic: tr&#xE1;vu (v&#xA0;matici reprezentovanou jako 0) a ze&#x10F; (-1).</p>\n<p>Velikost widgetu se zad&#xE1;v&#xE1; v pixelech. Mus&#xED;me ho ud&#x11B;lat dostate&#x10D;n&#x11B; velk&#xFD;, aby se do n&#x11B;j ve&#x161;la v&#x161;echna pol&#xED;&#x10D;ka mapy.\nVelikost jednoho pol&#xED;&#x10D;ka v pixelech zvol&#xED;me pro jednoduchost konstantou.</p>\n<p>Sou&#x159;adnice v Qt jsou v pixelech ve form&#x11B; <code>(x, y)</code> &#x2013; klasicky jak jsme zvykl&#xED;, <code>x</code> je horizont&#xE1;ln&#xED; sou&#x159;adnice &#x2013;\nkde&#x17E;to matice je ulo&#x17E;en&#xE1; po pol&#xED;&#x10D;k&#xE1;ch <code>(&#x159;&#xE1;dek, sloupec)</code>.\nAbychom se v tom neztratili, je dobr&#xE9; hned ze za&#x10D;&#xE1;tku ud&#x11B;lat funkce pro p&#x159;evod mezi sou&#x159;adn&#xFD;mi syst&#xE9;my\na d&#x16F;sledn&#x11B; rozli&#x161;ovat <code>(x, y)</code> vs. <code>(row, column)</code>.</p>\n<div class=\"highlight\"><pre><span></span><span class=\"n\">CELL_SIZE</span> <span class=\"o\">=</span> <span class=\"mi\">32</span>\n\n\n<span class=\"k\">def</span> <span class=\"nf\">pixels_to_logical</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">y</span><span class=\"p\">):</span>\n    <span class=\"k\">return</span> <span class=\"n\">y</span> <span class=\"o\">//</span> <span class=\"n\">CELL_SIZE</span><span class=\"p\">,</span> <span class=\"n\">x</span> <span class=\"o\">//</span> <span class=\"n\">CELL_SIZE</span>\n\n\n<span class=\"k\">def</span> <span class=\"nf\">logical_to_pixels</span><span class=\"p\">(</span><span class=\"n\">row</span><span class=\"p\">,</span> <span class=\"n\">column</span><span class=\"p\">):</span>\n    <span class=\"k\">return</span> <span class=\"n\">column</span> <span class=\"o\">*</span> <span class=\"n\">CELL_SIZE</span><span class=\"p\">,</span> <span class=\"n\">row</span> <span class=\"o\">*</span> <span class=\"n\">CELL_SIZE</span>\n\n\n<span class=\"k\">class</span> <span class=\"nc\">GridWidget</span><span class=\"p\">(</span><span class=\"n\">QtWidgets</span><span class=\"o\">.</span><span class=\"n\">QWidget</span><span class=\"p\">):</span>\n    <span class=\"k\">def</span> <span class=\"fm\">__init__</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">array</span><span class=\"p\">):</span>\n        <span class=\"nb\">super</span><span class=\"p\">()</span><span class=\"o\">.</span><span class=\"fm\">__init__</span><span class=\"p\">()</span>  <span class=\"c1\"># mus&#xED;me zavolat konstruktor p&#x159;edka</span>\n        <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">array</span> <span class=\"o\">=</span> <span class=\"n\">array</span>\n        <span class=\"c1\"># nastav&#xED;me velikost podle velikosti matice, jinak je n&#xE1;&#x161; widget p&#x159;&#xED;li&#x161; mal&#xFD;</span>\n        <span class=\"n\">size</span> <span class=\"o\">=</span> <span class=\"n\">logical_to_pixels</span><span class=\"p\">(</span><span class=\"o\">*</span><span class=\"n\">array</span><span class=\"o\">.</span><span class=\"n\">shape</span><span class=\"p\">)</span>\n        <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">setMinimumSize</span><span class=\"p\">(</span><span class=\"o\">*</span><span class=\"n\">size</span><span class=\"p\">)</span>\n        <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">setMaximumSize</span><span class=\"p\">(</span><span class=\"o\">*</span><span class=\"n\">size</span><span class=\"p\">)</span>\n        <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">resize</span><span class=\"p\">(</span><span class=\"o\">*</span><span class=\"n\">size</span><span class=\"p\">)</span>\n</pre></div><p><code>GridWidget</code> vlo&#x17E;&#xED;me do <code>QScrollArea</code>, kterou jsme si vytvo&#x159;ili v Qt Designeru:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"kn\">import</span> <span class=\"nn\">numpy</span>\n\n    <span class=\"o\">...</span>\n\n    <span class=\"c1\"># mapa zat&#xED;m nadefinovan&#xE1; rovnou v k&#xF3;du</span>\n    <span class=\"n\">array</span> <span class=\"o\">=</span> <span class=\"n\">numpy</span><span class=\"o\">.</span><span class=\"n\">zeros</span><span class=\"p\">((</span><span class=\"mi\">15</span><span class=\"p\">,</span> <span class=\"mi\">20</span><span class=\"p\">),</span> <span class=\"n\">dtype</span><span class=\"o\">=</span><span class=\"n\">numpy</span><span class=\"o\">.</span><span class=\"n\">int8</span><span class=\"p\">)</span>\n    <span class=\"n\">array</span><span class=\"p\">[:,</span> <span class=\"mi\">5</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"o\">-</span><span class=\"mi\">1</span>  <span class=\"c1\"># n&#x11B;jak&#xE1; ze&#x10F;</span>\n\n    <span class=\"c1\"># z&#xED;sk&#xE1;me oblast s posuvn&#xED;ky z Qt Designeru</span>\n    <span class=\"n\">scroll_area</span> <span class=\"o\">=</span> <span class=\"n\">window</span><span class=\"o\">.</span><span class=\"n\">findChild</span><span class=\"p\">(</span><span class=\"n\">QtWidgets</span><span class=\"o\">.</span><span class=\"n\">QScrollArea</span><span class=\"p\">,</span> <span class=\"s1\">&apos;scrollArea&apos;</span><span class=\"p\">)</span>\n\n    <span class=\"c1\"># d&#xE1;me do n&#xED; n&#xE1;&#x161; grid</span>\n    <span class=\"n\">grid</span> <span class=\"o\">=</span> <span class=\"n\">GridWidget</span><span class=\"p\">(</span><span class=\"n\">array</span><span class=\"p\">)</span>\n    <span class=\"n\">scroll_area</span><span class=\"o\">.</span><span class=\"n\">setWidget</span><span class=\"p\">(</span><span class=\"n\">grid</span><span class=\"p\">)</span>\n\n    <span class=\"o\">...</span>\n</pre></div><p>Po spu&#x161;t&#x11B;n&#xED; aplikace zat&#xED;m nic nov&#xE9;ho neuvid&#xED;te, maxim&#xE1;ln&#x11B; se trochu zm&#x11B;n&#xED; posuvn&#xED;ky.\nPot&#x159;ebujeme je&#x161;t&#x11B; za&#x159;&#xED;dit, aby se data z matice vykreslovala do gridu.\nNejlep&#x161;&#xED; je vykreslovat, kdykoliv n&#xE1;s OS (nebo Qt) vyzve, &#x17E;e pot&#x159;ebuje kus okna p&#x159;ekreslit:\np&#x159;i prvn&#xED;m zobrazen&#xED;, odminimalizov&#xE1;n&#xED; okna, uk&#xE1;z&#xE1;n&#xED; nov&#xE9; &#x10D;&#xE1;sti mapy p&#x159;es scrollov&#xE1;n&#xED;.\nTak&#xE9; je zbyte&#x10D;n&#xE9; vykreslovat obr&#xE1;zky mimo oblast, kterou je vid&#x11B;t na obrazovce.</p>\n<p>K tomuto &#xFA;&#x10D;elu n&#xE1;m poslou&#x17E;&#xED; ud&#xE1;lost (<em>event</em>).\nJak bylo &#x159;e&#x10D;eno v &#xFA;vodu, na rozd&#xED;l od sign&#xE1;l&#x16F; a slot&#x16F;, kter&#xE9; zaji&#x161;&#x165;uj&#xED; komunikaci v r&#xE1;mci aplikace,\nud&#xE1;losti vznikaj&#xED; mimo aplikaci.\nJde nap&#x159;&#xED;klad o kliknut&#xED; my&#x161;&#xED; (<a href=\"http://doc.qt.io/qt-5/qwidget.html#mousePressEvent\">mouse*Event</a>), vstup z kl&#xE1;vesnice (<a href=\"http://doc.qt.io/qt-5/qwidget.html#keyPressEvent\">key*Event</a>)\nnebo &#x17E;&#xE1;dost OS o p&#x159;ekreslen&#xED; okna (<a href=\"http://doc.qt.io/qt-5/qwidget.html#paintEvent\">paintEvent</a>).\nNa posledn&#xED; jmenovanou ud&#xE1;lost, <code>paintEvent</code>, te&#x10F; budeme reagovat.</p>\n<p>Ud&#xE1;losti se obsluhuj&#xED; p&#x159;edefinov&#xE1;n&#xED;m p&#x159;&#xED;slu&#x161;n&#xE9; metody, kter&#xE1; jako argument bere objekt popisuj&#xED;c&#xED;\ndanou ud&#xE1;lost.</p>\n<p>V r&#xE1;mci reakce na ud&#xE1;lost <code>paintEvent</code> m&#x16F;&#x17E;eme pou&#x17E;&#xED;vat <a href=\"http://doc.qt.io/qt-5/qpainter.html\">QPainter</a>, objekt, kter&#xFD; generalizuje kreslen&#xED;\nna r&#x16F;zn&#xE9; &#x201E;povrchy&#x201C; jako widgety, obr&#xE1;zky, nebo i instrukce pro tisk&#xE1;rnu.</p>\n<div class=\"highlight\"><pre><span></span><span class=\"kn\">from</span> <span class=\"nn\">PyQt5</span> <span class=\"kn\">import</span> <span class=\"n\">QtWidgets</span><span class=\"p\">,</span> <span class=\"n\">QtGui</span><span class=\"p\">,</span> <span class=\"n\">QtCore</span><span class=\"p\">,</span> <span class=\"n\">uic</span>\n\n\n<span class=\"k\">class</span> <span class=\"nc\">GridWidget</span><span class=\"p\">(</span><span class=\"n\">QtWidgets</span><span class=\"o\">.</span><span class=\"n\">QWidget</span><span class=\"p\">):</span>\n\n    <span class=\"o\">...</span>\n\n    <span class=\"k\">def</span> <span class=\"nf\">paintEvent</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">event</span><span class=\"p\">):</span>\n        <span class=\"n\">rect</span> <span class=\"o\">=</span> <span class=\"n\">event</span><span class=\"o\">.</span><span class=\"n\">rect</span><span class=\"p\">()</span>  <span class=\"c1\"># z&#xED;sk&#xE1;me informace o p&#x159;ekreslovan&#xE9; oblasti</span>\n\n        <span class=\"c1\"># zjist&#xED;me, jakou oblast na&#x161;&#xED; matice to p&#x159;edstavuje</span>\n        <span class=\"c1\"># nesm&#xED;me se p&#x159;itom dostat z matice ven</span>\n        <span class=\"n\">row_min</span><span class=\"p\">,</span> <span class=\"n\">col_min</span> <span class=\"o\">=</span> <span class=\"n\">pixels_to_logical</span><span class=\"p\">(</span><span class=\"n\">rect</span><span class=\"o\">.</span><span class=\"n\">left</span><span class=\"p\">(),</span> <span class=\"n\">rect</span><span class=\"o\">.</span><span class=\"n\">top</span><span class=\"p\">())</span>\n        <span class=\"n\">row_min</span> <span class=\"o\">=</span> <span class=\"nb\">max</span><span class=\"p\">(</span><span class=\"n\">row_min</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">)</span>\n        <span class=\"n\">col_min</span> <span class=\"o\">=</span> <span class=\"nb\">max</span><span class=\"p\">(</span><span class=\"n\">col_min</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">)</span>\n        <span class=\"n\">row_max</span><span class=\"p\">,</span> <span class=\"n\">col_max</span> <span class=\"o\">=</span> <span class=\"n\">pixels_to_logical</span><span class=\"p\">(</span><span class=\"n\">rect</span><span class=\"o\">.</span><span class=\"n\">right</span><span class=\"p\">(),</span> <span class=\"n\">rect</span><span class=\"o\">.</span><span class=\"n\">bottom</span><span class=\"p\">())</span>\n        <span class=\"n\">row_max</span> <span class=\"o\">=</span> <span class=\"nb\">min</span><span class=\"p\">(</span><span class=\"n\">row_max</span> <span class=\"o\">+</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">array</span><span class=\"o\">.</span><span class=\"n\">shape</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">])</span>\n        <span class=\"n\">col_max</span> <span class=\"o\">=</span> <span class=\"nb\">min</span><span class=\"p\">(</span><span class=\"n\">col_max</span> <span class=\"o\">+</span> <span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">array</span><span class=\"o\">.</span><span class=\"n\">shape</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">])</span>\n\n        <span class=\"n\">painter</span> <span class=\"o\">=</span> <span class=\"n\">QtGui</span><span class=\"o\">.</span><span class=\"n\">QPainter</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">)</span>  <span class=\"c1\"># budeme kreslit</span>\n\n        <span class=\"k\">for</span> <span class=\"n\">row</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">row_min</span><span class=\"p\">,</span> <span class=\"n\">row_max</span><span class=\"p\">):</span>\n            <span class=\"k\">for</span> <span class=\"n\">column</span> <span class=\"ow\">in</span> <span class=\"nb\">range</span><span class=\"p\">(</span><span class=\"n\">col_min</span><span class=\"p\">,</span> <span class=\"n\">col_max</span><span class=\"p\">):</span>\n                <span class=\"c1\"># z&#xED;sk&#xE1;me &#x10D;tvere&#x10D;ek, kter&#xFD; budeme vybarvovat</span>\n                <span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">y</span> <span class=\"o\">=</span> <span class=\"n\">logical_to_pixels</span><span class=\"p\">(</span><span class=\"n\">row</span><span class=\"p\">,</span> <span class=\"n\">column</span><span class=\"p\">)</span>\n                <span class=\"n\">rect</span> <span class=\"o\">=</span> <span class=\"n\">QtCore</span><span class=\"o\">.</span><span class=\"n\">QRectF</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"n\">CELL_SIZE</span><span class=\"p\">,</span> <span class=\"n\">CELL_SIZE</span><span class=\"p\">)</span>\n\n                <span class=\"c1\"># &#x161;ed&#xE1; pro zdi, zelen&#xE1; pro tr&#xE1;vu</span>\n                <span class=\"k\">if</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">array</span><span class=\"p\">[</span><span class=\"n\">row</span><span class=\"p\">,</span> <span class=\"n\">column</span><span class=\"p\">]</span> <span class=\"o\">&lt;</span> <span class=\"mi\">0</span><span class=\"p\">:</span>\n                    <span class=\"n\">color</span> <span class=\"o\">=</span> <span class=\"n\">QtGui</span><span class=\"o\">.</span><span class=\"n\">QColor</span><span class=\"p\">(</span><span class=\"mi\">115</span><span class=\"p\">,</span> <span class=\"mi\">115</span><span class=\"p\">,</span> <span class=\"mi\">115</span><span class=\"p\">)</span>\n                <span class=\"k\">else</span><span class=\"p\">:</span>\n                    <span class=\"n\">color</span> <span class=\"o\">=</span> <span class=\"n\">QtGui</span><span class=\"o\">.</span><span class=\"n\">QColor</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"mi\">255</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">)</span>\n\n                <span class=\"c1\"># vypln&#xED;me &#x10D;tvere&#x10D;ek barvou</span>\n                <span class=\"n\">painter</span><span class=\"o\">.</span><span class=\"n\">fillRect</span><span class=\"p\">(</span><span class=\"n\">rect</span><span class=\"p\">,</span> <span class=\"n\">QtGui</span><span class=\"o\">.</span><span class=\"n\">QBrush</span><span class=\"p\">(</span><span class=\"n\">color</span><span class=\"p\">))</span>\n</pre></div><p>Nyn&#xED; by ji&#x17E; mapa m&#x11B;la b&#xFD;t v&#xA0;okn&#x11B; vid&#x11B;t barevn&#x11B;.</p>\n<h3>Obr&#xE1;zky</h3>\n<p>Proto&#x17E;e barvi&#x10D;ky jsou p&#x159;&#xED;li&#x161; nudn&#xE9;, p&#x159;id&#xE1;me do mapov&#xE9;ho widgetu obr&#xE1;zky.</p>\n<p>Ve&#x161;kerou, ke cvi&#x10D;en&#xED; i k &#xFA;kolu pot&#x159;ebnou, grafiku najdete na <a href=\"https://github.com/pyvec/naucse.python.cz/tree/master/lessons/intro/pyqt/static/pics\">GitHubu</a>.\nJe k dispozici pod public domain (tj. &#x201E;d&#x11B;lej si s t&#xED;m, co chce&#x161;&#x201C;), poch&#xE1;z&#xED; ze studia <a href=\"http://kenney.nl/\">Kenney</a>\na je (spole&#x10D;n&#x11B; se dal&#x161;&#xED; voln&#x11B; licencovanou grafikou) ke sta&#x17E;en&#xED; z <a href=\"http://opengameart.org/users/kenney\">OpenGameArt.org</a>.</p>\n<p>Zat&#xED;m budeme pot&#x159;ebovat jen dva obr&#xE1;zky:</p>\n<ul>\n<li><a href=\"/2017/pyknihovny-brno/intro/pyqt/static/pics/grass.svg\">grass.svg</a></li>\n<li><a href=\"/2017/pyknihovny-brno/intro/pyqt/static/pics/wall.svg\">wall.svg</a></li>\n</ul>\n<p>Nejprve si na&#x10D;teme SVG soubory jako objekty <code>QSvgRenderer</code>:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"kn\">from</span> <span class=\"nn\">PyQt5</span> <span class=\"kn\">import</span> <span class=\"n\">QtWidgets</span><span class=\"p\">,</span> <span class=\"n\">QtCore</span><span class=\"p\">,</span> <span class=\"n\">QtGui</span><span class=\"p\">,</span> <span class=\"n\">QtSvg</span><span class=\"p\">,</span> <span class=\"n\">uic</span>\n\n<span class=\"n\">SVG_GRASS</span> <span class=\"o\">=</span> <span class=\"n\">QtSvg</span><span class=\"o\">.</span><span class=\"n\">QSvgRenderer</span><span class=\"p\">(</span><span class=\"s1\">&apos;grass.svg&apos;</span><span class=\"p\">)</span>\n<span class=\"n\">SVG_WALL</span> <span class=\"o\">=</span> <span class=\"n\">QtSvg</span><span class=\"o\">.</span><span class=\"n\">QSvgRenderer</span><span class=\"p\">(</span><span class=\"s1\">&apos;wall.svg&apos;</span><span class=\"p\">)</span>\n</pre></div><p>A pot&#xE9; je na spr&#xE1;vn&#xFD;ch m&#xED;stech vyrendrujeme:</p>\n<div class=\"highlight\"><pre><span></span>                <span class=\"o\">...</span>\n                <span class=\"n\">rect</span> <span class=\"o\">=</span> <span class=\"n\">QtCore</span><span class=\"o\">.</span><span class=\"n\">QRectF</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">,</span> <span class=\"n\">y</span><span class=\"p\">,</span> <span class=\"n\">CELL_SIZE</span><span class=\"p\">,</span> <span class=\"n\">CELL_SIZE</span><span class=\"p\">)</span>\n\n                <span class=\"c1\"># podkladov&#xE1; barva pod polopr&#x16F;hledn&#xE9; obr&#xE1;zky</span>\n                <span class=\"n\">white</span> <span class=\"o\">=</span> <span class=\"n\">QtGui</span><span class=\"o\">.</span><span class=\"n\">QColor</span><span class=\"p\">(</span><span class=\"mi\">255</span><span class=\"p\">,</span> <span class=\"mi\">255</span><span class=\"p\">,</span> <span class=\"mi\">255</span><span class=\"p\">)</span>\n                <span class=\"n\">painter</span><span class=\"o\">.</span><span class=\"n\">fillRect</span><span class=\"p\">(</span><span class=\"n\">rect</span><span class=\"p\">,</span> <span class=\"n\">QtGui</span><span class=\"o\">.</span><span class=\"n\">QBrush</span><span class=\"p\">(</span><span class=\"n\">white</span><span class=\"p\">))</span>\n\n                <span class=\"c1\"># tr&#xE1;vu d&#xE1;me v&#x161;ude, proto&#x17E;e i zdi stoj&#xED; na tr&#xE1;v&#x11B;</span>\n                <span class=\"n\">SVG_GRASS</span><span class=\"o\">.</span><span class=\"n\">render</span><span class=\"p\">(</span><span class=\"n\">painter</span><span class=\"p\">,</span> <span class=\"n\">rect</span><span class=\"p\">)</span>\n\n                <span class=\"c1\"># zdi d&#xE1;me jen tam, kam pat&#x159;&#xED;</span>\n                <span class=\"k\">if</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">array</span><span class=\"p\">[</span><span class=\"n\">row</span><span class=\"p\">,</span> <span class=\"n\">column</span><span class=\"p\">]</span> <span class=\"o\">&lt;</span> <span class=\"mi\">0</span><span class=\"p\">:</span>\n                    <span class=\"n\">SVG_WALL</span><span class=\"o\">.</span><span class=\"n\">render</span><span class=\"p\">(</span><span class=\"n\">painter</span><span class=\"p\">,</span> <span class=\"n\">rect</span><span class=\"p\">)</span>\n</pre></div><h2>Model/View</h2>\n<p>Nyn&#xED; trochu odbo&#x10D;&#xED;me a pov&#xED;me si kr&#xE1;tce o dal&#x161;&#xED;m podsyst&#xE9;mu Qt: o modelech.</p>\n<p>Qt obsahuje framework, kter&#xFD; mapuje informace do podoby tabulek, seznam&#x16F; nebo obecn&#xFD;ch strom&#x16F;.\nVznikl&#xE9; modely se potom daj&#xED; zobrazit ve specializovan&#xFD;ch widgetech.\nSamotn&#xE1; data m&#x16F;&#x17E;ou b&#xFD;t ulo&#x17E;ena kdekoli &#x2013; v pam&#x11B;ti, SQL datab&#xE1;zi, souborech a podobn&#x11B;.\nDokonce nemus&#xED; b&#xFD;t v&#x161;echna dostupn&#xE1;: existuje vestav&#x11B;n&#xFD; model pro souborov&#xFD; syst&#xE9;m,\nkter&#xFD; se d&#xE1; zobrazit ani&#x17E; by se proch&#xE1;zely v&#x161;echny soubory.\nKdy&#x17E; je informace pot&#x159;eba, model se postar&#xE1; o jej&#xED; na&#x10D;ten&#xED;.\nPomoc&#xED; model&#x16F; a modelov&#xFD;ch widget&#x16F; lze informace i m&#x11B;nit, a pokud je model\nzobrazen ve v&#xED;ce widgetech z&#xE1;rove&#x148;, zm&#x11B;ny se projev&#xED; ve v&#x161;ech.</p>\n<p>Obecn&#xE9; modely je bohu&#x17E;el relativn&#x11B; obt&#xED;&#x17E;n&#xE9; implementovat v Pythonu, proto&#x17E;e pou&#x17E;&#xED;vaj&#xED; t&#x159;&#xED;dy,\nkter&#xE9; ned&#x11B;d&#xED; z QObject, tak&#x17E;e je pot&#x159;eba sledovat, jestli je &#x201E;vlastn&#xED;&#x201C; Python nebo C++.\nNa&#x161;t&#x11B;st&#xED; ale existuj&#xED; widgety se zabudovan&#xFD;mi modely, kter&#xE9; obsahuj&#xED; i samotn&#xE1; data.\nTyto modely je slo&#x17E;it&#x11B;j&#x161;&#xED; napojit na existuj&#xED;c&#xED; aplika&#x10D;n&#xED; logiku, ale pro v&#x11B;t&#x161;inu &#xFA;&#x10D;el&#x16F; posta&#x10D;&#xED;.</p>\n<p>O obecn&#xFD;ch modelech si m&#x16F;&#x17E;ete p&#x159;e&#x10D;&#xED;st v <a href=\"http://doc.qt.io/qt-5/model-view-programming.html\">dokumentaci</a>.</p>\n<h2>QListWidget - Paleta</h2>\n<p>Jeden z widget&#x16F; se zabudovan&#xFD;m modelem je <code>QListWidget</code>, kter&#xFD; um&#xED; spravovat a zobrazovat\nn&#x11B;jak&#xFD; seznam.\nMy jsme si v Qt Designeru p&#x159;ipravili <code>QListWidget</code> s n&#xE1;zvem <code>palette</code>, kter&#xFD; pou&#x17E;ijeme\njako paletu jednotliv&#xFD;ch d&#xED;lk&#x16F;, kter&#xE9; budeme moci vkl&#xE1;dat do mapy.\nPolo&#x17E;ky se do tohoto modelu p&#x159;id&#xE1;vaj&#xED; n&#xE1;sledovn&#x11B;:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"k\">def</span> <span class=\"nf\">main</span><span class=\"p\">():</span>\n    <span class=\"o\">...</span>\n\n    <span class=\"c1\"># z&#xED;sk&#xE1;me paletu vytvo&#x159;enou v Qt Designeru</span>\n    <span class=\"n\">palette</span> <span class=\"o\">=</span> <span class=\"n\">window</span><span class=\"o\">.</span><span class=\"n\">findChild</span><span class=\"p\">(</span><span class=\"n\">QtWidgets</span><span class=\"o\">.</span><span class=\"n\">QListWidget</span><span class=\"p\">,</span> <span class=\"s1\">&apos;palette&apos;</span><span class=\"p\">)</span>\n\n    <span class=\"n\">item</span> <span class=\"o\">=</span> <span class=\"n\">QtWidgets</span><span class=\"o\">.</span><span class=\"n\">QListWidgetItem</span><span class=\"p\">(</span><span class=\"s1\">&apos;Grass&apos;</span><span class=\"p\">)</span>  <span class=\"c1\"># vytvo&#x159;&#xED;me polo&#x17E;ku</span>\n    <span class=\"n\">icon</span> <span class=\"o\">=</span> <span class=\"n\">QtGui</span><span class=\"o\">.</span><span class=\"n\">QIcon</span><span class=\"p\">(</span><span class=\"s1\">&apos;grass.svg&apos;</span><span class=\"p\">)</span>  <span class=\"c1\"># ikonu</span>\n    <span class=\"n\">item</span><span class=\"o\">.</span><span class=\"n\">setIcon</span><span class=\"p\">(</span><span class=\"n\">icon</span><span class=\"p\">)</span>  <span class=\"c1\"># p&#x159;i&#x159;ad&#xED;me ikonu polo&#x17E;ce</span>\n    <span class=\"n\">palette</span><span class=\"o\">.</span><span class=\"n\">addItem</span><span class=\"p\">(</span><span class=\"n\">item</span><span class=\"p\">)</span>  <span class=\"c1\"># p&#x159;id&#xE1;me polo&#x17E;ku do palety</span>\n</pre></div><p>Stejn&#xFD;m zp&#x16F;sobem lze do palety p&#x159;idat dal&#x161;&#xED; polo&#x17E;ky: krom&#x11B; tr&#xE1;vy budeme na toto\ncvi&#x10D;en&#xED; pot&#x159;ebovat i st&#x11B;nu.\nProto&#x17E;e v &#xFA;kolu bude polo&#x17E;ek v&#xED;ce, je lep&#x161;&#xED; si na to vytvo&#x159;it funkci &#x10D;i metodu.\nTo nech&#xE1;me na v&#xE1;s.</p>\n<p>Zat&#xED;m jsme vytvo&#x159;ili paletu, ve kter&#xE9; u&#x17E;ivatel m&#x16F;&#x17E;e polo&#x17E;ky vyb&#xED;rat.\nV&#xFD;b&#x11B;r polo&#x17E;ky aktivuje sign&#xE1;l <code>itemSelectionChanged</code>, na kter&#xFD; m&#x16F;&#x17E;eme\nnav&#xE1;zat vol&#xE1;n&#xED; funkce.\n(Pokud bychom m&#x11B;li pod kontrolou t&#x159;&#xED;du widgetu, jako tomu je u t&#x159;&#xED;dy <code>Grid</code>,\nmohli bychom m&#xED;sto toho i p&#x159;edefinovat metodu <code>itemSelectionChanged()</code>.)</p>\n<div class=\"highlight\"><pre><span></span><span class=\"k\">def</span> <span class=\"nf\">main</span><span class=\"p\">():</span>\n    <span class=\"o\">...</span>\n\n    <span class=\"k\">def</span> <span class=\"nf\">item_activated</span><span class=\"p\">():</span>\n        <span class=\"sd\">&quot;&quot;&quot;Tato funkce se zavol&#xE1;, kdy&#x17E; u&#x17E;ivatel zvol&#xED; polo&#x17E;ku&quot;&quot;&quot;</span>\n\n        <span class=\"c1\"># Polo&#x17E;ek m&#x16F;&#x17E;e obecn&#x11B; b&#xFD;t vybr&#xE1;no v&#xED;c, ale v na&#x161;em seznamu je to</span>\n        <span class=\"c1\"># zak&#xE1;z&#xE1;no (v Designeru selectionMode=SingleSelection).</span>\n        <span class=\"c1\"># Projdeme &quot;v&#x161;echny vybran&#xE9; polo&#x17E;ky&quot;, i kdy&#x17E; v&#xED;me &#x17E;e bude max. jedna.</span>\n        <span class=\"k\">for</span> <span class=\"n\">item</span> <span class=\"ow\">in</span> <span class=\"n\">palette</span><span class=\"o\">.</span><span class=\"n\">selectedItems</span><span class=\"p\">():</span>\n            <span class=\"n\">row_num</span> <span class=\"o\">=</span> <span class=\"n\">palette</span><span class=\"o\">.</span><span class=\"n\">indexFromItem</span><span class=\"p\">(</span><span class=\"n\">item</span><span class=\"p\">)</span><span class=\"o\">.</span><span class=\"n\">row</span><span class=\"p\">()</span>\n            <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">row_num</span><span class=\"p\">)</span>\n\n    <span class=\"n\">palette</span><span class=\"o\">.</span><span class=\"n\">itemSelectionChanged</span><span class=\"o\">.</span><span class=\"n\">connect</span><span class=\"p\">(</span><span class=\"n\">item_activated</span><span class=\"p\">)</span>\n</pre></div><p>Nyn&#xED;, kdy&#x17E; u&#x17E;ivatel zvol&#xED; polo&#x17E;ku, vyp&#xED;&#x161;e se do konzole jej&#xED; po&#x159;ad&#xED;.\nN&#xE1;s by ale sp&#xED;&#x161; zaj&#xED;malo, jak bude tato polo&#x17E;ka reprezentov&#xE1;na v matici s&#xA0;mapou.\nK polo&#x17E;ce v palet&#x11B; m&#x16F;&#x17E;eme ulo&#x17E;it informace pomoc&#xED; <code>item.setData(&lt;role&gt;, &lt;data&gt;)</code>.\nRol&#xED; pro informace je <a href=\"http://doc.qt.io/qt-5/qt.html#ItemDataRole-enum\">spousta</a> a n&#x11B;kolik z nich Qt pou&#x17E;&#xED;v&#xE1; pro vykreslov&#xE1;n&#xED;.\nPro vlastn&#xED; data m&#x16F;&#x17E;eme pou&#x17E;&#xED;t <code>QtCore.Qt.UserRole</code>.\nV p&#x159;&#xED;pad&#x11B; pot&#x159;eby ukl&#xE1;dat v&#xED;ce dat m&#x16F;&#x17E;eme d&#xE1;le zvolit <code>QtCore.Qt.UserRole + 1</code> atd.\nPro p&#x159;&#xED;pad, &#x17E;e budeme pot&#x159;ebovat rol&#xED; v&#xED;c, je dobr&#xE9; si je vhodn&#x11B; pojmenovat.</p>\n<div class=\"highlight\"><pre><span></span><span class=\"n\">VALUE_ROLE</span> <span class=\"o\">=</span> <span class=\"n\">QtCore</span><span class=\"o\">.</span><span class=\"n\">Qt</span><span class=\"o\">.</span><span class=\"n\">UserRole</span>\n\n<span class=\"k\">def</span> <span class=\"nf\">main</span><span class=\"p\">():</span>\n    <span class=\"o\">...</span>\n    <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">palette</span><span class=\"o\">.</span><span class=\"n\">addItem</span><span class=\"p\">(</span><span class=\"n\">item</span><span class=\"p\">)</span>  <span class=\"c1\"># p&#x159;id&#xE1;me polo&#x17E;ku do palety</span>\n    <span class=\"n\">item</span><span class=\"o\">.</span><span class=\"n\">setData</span><span class=\"p\">(</span><span class=\"n\">VALUE_ROLE</span><span class=\"p\">,</span> <span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">)</span>  <span class=\"c1\"># p&#x159;i&#x159;ad&#xED;me j&#xED; data</span>\n    <span class=\"o\">...</span>\n\n    <span class=\"k\">def</span> <span class=\"nf\">item_activated</span><span class=\"p\">():</span>\n        <span class=\"k\">for</span> <span class=\"n\">item</span> <span class=\"ow\">in</span> <span class=\"n\">palette</span><span class=\"o\">.</span><span class=\"n\">selectedItems</span><span class=\"p\">():</span>\n            <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">item</span><span class=\"o\">.</span><span class=\"n\">data</span><span class=\"p\">(</span><span class=\"n\">VALUE_ROLE</span><span class=\"p\">))</span>  <span class=\"c1\"># &#x10D;teme data stejn&#xE9; role z polo&#x17E;ky</span>\n</pre></div><p>Nyn&#xED; byste m&#x11B;li m&#xED;t v palet&#x11B; tr&#xE1;vu a st&#x11B;nu s pat&#x159;i&#x10D;n&#xFD;mi &#x10D;&#xED;sly (<code>0</code> a <code>-1</code>), kter&#xE9; se vypisuj&#xED; do konzole p&#x159;i zvolen&#xED; polo&#x17E;ky.</p>\n<p>Nakonec si &#x10D;&#xED;slo m&#xED;sto vypisov&#xE1;n&#xED; ulo&#x17E;&#xED;me do gridu, abychom ho mohli pozd&#x11B;ji pou&#x17E;&#xED;t.</p>\n<div class=\"highlight\"><pre><span></span>    <span class=\"k\">def</span> <span class=\"nf\">item_activated</span><span class=\"p\">():</span>\n        <span class=\"k\">for</span> <span class=\"n\">item</span> <span class=\"ow\">in</span> <span class=\"n\">palette</span><span class=\"o\">.</span><span class=\"n\">selectedItems</span><span class=\"p\">():</span>\n            <span class=\"n\">grid</span><span class=\"o\">.</span><span class=\"n\">selected</span> <span class=\"o\">=</span> <span class=\"n\">item</span><span class=\"o\">.</span><span class=\"n\">data</span><span class=\"p\">(</span><span class=\"n\">VALUE_ROLE</span><span class=\"p\">)</span>\n</pre></div><h2>Klik&#xE1;n&#xED; do gridu</h2>\n<p>Nyn&#xED; nezb&#xFD;v&#xE1; nic jin&#xE9;ho, ne&#x17E; pomoc&#xED; klik&#xE1;n&#xED; nan&#xE1;&#x161;et zvolen&#xE9; d&#xED;lky do mapy.\nK tomu op&#x11B;t pou&#x17E;ijeme ud&#xE1;lost, tentokr&#xE1;t ud&#xE1;lost kliknut&#xED;, tedy <code>mousePressEvent</code>.</p>\n<div class=\"highlight\"><pre><span></span><span class=\"k\">class</span> <span class=\"nc\">GridWidget</span><span class=\"p\">(</span><span class=\"n\">QtWidgets</span><span class=\"o\">.</span><span class=\"n\">QWidget</span><span class=\"p\">):</span>\n    <span class=\"o\">...</span>\n\n    <span class=\"k\">def</span> <span class=\"nf\">mousePressEvent</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">event</span><span class=\"p\">):</span>\n        <span class=\"c1\"># p&#x159;evedeme klik na sou&#x159;adnice matice</span>\n        <span class=\"n\">row</span><span class=\"p\">,</span> <span class=\"n\">column</span> <span class=\"o\">=</span> <span class=\"n\">pixels_to_logical</span><span class=\"p\">(</span><span class=\"n\">event</span><span class=\"o\">.</span><span class=\"n\">x</span><span class=\"p\">(),</span> <span class=\"n\">event</span><span class=\"o\">.</span><span class=\"n\">y</span><span class=\"p\">())</span>\n\n        <span class=\"c1\"># Pokud jsme v matici, aktualizujeme data</span>\n        <span class=\"k\">if</span> <span class=\"mi\">0</span> <span class=\"o\">&lt;=</span> <span class=\"n\">row</span> <span class=\"o\">&lt;</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">array</span><span class=\"o\">.</span><span class=\"n\">shape</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">]</span> <span class=\"ow\">and</span> <span class=\"mi\">0</span> <span class=\"o\">&lt;=</span> <span class=\"n\">column</span> <span class=\"o\">&lt;</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">array</span><span class=\"o\">.</span><span class=\"n\">shape</span><span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">]:</span>\n            <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">array</span><span class=\"p\">[</span><span class=\"n\">row</span><span class=\"p\">,</span> <span class=\"n\">column</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">selected</span>\n\n            <span class=\"c1\"># t&#xED;mto zajist&#xED;me p&#x159;ekreslen&#xED; widgetu v m&#xED;st&#x11B; zm&#x11B;ny:</span>\n            <span class=\"c1\"># (pro Python 3.4 a ni&#x17E;&#x161;&#xED; volejte jen self.update() bez argument&#x16F;)</span>\n            <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">update</span><span class=\"p\">(</span><span class=\"o\">*</span><span class=\"n\">logical_to_pixels</span><span class=\"p\">(</span><span class=\"n\">row</span><span class=\"p\">,</span> <span class=\"n\">column</span><span class=\"p\">),</span> <span class=\"n\">CELL_SIZE</span><span class=\"p\">,</span> <span class=\"n\">CELL_SIZE</span><span class=\"p\">)</span>\n</pre></div><div class=\"admonition note\"><p>Zde v&#xED;me, &#x17E;e kliknut&#xED; m&#x16F;&#x17E;e zm&#x11B;nit vykreslenou mapu pouze v m&#xED;st&#x11B; kliknut&#xED;.\nPokud by ale kliknut&#xED; na ur&#x10D;it&#xE9; pol&#xED;&#x10D;ko mohlo zm&#x11B;nit obsah mapy n&#x11B;kde jinde,\nje lep&#x161;&#xED; zavolat <code>self.update()</code> bez argument&#x16F; a &#x159;&#xED;ct tak syst&#xE9;mu, &#x17E;e se m&#xE1; p&#x159;ekreslit cel&#xFD; widget.</p>\n</div><p>Proto&#x17E;e po spu&#x161;t&#x11B;n&#xED; aplikace nen&#xED; zvolena &#x17E;&#xE1;dn&#xE1; polo&#x17E;ka a <code>self.selected</code> nen&#xED; definov&#xE1;n, je rozumn&#xE9; prost&#x11B; n&#x11B;jakou polo&#x17E;ku zvolit:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"c1\"># Za p&#x159;id&#xE1;n&#xED;m polo&#x17E;ek do palety a napojen&#xED; sign&#xE1;lu</span>\n<span class=\"n\">palette</span><span class=\"o\">.</span><span class=\"n\">setCurrentRow</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n</pre></div><h3>V&#xED;ce tla&#x10D;&#xED;tek my&#x161;i</h3>\n<p>M&#x16F;&#x17E;ete si vyzkou&#x161;et, &#x17E;e se mapa m&#x11B;n&#xED; p&#x159;i pou&#x17E;it&#xED; jak&#xE9;hokoliv tla&#x10D;&#xED;tka my&#x161;i.\nJe to proto, &#x17E;e <code>mousePressEvent</code> se stane, kdykoli na widgetu stiskneme libovoln&#xE9; tla&#x10D;&#xED;tko.\nPokud bychom cht&#x11B;li &#x159;e&#x161;it pouze lev&#xE9; (prim&#xE1;rn&#xED;) tla&#x10D;&#xED;tko, m&#x16F;&#x17E;eme zjistit, kter&#xE9; tla&#x10D;&#xED;tko ud&#xE1;lost vyvolalo:</p>\n<div class=\"highlight\"><pre><span></span>            <span class=\"k\">if</span> <span class=\"n\">event</span><span class=\"o\">.</span><span class=\"n\">button</span><span class=\"p\">()</span> <span class=\"o\">==</span> <span class=\"n\">QtCore</span><span class=\"o\">.</span><span class=\"n\">Qt</span><span class=\"o\">.</span><span class=\"n\">LeftButton</span><span class=\"p\">:</span>\n                <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">array</span><span class=\"p\">[</span><span class=\"n\">row</span><span class=\"p\">,</span> <span class=\"n\">column</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">selected</span>\n            <span class=\"k\">else</span><span class=\"p\">:</span>\n                <span class=\"k\">return</span>\n            <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">update</span><span class=\"p\">(</span><span class=\"o\">*</span><span class=\"n\">logical_to_pixels</span><span class=\"p\">(</span><span class=\"n\">row</span><span class=\"p\">,</span> <span class=\"n\">column</span><span class=\"p\">),</span> <span class=\"n\">CELL_SIZE</span><span class=\"p\">,</span> <span class=\"n\">CELL_SIZE</span><span class=\"p\">)</span>\n</pre></div><p>Na prav&#xE9; tla&#x10D;&#xED;tko my&#x161;i m&#x16F;&#x17E;eme namapovat funkci maz&#xE1;n&#xED;:</p>\n<div class=\"highlight\"><pre><span></span>            <span class=\"k\">elif</span> <span class=\"n\">event</span><span class=\"o\">.</span><span class=\"n\">button</span><span class=\"p\">()</span> <span class=\"o\">==</span> <span class=\"n\">QtCore</span><span class=\"o\">.</span><span class=\"n\">Qt</span><span class=\"o\">.</span><span class=\"n\">RightButton</span><span class=\"p\">:</span>\n                <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">array</span><span class=\"p\">[</span><span class=\"n\">row</span><span class=\"p\">,</span> <span class=\"n\">column</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"mi\">0</span>\n</pre></div><h3>Ta&#x17E;en&#xED; my&#x161;i</h3>\n<p>Pro spln&#x11B;n&#xED; &#xFA;kolu bude sta&#x10D;it objekty mazat a kl&#xE1;st pomoc&#xED; klik&#xE1;n&#xED; na jednotliv&#xE1; pol&#xED;&#x10D;ka.\nPokud v&#x161;ak chcete poskytnout u&#x17E;ivateli v&#x11B;t&#x161;&#xED; komfort, prozkoumejte dal&#x161;&#xED; <a href=\"http://doc.qt.io/qt-5/qwidget.html\">ud&#xE1;losti</a>\na m&#x16F;&#x17E;ete pol&#xED;&#x10D;ka nan&#xE1;&#x161;et/mazat i p&#x159;i kliknut&#xED; a t&#xE1;hnut&#xED;.\n(Mo&#x17E;n&#xE1; tu naraz&#xED;te na probl&#xE9;m, kdy se p&#x159;i p&#x159;&#xED;li&#x161; rychl&#xE9;m pohybu my&#x161;i generuj&#xED; ud&#xE1;losti\npro body p&#x159;&#xED;li&#x161; daleko od sebe.\nKdy&#x17E; program nest&#xED;h&#xE1;, OS nebo Qt spojuje v&#xED;c ud&#xE1;lost&#xED; pohybu my&#x161;i dohromady a pos&#xED;l&#xE1;\njen jednu posledn&#xED;.\nJednotliv&#xE9; body m&#x16F;&#x17E;ete spojit &#x10D;&#xE1;rou pomoc&#xED; knihovny <a href=\"https://pypi.org/project/bresenham/\">bresenham</a>.)</p>\n<h2>Menu a mod&#xE1;ln&#xED; dialog</h2>\n<p>Na&#x161;e aplikace bude um&#x11B;t vytvo&#x159;it novou, pr&#xE1;zdnou mapu.\nUk&#xE1;&#x17E;eme si, jak vytvo&#x159;it mod&#xE1;ln&#xED; dialog pro volby (&#x161;&#xED;&#x159;ka a v&#xFD;&#x161;ka nov&#xE9; mapy).\n&#x201E;Mod&#xE1;ln&#xED; dialog&#x201C; znamen&#xE1; okno, kter&#xE9; mus&#xED; u&#x17E;ivatel zav&#x159;&#xED;t, ne&#x17E; m&#x16F;&#x17E;e pracovat se zbytkem aplikace.</p>\n<p>Layout okna nejprve naklik&#xE1;me v Qt Designeru:</p>\n<ol>\n<li>Po spu&#x161;t&#x11B;n&#xED; zvol&#xED;me <em>Dialog with Buttons Bottom</em> a <em>Create</em>.</li>\n<li>P&#x159;es prav&#xE9; tla&#x10D;&#xED;tko pro dialog zvol&#xED;me <em>Lay Out &#x2023; Vertically</em>.</li>\n<li>Nad tla&#x10D;&#xED;tka <em>Cancel</em> a <em>OK</em> p&#x159;et&#xE1;hneme z <em>Widet Box</em> <em>Form Layout</em> (layouty lze takto p&#x159;&#xED;mo vno&#x159;ovat).</li>\n<li>Do n&#x11B;j p&#x159;et&#xE1;hneme postupn&#x11B; dvakr&#xE1;t <em>Label</em> a <em>Spin Box</em>, abychom vytvo&#x159;ili formul&#xE1;&#x159;.</li>\n<li>P&#x159;ejmenujeme v panelu <em>Property Editor</em> jednotliv&#xE9; p&#x159;idan&#xE9; polo&#x17E;ky tak, aby d&#xE1;valy v k&#xF3;du smysl (<code>widthBox</code>, <code>heightBox</code>).</li>\n<li>Poklik&#xE1;n&#xED;m na <em>Labely</em> zm&#x11B;n&#xED;me jejich text.</li>\n<li>Nastav&#xED;me v panelu <em>Property Editor</em> rozumn&#xE9; limity a v&#xFD;choz&#xED; hodnoty pro <em>Spin Boxy</em>.</li>\n<li>Okno p&#x159;&#xED;padn&#x11B; zmen&#x161;&#xED;me, aby nebylo zbyte&#x10D;n&#x11B; velk&#xE9;.</li>\n<li>V menu zvol&#xED;me <em>Edit &#x2023; Edit Buddies</em> a t&#xE1;hnut&#xED;m z <em>Labelu</em> na <em>Spin Box</em> nastav&#xED;me, ke kter&#xE9;mu prvku se <em>Label</em> vztahuje.</li>\n<li>V menu zvol&#xED;me <em>Edit &#x2023; Edit Tab Order</em> a zkontrolujeme, &#x17E;e po&#x159;ad&#xED;, ve kter&#xE9;m bude prvky vyb&#xED;rat kl&#xE1;vesa <code>Tab</code>, je rozumn&#xE9;.</li>\n<li>M&#x16F;&#x17E;eme se vr&#xE1;tit zp&#x11B;t na <em>Edit &#x2023; Edit Widgets</em>.</li>\n<li>Dialog ulo&#x17E;&#xED;me jako <code>newmaze.ui</code>.</li>\n</ol>\n<p>Pot&#xE9; p&#x159;iprav&#xED;me funkci pro zobrazen&#xED; dialogu a pro jeho vyhodnocen&#xED;:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"k\">def</span> <span class=\"nf\">new_dialog</span><span class=\"p\">(</span><span class=\"n\">window</span><span class=\"p\">,</span> <span class=\"n\">grid</span><span class=\"p\">):</span>\n    <span class=\"c1\"># Vytvo&#x159;&#xED;me nov&#xFD; dialog.</span>\n    <span class=\"c1\"># V dokumentaci maj&#xED; dialogy jako argument `this`;</span>\n    <span class=\"c1\"># jde o &quot;nad&#x159;azen&#xE9;&quot; okno.</span>\n    <span class=\"n\">dialog</span> <span class=\"o\">=</span> <span class=\"n\">QtWidgets</span><span class=\"o\">.</span><span class=\"n\">QDialog</span><span class=\"p\">(</span><span class=\"n\">window</span><span class=\"p\">)</span>\n\n    <span class=\"c1\"># Na&#x10D;teme layout z Qt Designeru.</span>\n    <span class=\"k\">with</span> <span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"s1\">&apos;newmaze.ui&apos;</span><span class=\"p\">)</span> <span class=\"k\">as</span> <span class=\"n\">f</span><span class=\"p\">:</span>\n        <span class=\"n\">uic</span><span class=\"o\">.</span><span class=\"n\">loadUi</span><span class=\"p\">(</span><span class=\"n\">f</span><span class=\"p\">,</span> <span class=\"n\">dialog</span><span class=\"p\">)</span>\n\n    <span class=\"c1\"># Zobraz&#xED;me dialog.</span>\n    <span class=\"c1\"># Funkce exec zajist&#xED; modalitu (tzn. nejde ovl&#xE1;dat zbytek aplikace,</span>\n    <span class=\"c1\"># dokud je dialog zobrazen) a vr&#xE1;t&#xED; se a&#x17E; potom, co u&#x17E;ivatel dialog zav&#x159;e.</span>\n    <span class=\"n\">result</span> <span class=\"o\">=</span> <span class=\"n\">dialog</span><span class=\"o\">.</span><span class=\"k\">exec</span><span class=\"p\">()</span>\n\n    <span class=\"c1\"># V&#xFD;sledn&#xE1; hodnota odpov&#xED;d&#xE1; tla&#x10D;&#xED;tku/zp&#x16F;sobu, kter&#xFD;m u&#x17E;ivatel dialog zav&#x159;el.</span>\n    <span class=\"k\">if</span> <span class=\"n\">result</span> <span class=\"o\">==</span> <span class=\"n\">QtWidgets</span><span class=\"o\">.</span><span class=\"n\">QDialog</span><span class=\"o\">.</span><span class=\"n\">Rejected</span><span class=\"p\">:</span>\n        <span class=\"c1\"># Dialog u&#x17E;ivatel zav&#x159;el nebo klikl na Cancel.</span>\n        <span class=\"k\">return</span>\n\n    <span class=\"c1\"># Na&#x10D;ten&#xED; hodnot ze SpinBox&#x16F;</span>\n    <span class=\"n\">cols</span> <span class=\"o\">=</span> <span class=\"n\">dialog</span><span class=\"o\">.</span><span class=\"n\">findChild</span><span class=\"p\">(</span><span class=\"n\">QtWidgets</span><span class=\"o\">.</span><span class=\"n\">QSpinBox</span><span class=\"p\">,</span> <span class=\"s1\">&apos;widthBox&apos;</span><span class=\"p\">)</span><span class=\"o\">.</span><span class=\"n\">value</span><span class=\"p\">()</span>\n    <span class=\"n\">rows</span> <span class=\"o\">=</span> <span class=\"n\">dialog</span><span class=\"o\">.</span><span class=\"n\">findChild</span><span class=\"p\">(</span><span class=\"n\">QtWidgets</span><span class=\"o\">.</span><span class=\"n\">QSpinBox</span><span class=\"p\">,</span> <span class=\"s1\">&apos;heightBox&apos;</span><span class=\"p\">)</span><span class=\"o\">.</span><span class=\"n\">value</span><span class=\"p\">()</span>\n\n    <span class=\"c1\"># Vytvo&#x159;en&#xED; nov&#xE9; mapy</span>\n    <span class=\"n\">grid</span><span class=\"o\">.</span><span class=\"n\">array</span> <span class=\"o\">=</span> <span class=\"n\">numpy</span><span class=\"o\">.</span><span class=\"n\">zeros</span><span class=\"p\">((</span><span class=\"n\">rows</span><span class=\"p\">,</span> <span class=\"n\">cols</span><span class=\"p\">),</span> <span class=\"n\">dtype</span><span class=\"o\">=</span><span class=\"n\">numpy</span><span class=\"o\">.</span><span class=\"n\">int8</span><span class=\"p\">)</span>\n\n    <span class=\"c1\"># Mapa m&#x16F;&#x17E;e b&#xFD;t jinak velk&#xE1;, tak mus&#xED;me zm&#x11B;nit velikost Gridu;</span>\n    <span class=\"c1\"># (tento k&#xF3;d pou&#x17E;&#xED;v&#xE1;me i jinde, m&#x11B;li bychom si na to ud&#x11B;lat funkci!)</span>\n    <span class=\"n\">size</span> <span class=\"o\">=</span> <span class=\"n\">logical_to_pixels</span><span class=\"p\">(</span><span class=\"n\">rows</span><span class=\"p\">,</span> <span class=\"n\">cols</span><span class=\"p\">)</span>\n    <span class=\"n\">grid</span><span class=\"o\">.</span><span class=\"n\">setMinimumSize</span><span class=\"p\">(</span><span class=\"o\">*</span><span class=\"n\">size</span><span class=\"p\">)</span>\n    <span class=\"n\">grid</span><span class=\"o\">.</span><span class=\"n\">setMaximumSize</span><span class=\"p\">(</span><span class=\"o\">*</span><span class=\"n\">size</span><span class=\"p\">)</span>\n    <span class=\"n\">grid</span><span class=\"o\">.</span><span class=\"n\">resize</span><span class=\"p\">(</span><span class=\"o\">*</span><span class=\"n\">size</span><span class=\"p\">)</span>\n\n    <span class=\"c1\"># P&#x159;ekreslen&#xED; cel&#xE9;ho Gridu</span>\n    <span class=\"n\">grid</span><span class=\"o\">.</span><span class=\"n\">update</span><span class=\"p\">()</span>\n\n\n<span class=\"k\">def</span> <span class=\"nf\">main</span><span class=\"p\">():</span>\n    <span class=\"o\">...</span>\n\n\n     <span class=\"c1\"># Napojen&#xED; sign&#xE1;lu actionNew.triggered</span>\n\n    <span class=\"n\">action</span> <span class=\"o\">=</span> <span class=\"n\">window</span><span class=\"o\">.</span><span class=\"n\">findChild</span><span class=\"p\">(</span><span class=\"n\">QtWidgets</span><span class=\"o\">.</span><span class=\"n\">QAction</span><span class=\"p\">,</span> <span class=\"s1\">&apos;actionNew&apos;</span><span class=\"p\">)</span>\n    <span class=\"n\">action</span><span class=\"o\">.</span><span class=\"n\">triggered</span><span class=\"o\">.</span><span class=\"n\">connect</span><span class=\"p\">(</span><span class=\"k\">lambda</span><span class=\"p\">:</span> <span class=\"n\">new_dialog</span><span class=\"p\">(</span><span class=\"n\">window</span><span class=\"p\">,</span> <span class=\"n\">grid</span><span class=\"p\">))</span>\n</pre></div><p>Dal&#x161;&#xED; dialogy, kter&#xE9; budeme pot&#x159;ebovat, jsou tak roz&#x161;&#xED;&#x159;en&#xE9; (a mezi jednotliv&#xFD;mi platformami tak r&#x16F;zn&#xE9;),\n&#x17E;e je Qt m&#xE1; p&#x159;edp&#x159;ipraven&#xE9;.\nJsou to dialogy pro uk&#xE1;z&#xE1;n&#xED; hl&#xE1;&#x161;ky, v&#xFD;b&#x11B;r souboru, barvy nebo fontu nebo pro nastaven&#xED; tisku.</p>\n<p>Tyto p&#x159;edp&#x159;ipraven&#xE9; dialogy maj&#xED; typicky statick&#xE9; metody, kter&#xE9; dialog vytvo&#x159;&#xED; a p&#x159;&#xED;mo zavolaj&#xED;\n<code>exec()</code> a vr&#xE1;t&#xED; v&#xFD;sledek.</p>\n<p>Pro spln&#x11B;n&#xED; &#xFA;kolu (dal&#x161;&#xED;ch polo&#x17E;ek v menu) se v&#xE1;m m&#x16F;&#x17E;ou hodit tyto dialogy:</p>\n<ul>\n<li><a href=\"http://doc.qt.io/qt-5/qfiledialog.html#getOpenFileName\">QtWidgets.QFileDialog.getOpenFileName</a></li>\n<li><a href=\"http://doc.qt.io/qt-5/qfiledialog.html#getSaveFileName\">QtWidgets.QFileDialog.getSaveFileName</a></li>\n<li><a href=\"http://doc.qt.io/qt-5/qmessagebox.html#critical\">QtWidgets.QMessageBox.critical</a></li>\n<li><a href=\"http://doc.qt.io/qt-5/qmessagebox.html#about\">QtWidgets.QMessageBox.about</a><ul>\n<li>Tip: Do <em>QMessageBoxu</em> jde d&#xE1;vat i HTML (ale v &#x159;et&#x11B;zci s n&#xED;m nesm&#xED; b&#xFD;t zalomeny &#x159;&#xE1;dky, jinak to nefunguje)</li>\n<li>Tip: Kdy&#x17E; hlavn&#xED;mu oknu aplikace nastav&#xED;te ikonu (<code>setWindowIcon(icon)</code>), v <em>About</em> dialogu bude automaticky vid&#x11B;t</li>\n</ul>\n</li>\n</ul>\n<h2>T&#x159;&#xED;da pro GUI aplikace</h2>\n<p>Funkce <code>main</code> se n&#xE1;m pomalu rozr&#x16F;st&#xE1; a dal&#x161;&#xED; funkce, kter&#xE9; vol&#xE1;, mus&#xED; b&#xFD;t bu&#x10F; definovan&#xE9; v n&#xED; (jako <code>item_activated</code>)\nnebo mus&#xED; br&#xE1;t relativn&#x11B; hodn&#x11B; argument&#x16F; (jako <code>new_dialog</code>).\nAbychom si zjednodu&#x161;ili pr&#xE1;ci, m&#x16F;&#x17E;eme logiku m&#xED;sto do funkce d&#xE1;t do t&#x159;&#xED;dy, ve kter&#xE9; si d&#x16F;le&#x17E;it&#xE9; prvky\nulo&#x17E;&#xED;me do atribut&#x16F; (<code>self.grid</code>, <code>self.window</code>, <code>self.app</code> atd.).\nDoporu&#x10D;ujeme ud&#x11B;lat p&#x159;&#xED;pravu v <code>__init__</code> a vol&#xE1;n&#xED; <code>window.show()</code> a <code>return app.exec()</code> d&#xE1;t do metody <code>run</code>.</p>\n<p>A to je zat&#xED;m v&#x161;e!\nDal&#x161;&#xED; vylep&#x161;en&#xED; budete m&#xED;t za &#xFA;kol &#x2013; nebo si aplikaci p&#x159;etvo&#x159;te podle sv&#xE9;ho uv&#xE1;&#x17E;en&#xED;.</p>\n\n\n        "
    }
  }
}