cosas qt6

Upload: grissgs

Post on 02-Mar-2018

225 views

Category:

Documents


0 download

TRANSCRIPT

  • 7/26/2019 cosas qt6

    1/6

    double dy = prevSettings.spanY() / (height() - 2 * Margin); settings.minX = prevSettings.minX + dx * rect.left(); settings.maxX = prevSettings.minX + dx * rect.right(); settings.minY = prevSettings.maxY - dy * rect.bottom(); settings.maxY = prevSettings.maxY - dy * rect.top(); settings.adjust();

    zoomStack.resize(curZoom + 1); zoomStack.append(settings); zoomIn(); }}

    When the user releases the left mouse button, we erase the rubber band and restore the standard arrow cursor. If the rubber band isat least 4 x 4, we perform the zoom. If the rubber band is smaller than that, it's likely that the user clicked the widget by mistake orto give it focus, so we do nothing.

    The code to perform the zoom is a bit complicated. This is because we deal with widget coordinates and plotter coordinates at thesame time. Most of the work we perform here is to convert the rubberBandRectfrom widget coordinates to plottercoordinates. Once we have done the conversion, we call PlotSettings::adjust()to round the numbers and find asensible number of ticks for each axis. Figures 5.10and 5.11illustrate the process.

    Figure 5.10. Converting the rubber band from widget to plotter coordinates

    Figure 5.11. Adjusting plotter coordinates and zooming in on the rubber band

    Then we perform the zoom. The zoom is achieved by pushing the new PlotSettingsthat we have just calculated on top of thezoom stack and calling zoomIn()to do the job.

    Code View:void Plotter::keyPressEvent(QKeyEvent *event){ switch (event->key()) { case Qt::Key_Plus: zoomIn(); break; case Qt::Key_Minus: zoomOut(); break; case Qt::Key_Left: zoomStack[curZoom].scroll(-1, 0); refreshPixmap(); break;

    130

  • 7/26/2019 cosas qt6

    2/6

    case Qt::Key_Right: zoomStack[curZoom].scroll(+1, 0); refreshPixmap(); break; case Qt::Key_Down: zoomStack[curZoom].scroll(0, -1); refreshPixmap(); break; case Qt::Key_Up: zoomStack[curZoom].scroll(0, +1); refreshPixmap(); break; default: QWidget::keyPressEvent(event);

    }}

    When the user presses a key and the Plotterwidget has focus, the keyPressEvent()function is called. We reimplement ithere to respond to six keys: +, -, Up, Down, Left, and Right. If the user pressed a key that we are not handling, we call the baseclass implementation. For simplicity, we ignore the Shift, Ctrl, and Alt modifier keys, which are available throughQKeyEvent::modifiers().

    void Plotter::wheelEvent(QWheelEvent *event){ int numDegrees = event->delta() / 8; int numTicks = numDegrees / 15;

    if (event->orientation() == Qt::Horizontal) {

    zoomStack[curZoom].scroll(numTicks, 0); } else { zoomStack[curZoom].scroll(0, numTicks); } refreshPixmap();}

    Wheel events occur when a mouse wheel is turned. Most mice provide only a vertical wheel, but some also have a horizontalwheel. Qt supports both kinds of wheel. Wheel events go to the widget that has the focus. The delta()function returns thedistance the wheel was rotated in eighths of a degree. Mice typically work in steps of 15 degrees. Here, we scroll by the requestednumber of ticks by modifying the topmost item on the zoom stack and update the display using refreshPixmap().

    The most common use of the mouse wheel is to scroll a scroll bar. When we use QScrollArea(covered in Chapter 6) toprovide scroll bars, QScrollAreahandles the mouse wheel events automatically, so we don't need to reimplementwheelEvent()ourselves.

    This finishes the implementation of the event handlers. Now let's review the private functions.

    void Plotter::updateRubberBandRegion(){ QRect rect = rubberBandRect.normalized(); update(rect.left(), rect.top(), rect.width(), 1); update(rect.left(), rect.top(), 1, rect.height()); update(rect.left(), rect.bottom(), rect.width(), 1); update(rect.right(), rect.top(), 1, rect.height());}

    The updateRubberBand()function is called from mousePressEvent(), mouseMoveEvent(), andmouseReleaseEvent()to erase or redraw the rubber band. It consists of four calls to update()that schedule a paint eventfor the four small rectangular areas that are covered by the rubber band (two vertical and two horizontal lines).

    void Plotter::refreshPixmap(){ pixmap = QPixmap(size()); pixmap.fill(this, 0, 0);

    QPainter painter(&pixmap); painter.initFrom(this); drawGrid(&painter); drawCurves(&painter); update();}

    The refreshPixmap()function redraws the plot onto the off-screen pixmap and updates the display. We resize the pixmap tohave the same size as the widget and fill it with the widget's erase color. This color is the "dark" component of the palette, because

    131

  • 7/26/2019 cosas qt6

    3/6

    of the call to setBackgroundRole()in the Plotterconstructor. If the background is a non-solid brush,QPixmap::fill()needs to know the offset in the widget where the pixmap will end up to align the brush pattern correctly.Here, the pixmap corresponds to the entire widget, so we specify position (0, 0).

    Then we create a QPainterto draw on the pixmap. The initFrom()call sets the painter's pen, background, and font to thesame ones as the Plotterwidget. Next, we call drawGrid()and drawCurves()to perform the drawing. At the end, wecall update()to schedule a paint event for the whole widget. The pixmap is copied to the widget in the paintEvent()function (p. 128).

    Code View:void Plotter::drawGrid(QPainter *painter){

    QRect rect(Margin, Margin, width() - 2 * Margin, height() - 2 * Margin); if (!rect.isValid()) return;

    PlotSettings settings = zoomStack[curZoom]; QPen quiteDark = palette().dark().color().light(); QPen light = palette().light().color();

    for (int i = 0; i setPen(quiteDark); painter->drawLine(x, rect.top(), x, rect.bottom()); painter->setPen(light); painter->drawLine(x, rect.bottom(), x, rect.bottom() + 5); painter->drawText(x - 50, rect.bottom() + 5, 100, 20, Qt::AlignHCenter | Qt::AlignTop,

    QString::number(label)); } for (int j = 0; j setPen(quiteDark); painter->drawLine(rect.left(), y, rect.right(), y); painter->setPen(light); painter->drawLine(rect.left() - 5, y, rect.left(), y); painter->drawText(rect.left() - Margin, y - 10, Margin - 5, 20, Qt::AlignRight | Qt::AlignVCenter, QString::number(label)); } painter->drawRect(rect.adjusted(0, 0, -1, -1));}

    The drawGrid()function draws the grid behind the curves and the axes. The area on which we draw the grid is specified byrect. If the widget isn't large enough to accommodate the graph, we return immediately.

    The first forloop draws the grid's vertical lines and the ticks along the x-axis. The second forloop draws the grid's horizontallines and the ticks along the y-axis. At the end, we draw a rectangle along the margins. The drawText()function is used todraw the numbers corresponding to the tick marks on both axes.

    The calls to drawText()have the following syntax:

    painter->drawText(x, y, width, height, alignment, text);

    where (x, y, width, height) define a rectangle, alignmentthe position of the text within that rectangle, and textthe textto draw. In this example, we have calculated the rectangle in which to draw the text manually; a more adaptable alternative wouldinvolve calculating the text's bounding rectangle using QFontMetrics.

    Code View:void Plotter::drawCurves(QPainter *painter){ static const QColor colorForIds[6] = { Qt::red, Qt::green, Qt::blue, Qt::cyan, Qt::magenta, Qt::yellow }; PlotSettings settings = zoomStack[curZoom]; QRect rect(Margin, Margin, width() - 2 * Margin, height() - 2 * Margin); if (!rect.isValid()) return;

    painter->setClipRect(rect.adjusted(+1, +1, -1, -1));

    132

  • 7/26/2019 cosas qt6

    4/6

    QMapIterator i(curveMap); while (i.hasNext()) { i.next();

    int id = i.key(); QVector data = i.value(); QPolygonF polyline(data.count()); for (int j = 0; j < data.count(); ++j) { double dx = data[j].x() - settings.minX; double dy = data[j].y() - settings.minY; double x = rect.left() + (dx * (rect.width() - 1) / settings.spanX()); double y = rect.bottom() - (dy * (rect.height() - 1)

    / settings.spanY()); polyline[j] = QPointF(x, y); } painter->setPen(colorForIds[uint(id) % 6]); painter->drawPolyline(polyline); }}

    The drawCurves()function draws the curves on top of the grid. We start by calling setClipRect()to set theQPainter's clip region to the rectangle that contains the curves (excluding the margins and the frame around the graph).QPainterwill then ignore drawing operations on pixels outside the area.

    Next, we iterate over all the curves using a Java-style iterator, and for each curve, we iterate over its constituent QPointFs. Wecall the iterator's key()function to retrieve the curve's ID, and its value()function to retrieve the corresponding curve data as

    a QVector. The inner forloop converts each QPointFfrom plotter coordinates to widget coordinates and storesthem in the polylinevariable.

    Once we have converted all the points of a curve to widget coordinates, we set the pen color for the curve (using one of a set ofpredefined colors) and call drawPolyline()to draw a line that goes through all the curve's points.

    This is the complete Plotterclass. All that remains are a few functions in PlotSettings.

    PlotSettings::PlotSettings(){ minX = 0.0; maxX = 10.0; numXTicks = 5;

    minY = 0.0; maxY = 10.0; numYTicks = 5;}

    The PlotSettingsconstructor initializes both axes to the range 0 to 10 with five tick marks.

    void PlotSettings::scroll(int dx, int dy){ double stepX = spanX() / numXTicks; minX += dx * stepX; maxX += dx * stepX;

    double stepY = spanY() / numYTicks; minY += dy * stepY; maxY += dy * stepY;}

    The scroll()function increments (or decrements) minX, maxX, minY, and maxYby the interval between two ticks times agiven number. This function is used to implement scrolling in Plotter::keyPressEvent().

    void PlotSettings::adjust(){ adjustAxis(minX, maxX, numXTicks); adjustAxis(minY, maxY, numYTicks);}

    The adjust()function is called from mouseReleaseEvent()to round the minX, maxX, minY, and maxYvalues to "nice"values and to determine the number of ticks appropriate for each axis. The private function adjustAxis()does its work oneaxis at a time.

    133

  • 7/26/2019 cosas qt6

    5/6

    void PlotSettings::adjustAxis(double &min, double &max, int &numTicks){ const int MinTicks = 4; double grossStep = (max - min) / MinTicks; double step = std::pow(10.0, std::floor(std::log10(grossStep)));

    if (5 * step < grossStep) { step *= 5; } else if (2 * step < grossStep) { step *= 2; }

    numTicks = int(std::ceil(max / step) - std::floor(min / step)); if (numTicks < MinTicks)

    numTicks = MinTicks; min = std::floor(min / step) * step; max = std::ceil(max / step) * step;}

    The adjustAxis()function converts its minand maxparameters into "nice" numbers and sets its numTicksparameter tothe number of ticks it calculates to be appropriate for the given [min, max] range. Because adjustAxis()needs to modify theactual variables (minX, maxX, numXTicks, etc.) and not just copies, its parameters are non-const references.

    Most of the code in adjustAxis()simply attempts to determine an appropriate value for the interval between two ticks (the"step"). To obtain nice numbers along the axis, we must select the step with care. For example, a step value of 3.8 would lead toan axis with multiples of 3.8, which is difficult for people to relate to. For axes labeled in decimal notation, "nice" step values arenumbers of the form 10n, 210n, or 510n.

    We start by computing the "gross step", a kind of maximum for the step value. Then we find the corresponding number of the

    form 10n

    that is smaller than or equal to the gross step. We do this by taking the decimal logarithm of the gross step, rounding thatvalue down to a whole number, then raising 10 to the power of this rounded number. For example, if the gross step is 236, wecompute log 236 = 2.37291...; then we round it down to 2 and obtain 102= 100 as the candidate step value of the form 10n.

    Once we have the first candidate step value, we can use it to calculate the other two candidates: 210nand 510n. For thepreceding example, the other two candidates are 200 and 500. The 500 candidate is larger than the gross step, so we can't use it.But 200 is smaller than 236, so we use 200 for the step size in this example.

    It's fairly easy to calculate numTicks, min, and maxfrom the step value. The new minvalue is obtained by rounding theoriginal mindown to the nearest multiple of the step, and the new maxvalue is obtained by rounding up to the nearest multiple ofthe step. The new numTicksis the number of intervals between the rounded minand maxvalues. For example, if minis 240and maxis 1184 upon entering the function, the new range becomes [200, 1200], with five tick marks.

    This algorithm will give suboptimal results in some cases. A more sophisticated algorithm is described in Paul S. Heckbert'sarticle "Nice Numbers for Graph Labels", published in Graphics Gems (Morgan Kaufmann, 1990).

    This chapter brings us to the end of Part Iof the book. It explained how to customize an existing Qt widget and how to build awidget from the ground up using QWidgetas the base class. We already saw how to lay out child widgets using layout managersin Chapter 2, and we will explore the theme further in Chapter 6.

    At this point, we know enough to write complete GUI applications using Qt. In Parts IIand III, we will explore Qt in greater depthso that we can make full use of Qt's power.

    134

  • 7/26/2019 cosas qt6

    6/6

    Part II: Intermediate Qt

    6. Layout Management

    Laying Out Widgets on a FormStacked LayoutsSplittersScrolling AreasDock Windows and ToolbarsMultiple Document Interface

    Every widget that is placed on a form must be given an appropriate size and position. Qt provides several classes that lay outwidgets on a form: QHBoxLayout, QVBoxLayout, QGridLayout, and QStackedLayout. These classes are soconvenient and easy to use that almost every Qt developer uses them, either directly in source code or through Qt Designer.

    Another reason to use Qt's layout classes is that they ensure that forms adapt automatically to different fonts, languages, andplatforms. If the user changes the system's font settings, the application's forms will respond immediately, resizing themselves ifnecessary. And if you translate the application's user interface to other languages, the layout classes take into consideration thewidgets' translated contents to avoid text truncation.

    Other classes that perform layout management include QSplitter, QScrollArea, QMainWindow, and QMdiArea. All ofthese classes provide a flexible layout that the user can manipulate. For example, QSplitterprovides a splitter bar that the usercan drag to resize widgets, and QMdiAreaoffers support for MDI (multiple document interface), a means of showing manydocuments simultaneously within an application's main window. Because they are often used as alternatives to the layout classesproper, we cover them in this chapter.

    Laying Out Widgets on a Form

    There are three basic ways of managing the layout of child widgets on a form: absolute positioning, manual layout, and layout

    managers. We will look at each of these approaches in turn, using the Find File dialog shown in Figure 6.1as our example.

    Figure 6.1. The Find File dialog

    135