Devadesátková hra Brány Skeldalu prošla portací a je dostupná na platformě Steam. Vyšel i parádní blog autora o portaci na moderní systémy a platformy včetně Linuxu.
Lidi dělají divné věci. Například spouští Linux v Excelu. Využít je emulátor RISC-V mini-rv32ima sestavený jako knihovna DLL, která je volaná z makra VBA (Visual Basic for Applications).
Revolut nabídne neomezený mobilní tarif za 12,50 eur (312 Kč). Aktuálně startuje ve Velké Británii a Německu.
Společnost Amazon miliardáře Jeffa Bezose vypustila na oběžnou dráhu první várku družic svého projektu Kuiper, který má z vesmíru poskytovat vysokorychlostní internetové připojení po celém světě a snažit se konkurovat nyní dominantnímu Starlinku nejbohatšího muže planety Elona Muska.
Poslední aktualizací začal model GPT-4o uživatelům příliš podlézat. OpenAI jej tak vrátila k předchozí verzi.
Google Chrome 136 byl prohlášen za stabilní. Nejnovější stabilní verze 136.0.7103.59 přináší řadu novinek z hlediska uživatelů i vývojářů. Podrobný přehled v poznámkách k vydání. Opraveno bylo 8 bezpečnostních chyb. Vylepšeny byly také nástroje pro vývojáře.
Homebrew (Wikipedie), správce balíčků pro macOS a od verze 2.0.0 také pro Linux, byl vydán ve verzi 4.5.0. Na stránce Homebrew Formulae lze procházet seznamem balíčků. K dispozici jsou také různé statistiky.
Byl vydán Mozilla Firefox 138.0. Přehled novinek v poznámkách k vydání a poznámkách k vydání pro vývojáře. Řešeny jsou rovněž bezpečnostní chyby. Nový Firefox 138 je již k dispozici také na Flathubu a Snapcraftu.
Šestnáctý ročník ne-konference jOpenSpace se koná 3. – 5. října 2025 v Hotelu Antoň v Telči. Pro účast je potřeba vyplnit registrační formulář. Ne-konference neznamená, že se organizátorům nechce připravovat program, ale naopak dává prostor všem pozvaným, aby si program sami složili z toho nejzajímavějšího, čím se v poslední době zabývají nebo co je oslovilo. Obsah, který vytvářejí všichni účastníci, se skládá z desetiminutových
… více »WebKit je framework pro vykreslování HTML, jehož port je v Qt dostupný od verze 4.4.
Qt poskytuje widget QWebView
, což je z uživatelského hlediska prostor pro vykreslení webové stránky. Když jej propojíme s několika málo ovládacími prvky, tak získáme relativně schopný webový prohlížeč s podporou tabů a sotva dvěma sty řádky kódu (viz níže). Pro představu: načtení stránky spočívá ve spuštění metody load()
s argumentem URL.
Rozhraní s taby poskytuje třída QTabWidget
, jejíž použití je celkem prosté. Do rozhraní se přidávají taby metodou addTab()
, které se jako argument mj. předá widget, který bude na tabu zobrazený. Po zavolání metody setTabsClosable(true)
lze taby i zavírat. Obsluhu zavření musíte poskytnout vy sami. K dispozici máte signál tabCloseRequested()
s indexem jako argument. Ten tedy napojíte na váš slot, který se postará o korektní zničení tabu na daném indexu. Odstranění tabu obstarává metoda removeTab()
, která však nesmaže widget, který na tabu byl zobrazený, takže ve většině případů je lepší smazat tento widget, což rovněž zapříčiní zavření tabu. Přepínání tabů funguje i bez zásahů, ale v případě potřeby lze obsloužit reakcí na signál currentChanged()
s indexem nového tabu jako argument, čehož v ukázce využívám.
Při pročítání kódu si všimněte praktického použití QRegExp, o kterém jsme mluvili minule. Novinkou je zde odpojení signálů od slotů. Chceme-li jednoduše odpojit všechno, co je napojené na signály widgetu, můžeme zavolat buď metodu daného widgetu:
widget->disconnect()
nebo statickou metodu třídy QObject:
disconnect(widget, 0, 0, 0)
Pro více ukázek se podívejte do dokumentace k QObject::disconnect()
.
Při vytváření GUI projektu v Qt Creatoru nezapomeňte povolit modul QtWebKit nebo případně přidat do .pro souboru:
QT += webkit
To zajistí doplnění cest ke hlavičkovým souborům a linkování s knihovnami WebKitu.
browser.h
: API.
#ifndef BROWSER_H #define BROWSER_H #include <QMainWindow> #include <QUrl> class QLabel; class QLineEdit; class QProgressBar; class QTabWidget; class QWebView; class Browser : public QMainWindow { Q_OBJECT public: Browser(QWidget *parent = 0); private: QLabel* statusLabel; QLineEdit* urlBar; QProgressBar* loading; QTabWidget* tabs; // "web" je widget poskytující webový prohlížeč QWebView* web; private slots: void addNewTab(); void tabChanged(int); void closeTab(int); void setLoadingStatus(bool); void loadUrl(); void setUrl(QUrl); void changeTitle(QString); void goBack(); void goForward(); void reload(); void stop(); }; #endif // BROWSER_H
browser.cpp
: Z důvodu přehlednosti jsem navrhl GUI ručně, místo použití Designeru, který by mi v tomto případě práci ani příliš neusnadnil. Novinkou je zde použití qobject_cast
místo static_cast
pro přetypování. qobject_cast
se chová podobně jako dynamic_cast
v C++, ale má několik výhod; například nepotřebuje RTTI. Funguje pouze na objekty, které přímo či nepřímo dědí QObject
a jsou deklarovány s makrem Q_OBJECT
.
#include "browser.h" #include <QAction> #include <QKeySequence> #include <QLabel> #include <QLineEdit> #include <QProgressBar> #include <QPushButton> #include <QRegExp> #include <QStatusBar> #include <QStyle> #include <QTabWidget> #include <QToolBar> #include <QWebView> Browser::Browser(QWidget *parent) : QMainWindow(parent), web(0) { // vytvoříme widgety: // políčko pro URL urlBar = new QLineEdit; // rozhraní s taby tabs = new QTabWidget; // tlačítko napravo od tabů pro vytvoření nového QPushButton* newTabBtn = new QPushButton(style()->standardIcon(QStyle::SP_FileDialogNewFolder), "", tabs); // text ve stavovém řádku statusLabel = new QLabel; // průběh načítání stránky ve stavovém řádku loading = new QProgressBar; // panel nástrojů (obsahující ovládací prvky prohlížeče) QToolBar* toolBar = addToolBar(tr("Controls")); // přidáme akce do panelu nástrojů a rovnou je napojíme na sloty toolBar->addAction(style()->standardIcon(QStyle::SP_ArrowBack), tr("Back"), this, SLOT(goBack())); toolBar->addAction(style()->standardIcon(QStyle::SP_ArrowForward), tr("Forward"), this, SLOT(goForward())); toolBar->addAction(style()->standardIcon(QStyle::SP_BrowserReload), tr("Reload"), this, SLOT(reload())); toolBar->addAction(style()->standardIcon(QStyle::SP_BrowserStop), tr("Stop"), this, SLOT(stop())); // uložíme si ukazatel akce pro vytvoření nového tabu QAction* actionAddTab = toolBar->addAction(style()->standardIcon(QStyle::SP_FileDialogNewFolder), tr("New tab"), this, SLOT(addNewTab())); // a přiřadíme jí klávesovou zkratku actionAddTab->setShortcut(QKeySequence("Ctrl+T")); // přidáme adresní řádek toolBar->addWidget(urlBar); toolBar->addAction(style()->standardIcon(QStyle::SP_CommandLink), tr("Go"), this, SLOT(loadUrl())); // propojíme tlačítko pro přidání tabů connect(newTabBtn, SIGNAL(clicked()), this, SLOT(addNewTab())); // reakce na změnu aktuálního tabu connect(tabs, SIGNAL(currentChanged(int)), this, SLOT(tabChanged(int))); // reakce na zavření daného tabu connect(tabs, SIGNAL(tabCloseRequested(int)), this, SLOT(closeTab(int))); // reakce stisknutí Enteru v adresním řádku connect(urlBar, SIGNAL(returnPressed()), this, SLOT(loadUrl())); // přidáme tlačítko vedle tabů tabs->setCornerWidget(newTabBtn); // povolíme přesouvání tabů tabs->setMovable(true); // nastavíme pevnou šířku ukazateli průběhu loading->setFixedWidth(100); // nastavíme hlavnímu oknu: // hlavní widget (rozhraní s taby) setCentralWidget(tabs); // a stavový řádek setStatusBar(new QStatusBar); // popisek ve stavovém řádku nebude zvětšovat okno statusBar()->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred); // přidáme do stavového řádku popisek statusBar()->addWidget(statusLabel); // a ukazatel průběhu statusBar()->addPermanentWidget(loading); // vytvoříme první tab addNewTab(); // nastavíme do adresního řádku adresu abclinuxu urlBar->setText("http://www.abclinuxu.cz"); // a stránku načteme loadUrl(); // nastavíme výchozí velikost okna resize(950, 700); } // vytvoří nový tab void Browser::addNewTab() { // přidáme tab int index = tabs->addTab(new QWebView, tr("New tab")); // pokud máme víc než 1 tab, if(tabs->count() > 1) { // přepneme se na tab nově vytvořený tabs->setCurrentIndex(index); // a povolíme zavírání tabů tabs->setTabsClosable(true); } // vyprázdníme adresní řádek urlBar->setText(""); // vynulujeme ukazatel průběhu načítání loading->reset(); // nastavíme titulek okna setWindowTitle("WebKit browser"); } void Browser::tabChanged(int index) { // pokud si zavřeme poslední tab, dostaneme index o hodnotě -1 // a k tomu by nemělo dojít, takže v debug. verzi shodíme program Q_ASSERT(index >= 0); // v obyčejné verzi se problém pokusíme obejít přidáním nového tabu if(index < 0) { addNewTab(); return; } if(web) { // odpojíme tab ze kterého přepínáme od GUI slotů (viz níže) disconnect(web, SIGNAL(urlChanged(QUrl)), this, SLOT(setUrl(QUrl))); disconnect(web, SIGNAL(titleChanged(QString)), this, SLOT(changeTitle(QString))); disconnect(web, SIGNAL(loadProgress(int)), loading, SLOT(setValue(int))); disconnect(web, SIGNAL(loadFinished(bool)), this, SLOT(setLoadingStatus(bool))); disconnect(web->page(), SIGNAL(linkHovered(QString,QString,QString)), statusLabel, SLOT(setText(QString))); } // získáme prohlížeč aktuálního tabu web = qobject_cast<QWebView*>( tabs->widget(index) ); // a napojíme jej na GUI: // projeví změnu URL z prohlížeče do adresního řádku connect(web, SIGNAL(urlChanged(QUrl)), this, SLOT(setUrl(QUrl))); // nastaví titulek stránky jako titulek okna a tabu connect(web, SIGNAL(titleChanged(QString)), this, SLOT(changeTitle(QString))); // zobrazuje průběh načítání connect(web, SIGNAL(loadProgress(int)), loading, SLOT(setValue(int))); connect(web, SIGNAL(loadFinished(bool)), this, SLOT(setLoadingStatus(bool))); // když kurzor najede na odkaz, zobrazíme jeho cíl ve stavovém řádku connect(web->page(), SIGNAL(linkHovered(QString,QString,QString)), statusLabel, SLOT(setText(QString))); // načteme správnou adresu do adresního řádku setUrl(web->url()); // vyprázdníme text ve stavovém řádku statusLabel->setText(""); // změníme titulek okna setWindowTitle(tabs->tabText(tabs->currentIndex())); } // zavře tab s daným indexem void Browser::closeTab(int index) { // v případě, že zavíráme aktuální tab, nastavíme web na 0, aby se tabChanged() nepokoušelo // odpojovat neexistující widget if(tabs->widget(index) == web) web = 0; // smazání widgetu zavře tab delete tabs->widget(index); // pokud po zavření akt. tabu zbyl už jen jeden, zakážeme jeho zavření if(tabs->count() < 2) tabs->setTabsClosable(false); } // informuje ve stavovém řádku o úspěšnosti načtení stránky void Browser::setLoadingStatus(bool ok) { QString text = tr("OK"); if(!ok) text = tr("Loading failed"); statusLabel->setText(text); } // načte adresu zadanou v adresním řádku void Browser::loadUrl() { QString url = urlBar->text(); // pokud daná adresa neobsahuje protokol, předpokládáme http:// if(!url.contains(QRegExp("^[a-z]+://"))) url.prepend("http://"); // načteme připravenou adresu web->load(QUrl(url, QUrl::TolerantMode)); } // nastaví dané URL do adresního řádku void Browser::setUrl(QUrl url) { // z URL odstraníme přihlašovací informace urlBar->setText(url.toString(QUrl::RemoveUserInfo)); } // nastaví titulek okna a aktuálního tabu void Browser::changeTitle(QString title) { if(title.isEmpty()) title = web->url().toString(QUrl::RemoveUserInfo); setWindowTitle(title); tabs->setTabText(tabs->currentIndex(), title); } // "Zpět" v prohlížeči void Browser::goBack() { web->back(); } // "Vpřed" v prohlížeči void Browser::goForward() { web->forward(); } // "Obnovit" v prohlížeči void Browser::reload() { web->reload(); } // "Stop" v prohlížeči void Browser::stop() { web->stop(); }
Zdrojáky si můžete stáhnout v archívu browser.tar.bz2.
Phonon je multiplatformní multimediální framework, který umožňuje přehrávat multimédia přes různé enginy, jako je Xine a GStreamer. Byl vyvinut pro použití v KDE 4 a Qt jej obsahuje od verze 4.4. Phonon pro KDE 4 je kompatibilní s tím, který je součástí Qt, takže pokud máte KDE 4, tak do .pro souboru přidejte kromě
QT += phonon
což zajistí linkování s tímto modulem, navíc ještě
INCLUDEPATH += /usr/include/KDE
Tímto umožníte chod vašeho Qt programu, který používá Phonon, uživatelům KDE 4 (na unixech).
Základní použití Phononu pro přehrávání zvuku je velice jednoduché. Stačí si vytvořit objekt reprezentující zvukový výstup (Phonon::AudioOutput
), objekt pro samotné přehrávání (Phonon::MediaObject
) a následně tyto dva objekty propojit pomocí Phonon::createPath()
. Teď už jen předáme objektu pro přehrávání nějaké URL pomocí setCurrentSource()
a můžeme zavolat play()
pro zahájení přehrávání. Tolik tedy k Phononu.
Když chcete za běhu programu zobrazit okno (např. dialog s nastavením), postup je podobný jako při zobrazování hlavního okna metodou QWidget::show()
. Teď před námi ovšem stojí jedno rozhodnutí: bude naše okno modální? Modální okno blokuje přístup k ostatním oknům programu, zatímco nemodální se otevře jako nezávislé okno, obvykle s vlastní položkou v pruhu úloh na panelu. Tato vlastnost se nastavuje metodou setWindowModality()
a dává smysl pochopitelně jen u oken. Modálnost se rozděluje na dvě varianty, buď okno blokuje přístup k celému programu (výchozí chování) nebo jen k rodičovskému oknu.
Často se místo běžného okna (QWidget
) hodí použít dialog (QDialog
), jehož použití uvidíte v následující ukázce, kde nám poslouží jako dialog s nastavením. Existuje několik možností jak dialog zobrazit:
Metoda (slot) | Modálnost | Okamžitý return |
show() | respektuje nastavení, výchozí = nemodální | ano |
exec() | vynutí modální dialog | ne, vrací návratovou hodnotu |
open() | vynutí modální dialog blokující rodičovské okno | ano |
Jaký režim tedy používat - modální, nebo nemodální? Je to na vás. Vhodné je rozhodnout se na základě účelu daného dialogu. Pokud jde o velice jednoduchý dialog, jako třeba ten v mé ukázce, tak tam asi modálnost nikomu vadit nebude, ale pokud půjde třeba o rozsáhlejší dialog s nastavením, které bude možné aktivovat tlačítkem "Použít", potom stojí za zvážení použití nemodální okno. Důvod, proč někteří programátoři používají modální okna i tam, kde se přiliš nehodí, může být fakt, že nemodální jsou trochu složitější na naprogramování, jelikož slot pro zobrazení okna ihned vrací a programátor musí sám hlídat, aby stejný dialog nebylo možné vyvolat dvakrát. Rozdíl si můžete prohlédnout v ukázce obsažené v dokumentaci.
main.cpp
: Nastavíme název programu a organizace (nebo doménu) kvůli nastavení (QSettings
) a Phononu, který to vyžaduje pro D-Bus.
#include <QApplication> #include "player.h" int main(int argc, char *argv[]) { QApplication a(argc, argv); a.setApplicationName("SimplePhononPlayer"); a.setOrganizationDomain("watzke.cz"); Player w; w.setWindowTitle(a.applicationName()); w.show(); return a.exec(); }
player.h
: API.
#ifndef PLAYER_H #define PLAYER_H #include <QMainWindow> #include <QSettings> #include <Phonon/AudioOutput> #include <Phonon/MediaObject> class QLabel; using namespace Phonon; class Player : public QMainWindow { Q_OBJECT public: Player(QWidget *parent = 0); ~Player(); private: AudioOutput* ao; MediaObject* mo; QLabel* statusLabel; QSettings settings; private slots: void openSettings(); void playFile(); void setStatusMessage(Phonon::State, Phonon::State); }; #endif // PLAYER_H
player.cpp
: Zde je novinkou použití makra Q_UNUSED
. Nestojí za tím žádná věda, slouží to čistě jen k umlčení kompilátoru, který by si jinak stěžoval na nevyužitou proměnnou, které díky tomuto můžete nastavit smysluplný název.
#include "player.h" #include "settings.h" #include <QFileDialog> #include <QLabel> #include <QToolBar> #include <QStatusBar> #include <QStyle> #include <Phonon/VolumeSlider> #include <Phonon/SeekSlider> Player::Player(QWidget *parent) : QMainWindow(parent) { // vytvoříme zvukový výstup, ao = new AudioOutput(this); // objekt pro přehrávání multimédií mo = new MediaObject(this); // a propojíme je spolu Phonon::createPath(mo, ao); // vytvoříme a naplníme panel nástrojů QToolBar* toolBar = addToolBar(tr("Controls")); toolBar->addAction(style()->standardIcon(QStyle::SP_DialogOpenButton), tr("Select a file"), this, SLOT(playFile())); toolBar->addAction(style()->standardIcon(QStyle::SP_ComputerIcon), tr("Settings"), this, SLOT(openSettings())); toolBar->addSeparator(); toolBar->addAction(style()->standardIcon(QStyle::SP_MediaStop), tr("Stop"), mo, SLOT(stop())); toolBar->addAction(style()->standardIcon(QStyle::SP_MediaPause), tr("Pause"), mo, SLOT(pause())); toolBar->addAction(style()->standardIcon(QStyle::SP_MediaPlay), tr("Play"), mo, SLOT(play())); toolBar->addSeparator(); // přidáme widget pro ovládání hlasitosti, který Phonon nabízí toolBar->addWidget(new VolumeSlider(ao)); // vynutíme si text vedle ikon toolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); // jako hlavní widget nastavíme posuvník, který je součástí Phononu setCentralWidget(new SeekSlider(mo)); // vytvoříme stavový řádek setStatusBar(new QStatusBar); // a na něm textovou oblast pro zobrazování informací statusBar()->addWidget(statusLabel = new QLabel(tr("Idle"))); // dlouhé hlášky ve stavovém řádku nebudou roztahovat okno statusBar()->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred); // skryjeme úchyt pro změnu velikosti okna statusBar()->setSizeGripEnabled(false); // změna stavu přehrávání vyvolá zobrazení patřičné informace connect(mo, SIGNAL(stateChanged(Phonon::State,Phonon::State)), this, SLOT(setStatusMessage(Phonon::State,Phonon::State))); // nastavíme výchozí velikost okna na doporučené hodnoty resize(sizeHint()); // obnovíme geometrii okna; pokud není uložená, použijeme aktuální (žádná změna) restoreGeometry(settings.value("geometry", saveGeometry()).toByteArray()); } Player::~Player() { // pokud je nastavené zapamatování geometrie okna, uložíme ji, jinak záznam smažeme if(settings.value("Settings/rememberGeometry", false).toBool()) settings.setValue("geometry", saveGeometry()); else settings.remove("geometry"); } // otevře okno s nastavením void Player::openSettings() { // vytvoříme okno Settings window(settings); // vynutíme modálnost a zobrazíme okno window.exec(); } // spustí přehrávání zvoleného souboru void Player::playFile() { QString fileName = QFileDialog::getOpenFileName(this, tr("Select an audio file")); if(fileName.isEmpty()) return; mo->setCurrentSource(fileName); mo->play(); } // nastaví informaci o stavu do stavového řádku void Player::setStatusMessage(Phonon::State newState, Phonon::State prevState) { Q_UNUSED(prevState); QString status; switch(newState) { case Phonon::LoadingState: status = tr("Loading") + "..."; break; case Phonon::StoppedState: status = tr("Stopped"); break; case Phonon::PlayingState: status = tr("Playing ") + mo->currentSource().fileName(); break; case Phonon::BufferingState: status = tr("Buffering") + "..."; break; case Phonon::PausedState: status = tr("Paused"); break; case Phonon::ErrorState: status = tr("Error: ") + mo->errorString(); break; default: break; } if(!status.isEmpty()) { statusLabel->setText(status); // když uživatel najede kurzorem na informaci, zobrazí se mu v tooltipu, // což se může hodit, když se informace nevejde do okna celá statusLabel->setToolTip(status); } }
settings.h
: API.
#ifndef SETTINGS_H #define SETTINGS_H #include <QDialog> #include <QSettings> class QCheckBox; class Settings : public QDialog { Q_OBJECT public: Settings(QSettings& settings); private: QSettings* m_settings; QCheckBox* geometryCb; protected: void accept(); }; #endif // SETTINGS_H
settings.cpp
:
#include "settings.h" #include <QCheckBox> #include <QDialogButtonBox> #include <QVBoxLayout> Settings::Settings(QSettings& settings) { // uložíme si ukazatel na instanci objektu s nastavením m_settings = &settings; // vytvoříme zaškrtávací pole geometryCb = new QCheckBox(tr("Remember main window's geometry")); // a std. dialogová tlačítka, QDialogButtonBox* dialogButtons = new QDialogButtonBox; // konkrétně "OK" a "Zrušit" dialogButtons->setStandardButtons(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); // propojíme signály tlačítek s patřičnými sloty connect(dialogButtons, SIGNAL(accepted()), this, SLOT(accept())); connect(dialogButtons, SIGNAL(rejected()), this, SLOT(reject())); // vytvoříme rozložení ovl. prvků QVBoxLayout* layout = new QVBoxLayout(this); layout->addWidget(geometryCb); layout->addWidget(dialogButtons); // zaškrtávací pole odráží aktuální hodnotu nastavení if(m_settings->value("Settings/rememberGeometry", false).toBool()) geometryCb->setChecked(true); // titulek okna setWindowTitle(tr("Settings")); } // uloží nastavení a zavře dialog void Settings::accept() { m_settings->setValue("Settings/rememberGeometry", geometryCb->isChecked()); done(QDialog::Accepted); }
Zdrojáky si můžete stáhnout v archívu okna.tar.bz2.
V příštím díle si ukážeme, jak vytvářet překlady programů a jak k programům přibalit různá data (např. obrázky, zvuky, atp.).
Nástroje: Tisk bez diskuse
Tiskni
Sdílej:
Geniální. S Qt si hraju už dlouho a oceňuji, že mi někdo strčí pod nos takovou studnici nápadů. Tahat tyhle věci někde ze zdrojáků hotových aplikací je únavné - tady mám nápad přímo a bez příkras
Fakt skvělý seriál. Dík za vysvětlení "modálnosti". Hned využiji.