390 likes | 818 Views
Qt – Functionality, Custom Widgets. C++ GUI Programming with Qt 4 Qt 4.5 Reference Documentation Blanchette and Summerfield, Ch. 4,5. Overview. Another example of essential Qt concepts – “Application Example” Help > Qt Reference Documentation > Qt Examples > Application Example (in 2 nd pp)
E N D
Qt – Functionality,Custom Widgets C++ GUI Programming with Qt 4 Qt 4.5 Reference Documentation Blanchette and Summerfield, Ch. 4,5
Overview • Another example of essential Qt concepts – “Application Example” • Help > Qt Reference Documentation > Qt Examples > Application Example (in 2nd pp) • Class definition in <app>.h: • For application’s menus, toolbars, slots, function • Create (implement) in <app>.cpp: • Menus, toolbars, status bar • Qt “actions” for menu items • Function to be called upon selection, by connecting item action and function • connect(newAct, SIGNAL(triggered()), this, SLOT(newFile())); • And item stuff – shortcut, status tip • “Functionality”, or just functions, for menu items • Define resources • E.g., images
Application Example • Application with menus, toolbars, and status bar • Simple text editor program built around QTextEdit • “Powerful” Qt widget • Many others … • Documentation next
QTextEdit Reference, 1 • A widget that is a text editor! • Note properties • And where come from
QTextEdit Reference, 2 • Slots and signals
QTextEdit Reference, 3 • Functions
MainWindow Class Definitionmainwindow.h – as before, slots, functions, menus, etc. class MainWindow : public QMainWindow { Q_OBJECT class QAction; class QMenu; class QPlainTextEdit; public: MainWindow(); protected: void closeEvent(QCloseEvent *event); private slots: void newFile(); void open(); bool save(); bool saveAs(); void about(); void documentWasModified(); private: void createActions(); void createMenus(); void createToolBars(); void createStatusBar(); void readSettings(); void writeSettings(); boolmaybeSave(); void loadFile(const QString &fileName); boolsaveFile(const QString &fileName); void setCurrentFile(const QString &fileName); QStringstrippedName(const QString &fullFileName); QPlainTextEdit *textEdit; QStringcurFile; QMenu *fileMenu; QMenu *editMenu; QMenu *helpMenu; QToolBar *fileToolBar; QToolBar *editToolBar; QAction *newAct; …
The “Central Widget” • Depending on design - central area of main window • In non-Qt-ese • Can be: • Standard widget • E.g., QtextEdit, Qtable • Custom widget • Widgets with layout manager • Splitter (is like Q[V/H]Box) • MDI workspace (multiple wins)
MainWindow Class Implementationmainwindow.cpp #include <QtGui> #include "mainwindow.h" // Invoking MainWindow creates all MainWindow::MainWindow() { textEdit = new QPlainTextEdit; // One call for lots of functionality setCentralWidget(textEdit); createActions(); createMenus(); createToolBars(); createStatusBar(); readSettings(); // Standard to save and restore on start connect(textEdit->document(), SIGNAL(contentsChanged()), this, SLOT(documentWasModified())); setCurrentFile(""); // Setup for later use setUnifiedTitleAndToolBarOnMac(true); }
Application’s Menus • Similar to what seen • BTW - status bar at bottom of main window shows description of menu item or toolbar button under cursor
Creating Menu Items with Actionsmainwindow.cpp void MainWindow::createMenus() // Create the top level menus { fileMenu = menuBar()->addMenu(tr("&File")); fileMenu->addAction(newAct); fileMenu->addAction(openAct); fileMenu->addAction(saveAct); fileMenu->addAction(saveAsAct); fileMenu->addSeparator(); fileMenu->addAction(exitAct); editMenu = menuBar()->addMenu(tr("&Edit")); editMenu->addAction(cutAct); editMenu->addAction(copyAct); editMenu->addAction(pasteAct); menuBar()->addSeparator(); helpMenu = menuBar()->addMenu(tr("&Help")); helpMenu->addAction(aboutAct); helpMenu->addAction(aboutQtAct); }
Creating Toolbar and Status Bar void MainWindow::createToolBars() // Toolbar created as menu is { fileToolBar = addToolBar(tr("File")); fileToolBar->addAction(newAct); fileToolBar->addAction(openAct); fileToolBar->addAction(saveAct); editToolBar = addToolBar(tr("Edit")); editToolBar->addAction(cutAct); editToolBar->addAction(copyAct); editToolBar->addAction(pasteAct); } void MainWindow::createStatusBar() { statusBar()->showMessage(tr("Ready")); }
Create Actions, 1“Actions” specify what happens when menu item selected // “Actions” specify what happens when menu item selected void MainWindow::createActions() { // From createMenus(): “fileMenu->addAction(newAct);” newAct = new QAction(QIcon(":/images/new.png"), tr("&New"), this); // Note relative location of icon newAct->setShortcuts(QKeySequence::New); newAct->setStatusTip(tr("Create a new file")); connect(newAct, SIGNAL(triggered()), this, SLOT(newFile())); openAct = new QAction(QIcon(":/images/open.png"), tr("&Open..."), this); openAct->setShortcuts(QKeySequence::Open); openAct->setStatusTip(tr("Open an existing file")); connect(openAct, SIGNAL(triggered()), this, SLOT(open())); saveAct = new QAction(QIcon(":/images/save.png"), tr("&Save"), this); saveAct->setShortcuts(QKeySequence::Save); saveAct->setStatusTip(tr("Save the document to disk")); connect(saveAct, SIGNAL(triggered()), this, SLOT(save()));
Create Actions, 2 //Continue specifying actions for rest of menu items saveAsAct = new QAction(tr("Save &As..."), this); saveAsAct->setShortcuts(QKeySequence::SaveAs); saveAsAct->setStatusTip(tr("Save the document under a new name")); connect(saveAsAct, SIGNAL(triggered()), this, SLOT(saveAs())); exitAct = new QAction(tr("E&xit"), this); … cutAct = new QAction(QIcon(":/images/cut.png"), tr("Cu&t"), this); … copyAct = new QAction(QIcon(":/images/copy.png"), tr("&Copy"), this); … pasteAct = new QAction(QIcon(":/images/paste.png"), tr("&Paste"), this); … aboutAct = new QAction(tr("&About"), this); … aboutQtAct = new QAction(tr("About &Qt"), this); …
“Functionality Implemented”, 1newFile, open - functions // “Functionality” means everything that is done … // From createActions: // connect(newAct, SIGNAL(triggered()), this, SLOT(newFile())); void MainWindow::newFile() { if (maybeSave()) { textEdit->clear(); // Next page setCurrentFile(""); } } void MainWindow::open() { if (maybeSave()) { // Use Qt function QString fileName = QFileDialog::getOpenFileName(this); // Use it if you have it – next slide if (!fileName.isEmpty()) loadFile(fileName); } }
Reference • QFileDialog
“Functionality Implemented”, 2setCurrentFile – one function among many // “Maintain” file name // E.g., set “” with call from open void MainWindow::setCurrentFile(const QString &fileName) { curFile = fileName; textEdit->document()->setModified(false); setWindowModified(false); QString shownName; if (curFile.isEmpty()) shownName = "untitled.txt"; else shownName = strippedName(curFile); setWindowTitle(tr("%1[*] - %2").arg(shownName).arg(tr("Application"))); }
“Functionality Implemented”, 3save, saveAs – more functions bool MainWindow::save() { // curFile value set in func setCurrentFile if (curFile.isEmpty()) { return saveAs(); } else { return saveFile(curFile); } } bool MainWindow::saveAs() { // Use a Qt widget QString fileName = QFileDialog::getSaveFileName(this); if (fileName.isEmpty()) return false; return saveFile(fileName); }
“Functionality implemented”, 4saveFile – another function bool MainWindow::saveFile(const QString &fileName) { QFile file(fileName); if (!file.open(QFile::WriteOnly | QFile::Text)) { QMessageBox::warning(this, tr("Application"), tr("Cannot write file %1:\n%2.") .arg(fileName) .arg(file.errorString())); return false; } QTextStream out(&file); QApplication::setOverrideCursor(Qt::WaitCursor); out << textEdit->toPlainText(); QApplication::restoreOverrideCursor(); setCurrentFile(fileName); statusBar()->showMessage(tr("File saved"), 2000); // status bar message return true; }
“Functionality Implemented”, 5loadFile – another function void MainWindow::loadFile(const QString &fileName) { QFile file(fileName); if (!file.open(QFile::ReadOnly | QFile::Text)) { QMessageBox::warning(this, tr("Application"), tr("Cannot read file %1:\n%2.") .arg(fileName) .arg(file.errorString())); return; } QTextStream in(&file); QApplication::setOverrideCursor(Qt::WaitCursor); textEdit->setPlainText(in.readAll()); QApplication::restoreOverrideCursor(); setCurrentFile(fileName); statusBar()->showMessage(tr("File loaded"), 2000); }
“Functionality Implemented”, 6about void MainWindow::about() { QMessageBox::about(this, tr("About Application"), tr("The <b>Application</b> example demonstrates how to " "write modern GUI applications using Qt, with a menu bar, " "toolbars, and a status bar.")); }
Summary • Idea was to provide a “template”, or examples of functionality needed • There is more to life than file processing …
“Custom Widgets” • Of course, OO approach allows inheritance and subclassing • And it is good • Subclassing Qt widgets allows “simple” modifications to be simple • Will see the book’s example, which may be simple to the Qt professional! • Hex spin box • But first, simply using code to alter functionality works, too • And to combine separate widget functionality, as well • E.g., age spin and line edit boxes’ • Next slide • Also, changing public properties and calling public functions • This probably how you want to do it in your program • Custom widgets are fine example of how things are done once familiar with system – but, maybe not yet
RecallSynchronization & Layout of 2 Widgets • Using signals and slots of the QSpinbox and QSlider, set value of one depending on value set in the other • E.g., change spinbox value to 70, slider will move to appropriate position • Change slider to some position, spinbox value will be changed based on position spinBox->setRange(0, 130); // Set/define range of each slider->setRange(0, 130); // A particular widget signal causes a function (slot) to be called // here, when the value in the spinbox is changed, the function to set the value in the // slider is called, and passed the new value of the spinbox to be the new value of the slider QObject::connect(spinBox, SIGNAL(valueChanged(int)), slider, SLOT(setValue(int))); // … and vice versa QObject::connect(slider, SIGNAL(valueChanged(int)), spinBox, SLOT(setValue(int)));
Example – Hexadecimal Spin Box • Spin box that accepts and displays hexadecimal values • (sub) Class definition • HexSpinBox inherits most of its functionality from QSpinBox (and that is good) • Reimplements 2 virtual functions from QSpinBox // HexSpinBox.h – (sub)class definition #include <qspinbox.h> class HexSpinBox : public QSpinBox { public: HexSpinBox(QWidget *parent, const char *name = 0); protected: QString mapValueToText(int value); int mapTextToValue(bool *ok); };
Hex Spin Box Functionality • User can modify box’s value by clicking arrows or typing in value • For what entered, restrict input to valid hex numbers • Use QRegExpValidator #include <qvalidator.h> #include "hexspinbox.h“ // Subclass of QSpinBox – hex vs. the decimal of QSpinBox HexSpinBox::HexSpinBox(QWidget *parent, const char *name) : QSpinBox(parent, name) { QRegExp regExp("[0-9A-Fa-f]+"); // Reference next setValidator(new QRegExpValidator(regExp, this)); setRange(0, 255); }
Qt Reference • QRegExp • Regular expression manipulation
Change QSpinBox Functionality • mapValueToText: converts integer value to string • QSpinBox calls it to update editor part of spin box when user presses arrows • Use Qstring::number() with arg 16 to convert value to lower-case hex • Use Qstring::upper() on result to make uppercase • Will change this and mapText to value for subclassing – simple! • (in the way what Petzold did with c was simple – but this really is, just new maybe) QString HexSpinBox::mapValueToText(int value) { return QString::number(value, 16).upper(); } • mapTextToValue: converts string to integer • QSpinBox calls it when user presses enter (line editor) int HexSpinBox::mapTextToValue(bool *ok) { return text().toInt(ok, 16); }
Subclassing QWidget • Very general - can combine, adapt, modify, etc. existing widgets • As we just saw • Or, … can create whatever functionality desired by subclassing Qwidget • Here, will reimplement event handlers to paint widget and respond to mouse clicks • Which seems like a lot, but not too bad (for the Qt professional) • This why we looked at the windows API! • QLabel, QPushbutton, QTable implemented this way • Example – icon editor – IconEditor implementation
IconEditor.h, 1Define (sub) class #include <qimage.h> #include <qwidget.h> class IconEditor : public QWidget // <- main thing { Q_OBJECT Q_PROPERTY(QColor penColor READ penColor WRITE setPenColor) // custom properties Q_PROPERTY(QImage iconImage READ iconImage WRITE setIconImage) Q_PROPERTY(int zoomFactor READ zoomFactor WRITE setZoomFactor) public: IconEditor(QWidget *parent = 0, const char *name = 0); void setPenColor(const QColor &newColor); QColor penColor() const { return curColor; } void setZoomFactor(int newZoom); int zoomFactor() const { return zoom; } void setIconImage(const QImage &newImage); const QImage &iconImage() const { return image; } QSize sizeHint() const;
IconEditor.h, 2 Define (sub) class // Reimplements 3 (important!) functions from QWidget and has private variables for 3 values // (familiar looking variable names?) protected: void mousePressEvent(QMouseEvent *event); void mouseMoveEvent(QMouseEvent *event); void paintEvent(QPaintEvent *event); private: void drawImagePixel(QPainter *painter, int i, int j); // Accesses image, below void setImagePixel(const QPoint &pos, bool opaque); // “ QColor curColor; QImage image; // Principal data structure int zoom; };
IconEditor.cppAll the functions … #include <qpainter.h> #include "iconeditor.h“ IconEditor::IconEditor(QWidget *parent, const char *name) : QWidget(parent, name, WStaticContents) { setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); curColor = black; // Default (start up) values: zoom = 8; // E.g., render pixel in 8x8 square image.create(16, 16, 32); image.fill(qRgba(0, 0, 0, 0)); image.setAlphaBuffer(true); } QSize IconEditor::sizeHint() const { QSize size = zoom * image.size(); if (zoom >= 3) // Will show grid at higher zooms size += QSize(1, 1); return size; }
IconEditor.cpp void IconEditor::setPenColor(const QColor &newColor) { curColor = newColor; } void IconEditor::setIconImage(const QImage &newImage) { if (newImage != image) { image = newImage.convertDepth(32); // Set right form image.detach(); // Copy for faster update(); // Force a repaint – as invalidaterect! updateGeometry(); // So can use sizehint } }
IconEditor.cpp // paintEvent is an event handler! – this handles the paint event, rather than something else in Qt // (this is down in it) void IconEditor::paintEvent(QPaintEvent *) { QPainter painter(this); if (zoom >= 3) { // if need grid, draw lines painter.setPen(colorGroup().foreground()); for (int i = 0; i <= image.width(); ++i) // Qt drawLine function painter.drawLine(zoom * i, 0, zoom * i, zoom * image.height()); for (int j = 0; j <= image.height(); ++j) painter.drawLine(0, zoom * j, zoom * image.width(), zoom * j); } for (int i = 0; i < image.width(); ++i) { for (int j = 0; j < image.height(); ++j) drawImagePixel(&painter, i, j); // drawImagePixel next page … } }
IconEditor.cpp void IconEditor::drawImagePixel(QPainter *painter, int i, int j) // i, j coords inQImage, NOT in { // widget – it will have zoom lines QColor color; QRgb rgb = image.pixel(i, j); if (qAlpha(rgb) == 0) color = colorGroup().base(); else color.setRgb(rgb); if (zoom >= 3) { // will draw blocks (fillRect) for individual pixels – like, “way zoomed in” painter->fillRect(zoom * i + 1, zoom * j + 1, zoom - 1, zoom - 1, color); } else { painter->fillRect(zoom * i, zoom * j, zoom, zoom, color); } }
IconEditor.cpp // Reimplement mouse handling events void IconEditor::mousePressEvent(QMouseEvent *event) { if (event->button() == LeftButton) setImagePixel(event->pos(), true); // setImagePixel (next) changes image else if (event->button() == RightButton) // to be used in next paint event setImagePixel(event->pos(), false); } void IconEditor::mouseMoveEvent(QMouseEvent *event) { if (event->state() & LeftButton) setImagePixel(event->pos(), true); else if (event->state() & RightButton) setImagePixel(event->pos(), false); }
IconEditor.cpp void IconEditor::setImagePixel(const QPoint &pos, bool opaque) { int i = pos.x() / zoom; int j = pos.y() / zoom; if (image.rect().contains(i, j)) { if (opaque) image.setPixel(i, j, penColor().rgb()); else image.setPixel(i, j, qRgba(0, 0, 0, 0)); QPainter painter(this); drawImagePixel(&painter, i, j); } }
Just a Preview • … of what can be done with interface tools • Idea with the programming this semester was to provide a “foundation” of terms and techniques … for use wherever relevant, e.g., Qt
End • .