Porting to Qt 4.2's Graphics View

Материал из Wiki.crossplatform.ru

Перейти к: навигация, поиск
Image:qt-logo_new.png Image:qq-title-article.png
Qt Quarterly | Выпуск 21 | Документация

by Andreas Aardal Hanssen

Graphics View is a new canvas framework for Qt 4. SupersedingQCanvas, Graphics View has lots of new features, including itemgrouping, item interaction through events, and floating-pointcoordinates. Still, Graphics View's design is similar to QCanvas's,ensuring that the transition to the new framework is as painless aspossible. In this article, we will port the "canvas" exampleincluded with Qt 3 to use Graphics View, demonstrating the portingprocess step by step.



Qt 3's canvas example consists of about 1000 lines of code. Wewill start with the original sources from the Qt 3 source package andport it one step at a time. At the end of the process, we will havetwo applications that behave exactly the same, but each using adifferent set of classes and a different version of Qt.

[править] Making the Example Compile with Qt 4

We start by making a copy of the examples/canvas directory fromQt 3. Then, using a command-line interpreter set up for Qt 4.2, werun the qt3to4 porting tool on the example's canvas.pro file.This starts an automated in-place modification of the example, takingcare of the most tedious parts of porting an existing Qt 3application to Qt 4.

$ qt3to4 canvas.pro
Using rules file: qt-4.2/tools/porting/src/q3porting.xml
Convert file tmp/canvas/canvas.cpp? (Y)es, (N)o, (A)ll A
Wrote to file: tmp/canvas/canvas.cpp
Wrote to file: tmp/canvas/main.cpp
Wrote to file: tmp/canvas/canvas.h
Wrote to file: tmp/canvas/canvas.pro
Writing log to portinglog.txt

The qt3to4 tool starts by asking whether or not it should convertcanvas.cpp. Simply type A to tell it to port all files. Thetool generates a log file called portinglog.txt that shows allmodifications that were made.

The resulting program uses Q3Canvas and friends from theQt3Support library. We must make a few manual modificationsto make the example compile with Qt4.

In this article's code snippets, we follow the diff program'sconvention and use a leading plus sign (+) to indicate that a codeline is being added to the program. Similarly, a leading minus (-)identifies a line that must be removed.

 #include <qapplication.h>
 #include <qimage.h>
+#include <QDesktopWidget>

In main.cpp, we must include <QDesktopWidget>, as QDesktopWidget isreferenced indirectly from QApplication::desktop() at the end ofthe file. In Qt 3, this was not necessary becausedesktop() returned a plain QWidget pointer.

Two modifications are required in canvas.cpp:

-  pixmap.convertFromImage(image, OrderedAlphaDither);
+  pixmap.convertFromImage(image, Qt::OrderedAlphaDither);
-  Main *m = new Main(canvas, 0, 0, WDestructiveClose);
+  Main *m = new Main(canvas, 0, 0, Qt::WDestructiveClose);

In Qt 3, Qt was a class. Since most Qt classes derived directlyor indirectly from Qt, we could usually omit the Qt:: prefix.

In Qt 4, Qt is a namespace, so we must either prefix every Qtmember with the namespace name or add a using namespace Qt;directive at the beginning of our source files. The porting toolcatches most of these cases but not all of them.

[править] Porting from QCanvas to Graphics View

At this point, the canvas example is a stand-alone Qt 4application that compiles and runs correctly. The only issue is thatwe rely on the Qt3Support library&emdash;more precisely onQ3Canvas. We have three options at our disposal:

  • We can call it a day.

    After all, linking against Qt3Support is not a crime.

  • We can replace Q3Canvas with QtCanvas.

    The QtCanvas class is provided as a Qt Solution. Its purposeis to make it possible to use the old canvas frameworkavailable without linking in the entire Qt3Support library.

  • We can port the application to Graphics View.

    Porting to Graphics View means that we have a more solid basis forfurther development. For example, if at a later point we want to additem grouping to the canvas example, we can build upon GraphicsView's grouping support.

In the rest of this article we will see how to carry out the thirdoption. To help us in the process, we can consult thePorting toGraphics View page of the Qt online documentation.

Using the porting tables found there as a guide, we start byreplacing Q3Canvas and Q3CanvasView with QGraphicsSceneand QGraphicsView in all source files.We must also change the includes:

-#include <q3canvas.h>
+#include <QGraphicsItem>
+#include <QGraphicsScene>
+#include <QGraphicsView>

We also replace Q3CanvasItems with their closest equivalents inGraphics View:

Old Class New Class
Q3CanvasRectangle QGraphicsRectItem
Q3CanvasSpline QGraphicsPathItem
Q3CanvasSprite QGraphicsPixmapItem
Q3CanvasText QGraphicsSimpleTextItem

Unsurprisingly, if we try to compile the example now, we get manyerrors and warnings.

[править] Compiling the Example Again

As a general rule when porting, it's a good idea to quickly bring theproject up to a state where it compiles again, even if it doesn'twork properly yet. Following this philosophy, the next step is tocomment out any block of code that references functions that don'texist in the new API. This includes incompatible constructors andsyntax errors. If a block of code accesses many functions that don'texist in the new API, we simply comment out the entire block.

For this example, commenting out all erroneous code is a five minuteoperation that quickly brings us to a point where the applicationcompiles again. This approach is very useful, as it allows us toport one component at a time, testing it as we go. Once the examplecompiles, we can run it and see what it looks like:


The result isn't very exciting yet: All we have is a dysfunctionalmenu system and a gray canvas area.

[править] Fixing the Behavior

Let's start by fixing QGraphicsScene'sconstructor in main.cpp, so that the size of the canvas is back towhat it was in the original example:

-  QGraphicsScene canvas; // (800, 600);
+  QGraphicsScene canvas(0, 0, 800, 600);

Unlike Q3Canvas, QGraphicsScenelets us specify an arbitrary top-left corner for the scene. To obtain thesame behavior as before, we must explicitly pass (0, 0) to the constructor.

FigureEditor::FigureEditor(QGraphicsScene &amp;c,
   QWidget *parent, const char *name, Qt::WFlags f)
-     // : QGraphicsView(&amp;c, parent, name, f)
+     : QGraphicsView(&amp;c, parent)
+  setObjectName(name);
+  setWindowFlags(name);

In the FigureEditor constructor, we uncomment the base classinitialization, we remove the name and flags arguments from QGraphicsView's constructor, and insteadcall setObjectName() and setWindowFlags() explicitly.

If we compile and run the example now, it will look exactly like theoriginal example but without any items. This is a good indicationthat we are on the right track.

The next step is straightforward and quite fun to do: porting theitem classes one at a time. For most cases, it's just a matter ofdealing with code that we commented out ourselves. We will show howto port ImageItem, the base class for the butterfly and logoitems.

class ImageItem : public QGraphicsRectItem
	ImageItem(QImage img, QGraphicsScene *canvas);
-	int rtti() const { return imageRTTI; }
+	int type() const { return imageRTTI; }
bool hit(const QPoint &amp;) const;
-	void drawShape(QPainter &amp;);
+	void paint(QPainter *, const QStyleOptionGraphicsItem *,
+			   QWidget *);
	QImage image;
	QPixmap pixmap;

The rtti() virtual function in Q3CanvasItem is calledtype() in QGraphicsItem, so wesimply rename it in the ImageItem subclass. Instead ofdrawShape(), we reimplement paint(), which plays the samerole but has a different signature.

ImageItem::ImageItem(QImage img, QGraphicsScene *canvas)
-   // : QGraphicsRectItem(canvas), image(img)
+   : QGraphicsRectItem(0, canvas), image(img)
-	// setSize(image.width(), image.height());
+	setRect(0, 0, image.width(), image.height());

Since Graphics View items can be created as children of other items,we must pass a null pointer as the parent to the QGraphicsRectItem constructor to createa top-level item. Also, there is no setSize() function in QGraphicsRectItem; instead,we call setRect() and pass (0, 0) as the top-left corner.

-void ImageItem::drawShape(QPainter &amp;p)
+void ImageItem::paint(QPainter *p,
+                      const QStyleOptionGraphicsItem *,
+                      QWidget *)
#if defined(Q_WS_QWS)
-	p.drawImage(int(x()), int(y()), image, 0, 0, -1, -1,
-              Qt::OrderedAlphaDither);
+	p->drawImage(0, 0, image, 0, 0, -1, -1,
+               Qt::OrderedAlphaDither);
-	p.drawPixmap(int(x()), int(y()), pixmap);
+	p->drawPixmap(0, 0, pixmap);

We turn the drawShape() implementation into a paint()implementation. The tricky part here is that with Graphics View, the QPainter is set up so that we must draw inlocal coordinates. As a consequence, we call drawPixmap() with(0, 0) as the top-left corner.

If we compile and run the application now, we obtain logos andbutterflies stacked on top of each other in the windows's top-leftcorner:


This shouldn't come as a surprise, considering that we've commentedout several code sections, including the item placement code inMain::addButterfly(), Main::addLogo(), etc. Let's start byfixing addButterfly():

   QAbstractGraphicsShapeItem *i =
          new ImageItem(butterflyimg[rand() % 4], &amp;canvas);
 -	// i->move(rand() % (canvas.width()
 -	//                   - butterflyimg->width()),
 -	//         rand() % (canvas.height()
 -	//                   - butterflyimg->height()));
 -	// i->setZ(rand() % 256 + 250);
 -  i->show();
 +	i->setPos(rand() % int(canvas.width()
 +                         - butterflyimg->width()),
 +            rand() % int(canvas.height()
 +                         - butterflyimg->height()));
 +  i->setZValue(rand() % 256 + 250);

QGraphicsItem::setPos() does the sameas what move() did before, and setZ() is now calledsetZValue(). We also need a couple of int casts because QGraphicsScene's width() andheight() functions now return floating-point values and %is only defined for integers.

Notice that we don't need to callshow() anymore, because Graphics View items are visible bydefault. Similar changes are required in addLogo():

QAbstractGraphicsShapeItem *i =
      new ImageItem(logoimg[rand() % 4], &amp;canvas);
-	// i->move(rand() % (canvas.width()
-	//                   - logoimg->width()),
-	//         rand() % (canvas.height()
-	//                   - logoimg->width()));
-	// i->setZ(rand() % 256 + 256);
-	i->show();
+	i->setPos(rand() % int(canvas.width()
+                         - logoimg->width()),
+            rand() % int(canvas.height()
+                         - logoimg->width()));
+	i->setZValue(rand() % 256 + 256);

If we run the example now, all butterflies and logos are placedat random positions as we would expect. Our example is starting tolook more and more like the original. Next function: addSpline().

For Q3CanvasSpline, the natural transition is to port to QGraphicsPathItem,which is based on QPainterPath. QPainterPath supports curves, but itsinterface is different from that of Q3CanvasSpline, so some portingis required:

-	// QGraphicsPathItem *i =
-	//       new QGraphicsPathItem(&amp;canvas);
+	QGraphicsPathItem *i =
+         new QGraphicsPathItem(0, &amp;canvas);
Q3PointArray pa(12);
pa[0] = QPoint(0, 0);
pa[1] = QPoint(size / 2, 0);
pa[11] = QPoint(-size / 2, 0);
-	i->setControlPoints(pa);
+	QPainterPath path;
+	path.moveTo(pa[0]);
+	for (int i = 1; i < pa.size(); i += 3)
+		path.cubicTo(pa[i], pa[(i + 1) % pa.size()],
+                 pa[(i + 2) % pa.size()]);
+	i->setPath(path);

Because QGraphicsPathItem is a genericvector path item (not a spline item), we must build the curve shape manuallyusing QPainterPath's moveTo() andcubicTo() functions instead of callingQ3CanvasSpline::setControlPoints().

Porting the other items is straightforward, so let's skip ahead a bitand add some navigation support to the view instead. InMain::rotateClockwise(), we can replace three lines of code withone to get rotation support.

void Main::rotateClockwise()
-	// QMatrix m = editor->worldMatrix();
-	// m.rotate(22.5);
-	// editor->setWorldMatrix(m);
+	editor->rotate(22.5);

We can do similar changes to the other functions accessible throughthe View menu (zoomIn(), zoomOut(), etc.).In Main::paint(), we must reintroduce printing support:

void Main::print()
	if (!printer) printer = new QPrinter;
	if (printer->setup(this)) {
		QPainter pp(printer);
-		// canvas.drawArea(QRect(0, 0, canvas.width(),
-		//                 canvas.height()),
-		//                 &amp;pp, FALSE);
+		canvas.render(&amp;pp);

The only change here is due to the differences in the signatures ofQ3Canvas::drawArea() and QGraphicsScene::render(). It turns outwe can leave out most arguments to drawArea() because suitable defaultvalues are provided with the new API.

That's it! We now have a fully functional canvas example based onthe new Graphics View framework.

[править] Conclusion

As you can see from this article, moving from Q3Canvas toGraphics View is a fairly straightforward operation, once we'veunderstood the main differences between the two APIs.

There are a few pitfalls, though. Some Q3Canvas features thatwere targeted at 2D game programming, such as tiles and sprites, arenot directly supported in Graphics View; the "Porting to GraphicsView" page explains how to implement these features without too muchhassle. In addition, existing code that implicitly relied on integerprecision might still compile but behave differently with GraphicsView.

The complete source code for the ported canvas example isdistributed in Qt 4.2's examples/graphicsview/portedcanvasdirectory. Qt 4.2 also includes a portedasteroids example thatcorresponds to the Qt 3 asteroids example, which also used QCanvas.

To conclude, I would like to wish you good luck with porting toGraphics View! If you run into trouble, let us know so we can helpand possibly improve the porting documentation.