From 11dead9a3a751ea21c3c0ec5bbee3ec0e17ea0b8 Mon Sep 17 00:00:00 2001 From: Tim Sutton Date: Wed, 19 Sep 2012 17:15:39 +0200 Subject: [PATCH] In progress support for retrieval of html composer and items by id from map composition. Also refactoring python composer tests. --- python/core/qgscomposermultiframe.sip | 6 +- src/core/composer/qgscomposermultiframe.cpp | 2 +- src/core/composer/qgscomposermultiframe.h | 10 +- src/core/composer/qgscomposition.cpp | 44 +++++- src/core/composer/qgscomposition.h | 18 ++- tests/src/python/qgscompositionchecker.py | 54 ++++--- tests/src/python/test_qgscomposerhtml.py | 153 +++++++++++++------- 7 files changed, 206 insertions(+), 81 deletions(-) diff --git a/python/core/qgscomposermultiframe.sip b/python/core/qgscomposermultiframe.sip index ecea5975f8c..b3a7f6c513b 100644 --- a/python/core/qgscomposermultiframe.sip +++ b/python/core/qgscomposermultiframe.sip @@ -42,6 +42,10 @@ public: /**Removes and deletes all frames from mComposition*/ void deleteFrames(); - int nFrames() const; + int nFrames() const /Deprecated/; + /** Return the number of frames associated with this multiframeset. + @note added in 2.0, replaces nFrames + **/ + int frameCount() const; QgsComposerFrame* frame( int i ); }; diff --git a/src/core/composer/qgscomposermultiframe.cpp b/src/core/composer/qgscomposermultiframe.cpp index 64bc84b7395..a9469908b98 100644 --- a/src/core/composer/qgscomposermultiframe.cpp +++ b/src/core/composer/qgscomposermultiframe.cpp @@ -238,7 +238,7 @@ void QgsComposerMultiFrame::deleteFrames() mResizeMode = bkResizeMode; } -QgsComposerFrame* QgsComposerMultiFrame::frame( int i ) +QgsComposerFrame* QgsComposerMultiFrame::frame( int i ) const { if ( i >= mFrameItems.size() ) { diff --git a/src/core/composer/qgscomposermultiframe.h b/src/core/composer/qgscomposermultiframe.h index 50ff4a2db30..cb82e4c039f 100644 --- a/src/core/composer/qgscomposermultiframe.h +++ b/src/core/composer/qgscomposermultiframe.h @@ -69,8 +69,14 @@ class CORE_EXPORT QgsComposerMultiFrame: public QObject /**Removes and deletes all frames from mComposition*/ void deleteFrames(); - int nFrames() const { return mFrameItems.size(); } - QgsComposerFrame* frame( int i ); + + /** Deprecated in 2.0 use frameCount rather. **/ + Q_DECL_DEPRECATED int nFrames() const { return mFrameItems.size(); } + /** Return the number of frames associated with this multiframeset. + @note added in 2.0, replaces nFrames + **/ + int frameCount() const { return mFrameItems.size(); } + QgsComposerFrame* frame( int i ) const; protected: QgsComposition* mComposition; diff --git a/src/core/composer/qgscomposition.cpp b/src/core/composer/qgscomposition.cpp index a589fb5b16a..ac85755c62c 100644 --- a/src/core/composer/qgscomposition.cpp +++ b/src/core/composer/qgscomposition.cpp @@ -185,8 +185,6 @@ QList QgsComposition::composerMapItems() const const QgsComposerMap* QgsComposition::getComposerMapById( int id ) const { - QList resultList; - QList itemList = items(); QList::iterator itemIt = itemList.begin(); for ( ; itemIt != itemList.end(); ++itemIt ) @@ -200,7 +198,47 @@ const QgsComposerMap* QgsComposition::getComposerMapById( int id ) const } } } + return 0; +} +const QgsComposerHtml* QgsComposition::getComposerHtmlByItem( QgsComposerItem *item ) const +{ + QList itemList = items(); + QList::iterator itemIt = itemList.begin(); + for ( ; itemIt != itemList.end(); ++itemIt ) + { + const QgsComposerHtml* composerHtml = dynamic_cast( *itemIt ); + if ( composerHtml ) + { + //Now cycle through the items associated with this html composer + //and return the composer if the item matches any of them + for ( int i=0; iframeCount(); i++ ) + { + if ( composerHtml->frame(i)->id() == item->id() ) + { + return composerHtml; + } + } + } + } + return 0; +} + +const QgsComposerItem* QgsComposition::getComposerItemById( QString theId ) const +{ + QList itemList = items(); + QList::iterator itemIt = itemList.begin(); + for ( ; itemIt != itemList.end(); ++itemIt ) + { + const QgsComposerItem* mypItem = dynamic_cast( *itemIt ); + if ( mypItem ) + { + if ( mypItem->id() == theId ) + { + return mypItem; + } + } + } return 0; } @@ -1282,7 +1320,7 @@ void QgsComposition::removeComposerItem( QgsComposerItem* item, bool createComma //check if there are frames left. If not, remove the multi frame if ( frameItem && multiFrame ) { - if ( multiFrame->nFrames() < 1 ) + if ( multiFrame->frameCount() < 1 ) { removeMultiFrame( multiFrame ); if ( createCommand ) diff --git a/src/core/composer/qgscomposition.h b/src/core/composer/qgscomposition.h index 7d8f6fec10e..ff341b01709 100644 --- a/src/core/composer/qgscomposition.h +++ b/src/core/composer/qgscomposition.h @@ -124,9 +124,25 @@ class CORE_EXPORT QgsComposition: public QGraphicsScene template void composerItems( QList& itemList ); /**Returns the composer map with specified id - @return id or 0 pointer if the composer map item does not exist*/ + @return QgsComposerMap or 0 pointer if the composer map item does not exist*/ const QgsComposerMap* getComposerMapById( int id ) const; + /*Returns the composer html with specified id (a string as named in the + composer user interface item properties). + @note Added in QGIS 2.0 + @param id - A QString representing the id of the item. + @return QgsComposerHtml pointer or 0 pointer if no such item exists. + */ + const QgsComposerHtml* getComposerHtmlByItem( QgsComposerItem *item ) const; + + /**Returns a composer item given its text identifier. + @note added in 2.0 + @param theId - A QString representing the identifier of the item to + retrieve. + @return QgsComposerItem pointer or 0 pointer if no such item exists. + **/ + const QgsComposerItem* getComposerItemById( QString theId ) const; + int printResolution() const {return mPrintResolution;} void setPrintResolution( int dpi ) {mPrintResolution = dpi;} diff --git a/tests/src/python/qgscompositionchecker.py b/tests/src/python/qgscompositionchecker.py index 98ddc030218..8fb302836fc 100644 --- a/tests/src/python/qgscompositionchecker.py +++ b/tests/src/python/qgscompositionchecker.py @@ -14,7 +14,7 @@ qgscompositionchecker.py - check rendering of Qgscomposition against an expected * * ***************************************************************************/ ''' -from PyQt4.QtCore import * +from PyQt4.QtCore import * from PyQt4.QtGui import * from qgis.core import * @@ -22,17 +22,18 @@ class QgsCompositionChecker: def testComposition(self, mTestName, mComposition, mExpectedImageFile, page=0 ): if ( mComposition == None): - return false + myMessage = "Composition not valid" + return False, myMessage #load expected image expectedImage = QImage( mExpectedImageFile ) - + #get width/height, create image and render the composition to it width = expectedImage.width(); height = expectedImage.height(); outputImage = QImage( QSize( width, height ), QImage.Format_ARGB32 ) - + mComposition.setPlotStyle( QgsComposition.Print ) outputImage.setDotsPerMeterX( expectedImage.dotsPerMeterX() ) outputImage.setDotsPerMeterY( expectedImage.dotsPerMeterX() ) @@ -40,29 +41,47 @@ class QgsCompositionChecker: p = QPainter( outputImage ) mComposition.renderPage( p, page ) p.end() - + renderedFilePath = QDir.tempPath() + QDir.separator() + QFileInfo( mExpectedImageFile ).baseName() + "_rendered_python.png" outputImage.save( renderedFilePath, "PNG" ) - + diffFilePath = QDir.tempPath() + QDir.separator() + QFileInfo( mExpectedImageFile ).baseName() + "_diff_python.png" testResult = self.compareImages( expectedImage, outputImage, diffFilePath ) - - myDashMessage = "" + renderedFilePath + "" + "\n" + "" + mExpectedImageFile + "" + "\n" + "" + diffFilePath + "" + + myDashMessage = (('' + '%s' + '' + '%s\n' + '' + '%s') % + (mTestName, renderedFilePath, mTestName, + mExpectedImageFile, mTestName, diffFilePath ) + ) qDebug( myDashMessage ) - return testResult + if not testResult: + myMessage = ('Expected: %s\nGot: %s\nDifference: %s\n' % + (mExpectedImageFile, renderedFilePath, diffFilePath)) + else: + myMessage = 'Control and test images matched.' + return testResult, myMessage def compareImages( self, imgExpected, imgRendered, differenceImagePath ): - if ( imgExpected.width() != imgRendered.width() or imgExpected.height() != imgRendered.height() ): - return false - + if ( imgExpected.width() != imgRendered.width() + or imgExpected.height() != imgRendered.height() ): + return False + imageWidth = imgExpected.width() imageHeight = imgExpected.height() mismatchCount = 0 - - differenceImage = QImage( imageWidth, imageHeight, QImage.Format_ARGB32_Premultiplied ) + + differenceImage = QImage( + imageWidth, imageHeight, QImage.Format_ARGB32_Premultiplied ) differenceImage.fill( qRgb( 152, 219, 249 ) ) - + pixel1 = QColor().rgb() pixel2 = QColor().rgb() for i in range( imageHeight ): @@ -72,13 +91,12 @@ class QgsCompositionChecker: if ( pixel1 != pixel2 ): mismatchCount = mismatchCount + 1 differenceImage.setPixel( j, i, qRgb( 255, 0, 0 ) ) - + if not differenceImagePath.isEmpty(): differenceImage.save( differenceImagePath, "PNG" ) - + #allow pixel deviation of 1 percent pixelCount = imageWidth * imageHeight; # print "MismatchCount: "+str(mismatchCount) # print "PixelCount: "+str(pixelCount) return (float(mismatchCount) / float(pixelCount) ) < 0.01 - diff --git a/tests/src/python/test_qgscomposerhtml.py b/tests/src/python/test_qgscomposerhtml.py index 1fc6b682d25..74a88c8ec4b 100644 --- a/tests/src/python/test_qgscomposerhtml.py +++ b/tests/src/python/test_qgscomposerhtml.py @@ -1,9 +1,11 @@ # -*- coding: utf-8 -*- ''' -test_qgscomposerhtml.py +test_qgscomposerhtml.py -------------------------------------- Date : August 2012 - Copyright : (C) 2012 by Dr. Horst Düster / Dr. Marco Hugentobler + Copyright : (C) 2012 by Dr. Horst Düster / + Dr. Marco Hugentobler + Tim Sutton email : marco@sourcepole.ch *************************************************************************** * * @@ -13,66 +15,107 @@ test_qgscomposerhtml.py * (at your option) any later version. * * * ***************************************************************************/ -''' +''' import unittest -from utilities import * -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * +import os +from utilities import unitTestDataPath, getQgisTestApp +from PyQt4.QtCore import QUrl, QString, qDebug +from qgis.core import (QgsComposition, + QgsComposerHtml, + QgsComposerFrame, + QgsComposerMultiFrame) + from qgscompositionchecker import QgsCompositionChecker QGISAPP, CANVAS, IFACE, PARENT = getQgisTestApp() +TEST_DATA_DIR = unitTestDataPath() class TestQgsComposerMap(unittest.TestCase): - - def testCase(self): - self.mComposition = QgsComposition( None ) - self.mComposition.setPaperSize( 297, 210 ) #A4 landscape - self.table() - self.tableMultiFrame() - - def table(self): - TEST_DATA_DIR = unitTestDataPath() - htmlItem = QgsComposerHtml( self.mComposition, False ) - htmlFrame = QgsComposerFrame( self.mComposition, htmlItem, 0, 0, 100, 200 ) - htmlFrame.setFrameEnabled( True ) - htmlItem.addFrame( htmlFrame ) - htmlItem.setUrl( QUrl( QString( "file:///%1" ).arg( QString( TEST_DATA_DIR ) + QDir.separator() + "html_table.html" ) ) ); - checker = QgsCompositionChecker( ) - result = checker.testComposition( "Composer html table", self.mComposition, QString( TEST_DATA_DIR + QDir.separator().toAscii() + "control_images" + QDir.separator().toAscii() + "expected_composerhtml" + QDir.separator().toAscii() + "composerhtml_table.png" ) ) - self.mComposition.removeMultiFrame( htmlItem ) - del htmlItem - assert result == True - - def tableMultiFrame(self): - TEST_DATA_DIR = unitTestDataPath() - htmlItem = QgsComposerHtml( self.mComposition, False ) - htmlFrame = QgsComposerFrame( self.mComposition, htmlItem, 10, 10, 100, 50 ) - htmlItem.addFrame( htmlFrame ) - htmlItem.setResizeMode( QgsComposerMultiFrame.RepeatUntilFinished ) - - htmlItem.setUrl( QUrl( QString( "file:///%1" ).arg( QString( TEST_DATA_DIR ) + QDir.separator() + "html_table.html" ) ) ) - htmlItem.frame( 0 ).setFrameEnabled( True ) - + + def setUp(self): + """Run before each test.""" + self.mComposition = QgsComposition(None) + self.mComposition.setPaperSize(297, 210) #A4 landscape + self.htmlItem = QgsComposerHtml(self.mComposition, False) + + def tearDown(self): + """Run after each test.""" + print "Tear down" + if self.htmlItem: + self.mComposition.removeMultiFrame(self.htmlItem) + del self.htmlItem + + def controlImagePath(self, theImageName): + """Helper to get the path to a control image.""" + myPath = os.path.join(TEST_DATA_DIR, + "control_images", + "expected_composerhtml", + theImageName) + assert os.path.exists(myPath) + return myPath + + def htmlUrl(self): + """Helper to get the url of the html doc.""" + myPath = os.path.join(TEST_DATA_DIR, "html_table.html") + myUrl = QUrl(QString("file:///%1").arg(myPath)) + return myUrl + + def testTable(self): + """Test we can render a html table in a single frame.""" + htmlFrame = QgsComposerFrame(self.mComposition, + self.htmlItem, 0, 0, 100, 200) + htmlFrame.setFrameEnabled(True) + self.htmlItem.addFrame(htmlFrame) + self.htmlItem.setUrl(self.htmlUrl()) + checker = QgsCompositionChecker() + myResult, myMessage = checker.testComposition( + "Composer html table", + self.mComposition, + self.controlImagePath("composerhtml_table.png")) + qDebug(myMessage) + assert myResult, myMessage + + def testTableMultiFrame(self): + """Test we can render to multiframes.""" + htmlFrame = QgsComposerFrame(self.mComposition, self.htmlItem, + 10, 10, 100, 50) + self.htmlItem.addFrame(htmlFrame) + self.htmlItem.setResizeMode(QgsComposerMultiFrame.RepeatUntilFinished) + self.htmlItem.setUrl(self.htmlUrl()) + self.htmlItem.frame(0).setFrameEnabled(True) + result = True - #page 1 - checker1 = QgsCompositionChecker( ) - if not checker1.testComposition( "Composer html table", self.mComposition, QString( QString( TEST_DATA_DIR ) + QDir.separator() + "control_images" + QDir.separator() + "expected_composerhtml" + QDir.separator() + "composerhtml_table_multiframe1.png" ), 0 ): - result = False - - checker2 = QgsCompositionChecker( ) - if not checker2.testComposition( "Composer html table", self.mComposition, QString( QString( TEST_DATA_DIR ) + QDir.separator() + "control_images" + QDir.separator() + "expected_composerhtml" + QDir.separator() + "composerhtml_table_multiframe2.png" ) , 1 ): - result = False - - checker3 = QgsCompositionChecker( ) - if not checker3.testComposition( "Composer html table", self.mComposition, QString( QString( TEST_DATA_DIR ) + QDir.separator() + "control_images" + QDir.separator() + "expected_composerhtml" + QDir.separator() + "composerhtml_table_multiframe3.png" ), 2 ): - result = False - - self.mComposition.removeMultiFrame( htmlItem ) - del htmlItem - - assert result == True - + myPage = 0 + checker1 = QgsCompositionChecker() + myControlImage = self.controlImagePath( + "composerhtml_table_multiframe1.png") + print "Checking page 1" + myResult, myMessage = checker1.testComposition("Composer html table", + self.mComposition, + myControlImage, + myPage) + assert myResult, myMessage + + myPage = 1 + checker2 = QgsCompositionChecker() + myControlImage = self.controlImagePath( + "composerhtml_table_multiframe2.png") + myResult, myMessage = checker2.testComposition("Composer html table", + self.mComposition, + myControlImage, + myPage) + assert myResult, myMessage + + myPage = 2 + checker3 = QgsCompositionChecker() + myControlImage = self.controlImagePath( + "composerhtml_table_multiframe3.png") + myResult, myMessage = checker3.testComposition("Composer html table", + self.mComposition, + myControlImage, + myPage) + assert myResult, myMessage + if __name__ == '__main__': unittest.main()