Browse Source

ENH: Add sizeHints to ctkAxesWidget

STYLE: Use left/right/bottom/top variables for clarity

- instead of x0, x1, y0, y1

BUG: Fix bad zooming close to edges

ENH: ctkVTKMagnifyWidgetTest2: bullsEyeWidth = magnification + 2

STYLE: Comments

ENH: Fix bad zooming when size and magnification are not both either even or odd

STYLE: Stay within 80 characters per line

ENH: Test magnify widget with combinations of even/odd label size and magnification

STYLE: Comment

ENH: Prevent magnification tests from running in parallel

- because they all rely on and move the cursor

STYLE: Fix return type of cursorType()

ENH: Specify QPen instead of cursor color alone

ENH: Add testing for ctkCursorPixmapWidget pen width

ENH: ctkCursorPixmapWidget does not support transparent margin colors

ENH: Missing error checking and updating in set functions

ENH: Test cursor pen width 0 and 1

ENH: Handle bullsEyeWidth of 0 and 1

STYLE: Explicitely include math.h for floor/ceil

ENH: Show cursor and magnify widgets at end of test
Danielle Pace 14 years ago
parent
commit
709e942211

+ 26 - 1
Libs/Visualization/VTK/Widgets/Testing/Cpp/CMakeLists.txt

@@ -67,6 +67,17 @@ MACRO( SIMPLE_TEST  TESTNAME )
   SET_PROPERTY(TEST ${TESTNAME} PROPERTY LABELS ${PROJECT_NAME})
 ENDMACRO( SIMPLE_TEST  )
 
+MACRO( MAGNIFY_WIDGET_TEST  TESTNAME  TESTTYPE  SIZE  MAGNIFICATION)
+  ADD_TEST( ${TESTNAME}${TESTTYPE} ${KIT_TESTS} ${TESTNAME}
+            -D "${CTKData_DIR}/Data"
+            -V "${CTKData_DIR}/Baseline/Libs/Visualization/VTK/Widgets"
+            -T "${TESTTYPE}"
+            -S "${SIZE}"
+            -M "${MAGNIFICATION}"
+            )
+  SET_PROPERTY(TEST ${TESTNAME}${TESTTYPE} PROPERTY LABELS ${PROJECT_NAME})
+ENDMACRO( MAGNIFY_WIDGET_TEST  )
+
 #
 # Add Tests
 #
@@ -101,7 +112,21 @@ SIMPLE_TEST( ctkVTKThumbnailViewTest1 )
 #
 IF(EXISTS "${CTKData_DIR}")
   #
-  SIMPLE_TEST( ctkVTKMagnifyWidgetTest2 )
+  # Dependencies required so that these tests won't run in parallel
+  #
+  MAGNIFY_WIDGET_TEST( ctkVTKMagnifyWidgetTest2 OddOdd 341 17 )
+  MAGNIFY_WIDGET_TEST( ctkVTKMagnifyWidgetTest2 EvenEven 340 18 )
+  SET_TESTS_PROPERTIES( ctkVTKMagnifyWidgetTest2EvenEven
+                        PROPERTIES DEPENDS
+                        ctkVTKMagnifyWidgetTest2OddOdd )
+  MAGNIFY_WIDGET_TEST( ctkVTKMagnifyWidgetTest2 OddEven 341 18 )
+  SET_TESTS_PROPERTIES( ctkVTKMagnifyWidgetTest2OddEven
+                        PROPERTIES DEPENDS
+                        ctkVTKMagnifyWidgetTest2EvenEven )
+  MAGNIFY_WIDGET_TEST( ctkVTKMagnifyWidgetTest2 EvenOdd 340 17 )
+  SET_TESTS_PROPERTIES( ctkVTKMagnifyWidgetTest2EvenOdd
+                        PROPERTIES DEPENDS
+                        ctkVTKMagnifyWidgetTest2OddEven )
   SIMPLE_TEST( ctkVTKSliceViewTest2 )
   SIMPLE_TEST( ctkVTKRenderViewTest2 )
 ENDIF()

+ 14 - 3
Libs/Visualization/VTK/Widgets/Testing/Cpp/ctkVTKMagnifyWidgetTest1.cpp

@@ -20,6 +20,7 @@
 
 // Qt includes
 #include <QApplication>
+#include <QTimer>
 
 // CTK includes
 #include "ctkVTKMagnifyWidget.h"
@@ -40,7 +41,9 @@ int ctkVTKMagnifyWidgetTest1(int argc, char * argv [] )
 
   // check default values
   if (!magnify.showCursor() ||
-      magnify.cursorColor() != magnify.palette().color(QPalette::Highlight) ||
+      magnify.cursorPen().color() != magnify.palette().color(QPalette::Highlight) ||
+      magnify.cursorPen().width() != 0 ||
+      magnify.cursorPen().joinStyle() != Qt::MiterJoin ||
       magnify.cursorType() != ctkCursorPixmapWidget::CrossHairCursor ||
       magnify.marginColor() != magnify.palette().color(QPalette::Window) ||
       magnify.bullsEyeWidth() != 15 ||
@@ -49,7 +52,9 @@ int ctkVTKMagnifyWidgetTest1(int argc, char * argv [] )
     {
     std::cerr << "ctkVTKMagnifyWidget: Wrong default values. " << std::endl
               << " " << magnify.showCursor()
-              << " " << qPrintable(magnify.cursorColor().name())
+              << " " << qPrintable(magnify.cursorPen().color().name())
+              << " " << magnify.cursorPen().width()
+              << " " << static_cast<int>(magnify.cursorPen().joinStyle())
               << " " << magnify.cursorType()
               << " " << qPrintable(magnify.marginColor().name())
               << " " << magnify.bullsEyeWidth()
@@ -165,7 +170,13 @@ int ctkVTKMagnifyWidgetTest1(int argc, char * argv [] )
     return EXIT_FAILURE;
     }
 
-  return EXIT_SUCCESS;
+  magnify.show();
+  if (argc < 2 || QString(argv[1]) != "-I" )
+    {
+    QTimer::singleShot(200, &app, SLOT(quit()));
+    }
+
+  return app.exec();
 
 }
 

+ 26 - 19
Libs/Visualization/VTK/Widgets/Testing/Cpp/ctkVTKMagnifyWidgetTest2.cpp

@@ -63,8 +63,8 @@ void imageSave(ctkVTKMagnifyWidget * magnify, QString baselineDirectory,
 //-----------------------------------------------------------------------------
 bool runBaselineTest(int time, QApplication& app, ctkVTKMagnifyWidget * magnify,
                      QWidget * underWidget, bool shouldBeUnder,
-                     QString baselineDirectory, QString baselineFilename,
-                     QString errorMessage)
+                     QString baselineDirectory, QString testName,
+                     QString testNumber, QString errorMessage)
 {
   QTimer::singleShot(time, &app, SLOT(quit()));
   if (app.exec() == EXIT_FAILURE)
@@ -79,11 +79,12 @@ bool runBaselineTest(int time, QApplication& app, ctkVTKMagnifyWidget * magnify,
         << qPrintable(errorMessage) << std::endl;
     return false;
     }
+  QString baselineFilename
+      = "ctkVTKMagnifyWidgetTest2" + testNumber + testName + ".png";
   if (!imageCompare(magnify, baselineDirectory, baselineFilename))
     {
     std::cerr << "ctkVTKMagnifyWidget baseline comparison failed when "
               << qPrintable(errorMessage) << "." << std::endl;
-
     return false;
     }
   return true;
@@ -99,6 +100,9 @@ int ctkVTKMagnifyWidgetTest2(int argc, char * argv [] )
   parser.addArgument("", "-D", QVariant::String);
   parser.addArgument("", "-V", QVariant::String);
   parser.addArgument("", "-I", QVariant::String);
+  parser.addArgument("", "-T", QVariant::String);
+  parser.addArgument("", "-S", QVariant::String);
+  parser.addArgument("", "-M", QVariant::String);
   bool ok = false;
   QHash<QString, QVariant> parsedArgs = parser.parseArguments(app.arguments(), &ok);
   if (!ok)
@@ -108,19 +112,22 @@ int ctkVTKMagnifyWidgetTest2(int argc, char * argv [] )
     }
   QString dataDirectory = parsedArgs["-D"].toString();
   QString baselineDirectory = parsedArgs["-V"].toString();
+  QString testType = parsedArgs["-T"].toString();
   bool interactive = parsedArgs["-I"].toBool();
+  int size = parsedArgs["-S"].toInt();
+  double magnification = parsedArgs["-M"].toDouble();
 
   // Create the parent widget
   QWidget parentWidget;
   QHBoxLayout layout(&parentWidget);
 
   // Magnify widget parameters (we want an odd widget size and odd bullsEye)
-  int size = 341;
   bool showCursor = true;
-  QColor cursorColor = Qt::yellow;
+  QPen cursorPen(QPen(Qt::yellow));
+  cursorPen.setJoinStyle(Qt::MiterJoin);
   ctkCursorPixmapWidget::CursorType cursorType
       = ctkCursorPixmapWidget::BullsEyeCursor;
-  double magnification = 17;
+  double bullsEyeWidth = magnification + 2;
   QColor marginColor = Qt::magenta;
 
   // Create the magnify widget
@@ -128,9 +135,9 @@ int ctkVTKMagnifyWidgetTest2(int argc, char * argv [] )
   magnify->setMinimumSize(size,size);
   magnify->setMaximumSize(size,size);
   magnify->setShowCursor(showCursor);
-  magnify->setCursorColor(cursorColor);
+  magnify->setCursorPen(cursorPen);
   magnify->setCursorType(cursorType);
-  magnify->setBullsEyeWidth(magnification);
+  magnify->setBullsEyeWidth(bullsEyeWidth);
   magnify->setMarginColor(marginColor);
   magnify->setMagnification(magnification);
   layout.addWidget(magnify);
@@ -142,10 +149,10 @@ int ctkVTKMagnifyWidgetTest2(int argc, char * argv [] )
               << magnify->showCursor() << std::endl;
     return EXIT_FAILURE;
     }
-  if (magnify->cursorColor() != cursorColor)
+  if (magnify->cursorPen() != cursorPen)
     {
-    std::cerr << "ctkVTKMagnifyWidget:setCursorColor failed. "
-              << qPrintable(magnify->cursorColor().name()) << std::endl;
+    std::cerr << "ctkVTKMagnifyWidget:setCursorPen failed. "
+              << qPrintable(magnify->cursorPen().color().name()) << std::endl;
     return EXIT_FAILURE;
     }
   if (magnify->cursorType() != cursorType)
@@ -154,7 +161,7 @@ int ctkVTKMagnifyWidgetTest2(int argc, char * argv [] )
               << magnify->cursorType() << std::endl;
     return EXIT_FAILURE;
     }
-  if (magnify->bullsEyeWidth() != magnification)
+  if (magnify->bullsEyeWidth() != bullsEyeWidth)
     {
     std::cerr << "ctkVTKMagnifyWidget:setBullsEyeWidth failed. "
               << magnify->bullsEyeWidth() << std::endl;
@@ -264,7 +271,7 @@ int ctkVTKMagnifyWidgetTest2(int argc, char * argv [] )
   QCursor::setPos(insideSlice0);
   parentWidget.show();
   if (!runBaselineTest(time, app, magnify, allSliceViews[0], true,
-                       baselineDirectory, "ctkVTKMagnifyWidgetTest2a.png",
+                       baselineDirectory, testType, "a",
                        "magnify widget first shown with cursor inside observed widget"))
     {
     return EXIT_FAILURE;
@@ -276,7 +283,7 @@ int ctkVTKMagnifyWidgetTest2(int argc, char * argv [] )
   QCursor::setPos(outside);
   parentWidget.show();
   if (!runBaselineTest(time, app, magnify, &parentWidget, false,
-                       baselineDirectory, "ctkVTKMagnifyWidgetTest2b.png",
+                       baselineDirectory, testType, "b",
                        "magnify widget first shown with cursor outside observed widget"))
     {
     return EXIT_FAILURE;
@@ -285,7 +292,7 @@ int ctkVTKMagnifyWidgetTest2(int argc, char * argv [] )
   // Test magnification after move to allSliceViews[1]
   QCursor::setPos(insideSlice1);
   if (!runBaselineTest(time, app, magnify, allSliceViews[1], true,
-                       baselineDirectory, "ctkVTKMagnifyWidgetTest2c.png",
+                       baselineDirectory, testType, "c",
                        "cursor moved inside 2nd observed widget the first time"))
     {
     return EXIT_FAILURE;
@@ -294,7 +301,7 @@ int ctkVTKMagnifyWidgetTest2(int argc, char * argv [] )
   // Test magnification after move within allSliceViews[1] close to border
   QCursor::setPos(insideSlice1edge);
   if (!runBaselineTest(time, app, magnify, allSliceViews[1], true,
-                       baselineDirectory, "ctkVTKMagnifyWidgetTest2d.png",
+                       baselineDirectory, testType, "d",
                        "cursor moved inside 2nd observed widget the second time"))
     {
     return EXIT_FAILURE;
@@ -303,7 +310,7 @@ int ctkVTKMagnifyWidgetTest2(int argc, char * argv [] )
   // Test magnification after move outside an observed widget (should be blank)
   QCursor::setPos(insideSlice2);
   if (!runBaselineTest(time, app, magnify, allSliceViews[2], true,
-                       baselineDirectory, "ctkVTKMagnifyWidgetTest2e.png",
+                       baselineDirectory, testType, "e",
                        "cursor moved inside unobserved widget"))
     {
     return EXIT_FAILURE;
@@ -313,7 +320,7 @@ int ctkVTKMagnifyWidgetTest2(int argc, char * argv [] )
   // right corner)
   QCursor::setPos(insideSlice0bottomRightCorner);
   if (!runBaselineTest(time, app, magnify, allSliceViews[0], true,
-                       baselineDirectory, "ctkVTKMagnifyWidgetTest2f.png",
+                       baselineDirectory, testType, "f",
                        "cursor moved to bottom-right corner of observed widget"))
     {
     return EXIT_FAILURE;
@@ -323,7 +330,7 @@ int ctkVTKMagnifyWidgetTest2(int argc, char * argv [] )
   // corner)
   QCursor::setPos(insideSlice0topLeftCorner);
   if (!runBaselineTest(time, app, magnify, allSliceViews[0], true,
-                       baselineDirectory, "ctkVTKMagnifyWidgetTest2g.png",
+                       baselineDirectory, testType, "g",
                        "cursor moved to top-left corner of observed widget"))
     {
     return EXIT_FAILURE;

+ 97 - 39
Libs/Visualization/VTK/Widgets/ctkVTKMagnifyWidget.cpp

@@ -32,6 +32,9 @@
 #include <vtkRenderWindow.h>
 #include <vtkUnsignedCharArray.h>
 
+// STD includes
+#include <math.h>
+
 // Convenient macro
 #define VTK_CREATE(type, name) \
   vtkSmartPointer<type> name = vtkSmartPointer<type>::New()
@@ -143,53 +146,55 @@ void ctkVTKMagnifyWidgetPrivate::updatePixmap(QVTKWidget * widget, QPointF pos)
   // Given a magnification and the label's widget size, compute the number of
   // pixels we can show.  We should round to get a larger integer extent, since
   // we will later adjust the pixmap's location in paintEvent().
+  // Left-right and up-down are in the render window coordinate frame.
+  // (which is different in the y-direction compared to Qt coordinates).
   QSizeF sizeToMagnify = QSizeF(q->size()) / this->Magnification;
-  double dx0 = (mouseWindowPos.x() - ((sizeToMagnify.width()-1.0) / 2.0));
-  double dx1 = (mouseWindowPos.x() + ((sizeToMagnify.width()-1.0) / 2.0));
-  double dy0 = (mouseWindowPos.y() - ((sizeToMagnify.height()-1.0) / 2.0));
-  double dy1 = (mouseWindowPos.y() + ((sizeToMagnify.height()-1.0) / 2.0));
+  double posLeft = (mouseWindowPos.x() - ((sizeToMagnify.width()-1.0) / 2.0));
+  double posRight = (mouseWindowPos.x() + ((sizeToMagnify.width()-1.0) / 2.0));
+  double posBottom = (mouseWindowPos.y() - ((sizeToMagnify.height()-1.0) / 2.0));
+  double posTop = (mouseWindowPos.y() + ((sizeToMagnify.height()-1.0) / 2.0));
 
   // Round to ints, for indexing into the pixel array
-  int ix0 = floor(dx0);
-  int ix1 = ceil(dx1);
-  int iy0 = floor(dy0);
-  int iy1 = ceil(dy1);
+  int indexLeft = floor(posLeft);
+  int indexRight = ceil(posRight);
+  int indexBottom = floor(posBottom);
+  int indexTop = ceil(posTop);
 
   // Handle when mouse is near the border
-  int min_x0 = 0;
-  int max_x1 = windowSize[0]-1;
-  int min_y0 = 0;
-  int max_y1 = windowSize[1]-1;
+  int minLeft = 0;
+  int maxRight = windowSize[0]-1;
+  int minBottom = 0;
+  int maxTop = windowSize[1]-1;
 
-  bool overLeft = ix0 < min_x0;
-  bool overRight = ix1 > max_x1;
-  bool overBottom = iy0 < min_y0;
-  bool overTop = iy1 > max_y1;
+  bool overLeft = indexLeft < minLeft;
+  bool overRight = indexRight > maxRight;
+  bool overBottom = indexBottom < minBottom;
+  bool overTop = indexTop > maxTop;
 
   // Ensure we don't access nonexistant indices
   if (overLeft)
     {
-    ix0 = min_x0;
-    dx0 = min_x0;
+    indexLeft = minLeft;
+    posLeft = minLeft;
     }
   if (overRight)
     {
-    ix1 = max_x1;
-    dx1 = max_x1;
+    indexRight = maxRight;
+    posRight = maxRight;
     }
   if (overBottom)
     {
-    iy0 = min_y0;
-    dy0 = min_y0;
+    indexBottom = minBottom;
+    posBottom = minBottom;
     }
   if (overTop)
     {
-    iy1 = max_y1;
-    dy1 = max_y1;
+    indexTop = maxTop;
+    posTop = maxTop;
     }
 
   // Error case
-  if (ix0 > ix1 || iy0 > iy1)
+  if (indexLeft > indexRight || indexBottom > indexTop)
     {
     this->removePixmap();
     return;
@@ -223,13 +228,15 @@ void ctkVTKMagnifyWidgetPrivate::updatePixmap(QVTKWidget * widget, QPointF pos)
     }
   q->setAlignment(alignment);
 
-  // Retrieve the pixel data into a QImage
-  QSize actualSize(ix1-ix0+1, iy1-iy0+1);
+  // Retrieve the pixel data into a QImage (flip vertically to move from render
+  // window coordinates to Qt coordinates)
+  QSize actualSize(indexRight-indexLeft+1, indexTop-indexBottom+1);
   QImage image(actualSize.width(), actualSize.height(), QImage::Format_RGB32);
   vtkUnsignedCharArray * pixelData = vtkUnsignedCharArray::New();
   pixelData->SetArray(image.bits(), actualSize.width() * actualSize.height() * 4, 1);
   int front = renderWindow->GetDoubleBuffer();
-  int success = renderWindow->GetRGBACharPixelData(ix0, iy0, ix1, iy1, front, pixelData);
+  int success = renderWindow->GetRGBACharPixelData(
+      indexLeft, indexBottom, indexRight, indexTop, front, pixelData);
   if (!success)
     {
     this->removePixmap();
@@ -241,18 +248,66 @@ void ctkVTKMagnifyWidgetPrivate::updatePixmap(QVTKWidget * widget, QPointF pos)
 
   // Scale the image to zoom, using FastTransformation to prevent smoothing
   QSize imageSize = actualSize * this->Magnification;
-  image = image.scaled(imageSize, Qt::KeepAspectRatioByExpanding, Qt::FastTransformation);
+  image = image.scaled(imageSize, Qt::KeepAspectRatioByExpanding,
+                       Qt::FastTransformation);
 
   // Crop the magnified image to solve the problem of magnified partial pixels
-  int x0diff = round((dx0 - static_cast<double>(ix0)) * this->Magnification);
-  int x1diff = imageSize.width()
-      - round((static_cast<double>(ix1) - dx1) * this->Magnification);
-  int y0diff = round((dy0 - static_cast<double>(iy0)) * this->Magnification);
-  int y1diff = imageSize.height()
-      - round((static_cast<double>(iy1) - dy1) * this->Magnification);
-  QRect cropRect(QPoint(x0diff, y0diff), QPoint(x1diff, y1diff));
+  double errorLeft
+      = (posLeft - static_cast<double>(indexLeft)) * this->Magnification;
+  double errorRight
+      = (static_cast<double>(indexRight) - posRight) * this->Magnification;
+  double errorBottom
+      = (posBottom - static_cast<double>(indexBottom)) * this->Magnification;
+  double errorTop
+      = (static_cast<double>(indexTop) - posTop) * this->Magnification;
+
+  // When cropping the Qt image, the 'adjust' variables are in Qt coordinates,
+  // not render window coordinates (bottom and top switch).
+  int cropIndexLeft = round(errorLeft);
+  int cropIndexRight = imageSize.width() - round(errorRight) - 1;
+  int cropIndexTop = round(errorTop);
+  int cropIndexBottom = imageSize.height() - round(errorBottom) - 1;
+
+  // Handle case when label size and magnification are not both even or odd
+  // (errorLeft/errorRight/errorBottom/errorTop will have fractional component,
+  // so cropped image wouldn't be the correct size unless we adjust further).
+  int requiredWidth = round((posRight - posLeft + 1) * this->Magnification);
+  int requiredHeight = round((posTop - posBottom + 1) * this->Magnification);
+  int actualWidth = cropIndexRight - cropIndexLeft + 1;
+  int actualHeight = cropIndexBottom - cropIndexTop + 1;
+  int diffWidth = requiredWidth - actualWidth;
+  int diffHeight = requiredHeight - actualHeight;
+  // Too wide
+  if (diffWidth < 0 && cropIndexRight != imageSize.width()-1)
+    {
+    Q_ASSERT(actualWidth - requiredWidth <= 1);
+    cropIndexRight += diffWidth;
+    }
+  // Too narrow
+  else if (diffWidth > 0 && cropIndexLeft != 0)
+    {
+    Q_ASSERT(requiredWidth - actualWidth <= 1);
+    cropIndexLeft -= diffWidth;
+    }
+  // Too tall
+  if (diffHeight < 0 && cropIndexBottom != imageSize.height()-1)
+    {
+    Q_ASSERT(actualHeight - requiredHeight <= 1);
+    cropIndexBottom += diffHeight;
+    }
+  // Too short
+  else if (diffHeight > 0 && cropIndexTop != 0)
+    {
+    Q_ASSERT(requiredHeight - actualHeight <= 1);
+    cropIndexTop -= diffHeight;
+    }
+
+  // Finally crop the QImage for display
+  QRect cropRect(QPoint(cropIndexLeft, cropIndexTop),
+                 QPoint(cropIndexRight, cropIndexBottom));
   image = image.copy(cropRect);
 
+
   // Finally, set the pixelmap to the new one we have created
   q->setPixmap(QPixmap::fromImage(image));
 }
@@ -280,11 +335,14 @@ CTK_GET_CPP(ctkVTKMagnifyWidget, double, magnification, Magnification);
 // --------------------------------------------------------------------------
 void ctkVTKMagnifyWidget::setMagnification(double newMagnification)
 {
-  if (newMagnification > 0)
+  Q_D(ctkVTKMagnifyWidget);
+  if (newMagnification == d->Magnification || newMagnification <= 0)
     {
-    Q_D(ctkVTKMagnifyWidget);
-    d->Magnification = newMagnification;
+    return;
     }
+
+  d->Magnification = newMagnification;
+  this->update();
 }
 
 // --------------------------------------------------------------------------

+ 7 - 1
Libs/Visualization/VTK/Widgets/ctkVTKMagnifyWidget.h

@@ -33,6 +33,11 @@ class QVTKWidget;
 
 class ctkVTKMagnifyWidgetPrivate;
 
+/// Gives a magnified view of a QVTKWidget around the mouse position, with
+/// overlaid cursor (ex. cross-hair).  You must specify the QVTKWidget(s) to be
+/// observed.
+/// \sa ctkCursorPixmapWidget
+
 class CTK_VISUALIZATION_VTK_WIDGETS_EXPORT ctkVTKMagnifyWidget
   : public ctkCursorPixmapWidget
 {
@@ -45,7 +50,8 @@ public:
   explicit ctkVTKMagnifyWidget(QWidget* parent = 0);
   virtual ~ctkVTKMagnifyWidget();
 
-  /// Set/get the magnification (zoom).  Default 1.0.
+  /// Set/get the magnification (zoom).  Looks best when the magnification and
+  /// the widget size are both either even or odd.  Default 1.0.
   double magnification() const;
   void setMagnification(double newMagnification);
 

+ 26 - 12
Libs/Widgets/Testing/Cpp/ctkCursorPixmapWidgetTest1.cpp

@@ -20,6 +20,7 @@
 
 // Qt includes
 #include <QApplication>
+#include <QTimer>
 
 // CTK includes
 #include "ctkCursorPixmapWidget.h"
@@ -37,14 +38,18 @@ int ctkCursorPixmapWidgetTest1(int argc, char * argv [] )
 
   // check default values
   if (!cursor.showCursor() ||
-      cursor.cursorColor() != cursor.palette().color(QPalette::Highlight) ||
+      cursor.cursorPen().color() != cursor.palette().color(QPalette::Highlight) ||
+      cursor.cursorPen().width() != 0 ||
+      cursor.cursorPen().joinStyle() != Qt::MiterJoin ||
       cursor.cursorType() != ctkCursorPixmapWidget::CrossHairCursor ||
       cursor.marginColor() != cursor.palette().color(QPalette::Window) ||
       cursor.bullsEyeWidth() != 15)
     {
     std::cerr << "ctkCursorPixmapWidget: Wrong default values. " << std::endl
               << " " << cursor.showCursor()
-              << " " << qPrintable(cursor.cursorColor().name())
+              << " " << qPrintable(cursor.cursorPen().color().name())
+              << " " << cursor.cursorPen().width()
+              << " " << static_cast<int>(cursor.cursorPen().joinStyle())
               << " " << cursor.cursorType()
               << " " << qPrintable(cursor.marginColor().name())
               << " " << cursor.bullsEyeWidth() << std::endl;
@@ -61,14 +66,14 @@ int ctkCursorPixmapWidgetTest1(int argc, char * argv [] )
     }
   cursor.setShowCursor(true);
 
-  // Cursor color
-  QColor transparentBlue(Qt::blue);
-  transparentBlue.setAlphaF(0.25);
-  cursor.setCursorColor(transparentBlue);
-  if (cursor.cursorColor() != transparentBlue)
+  // Cursor pen
+  QPen cursorPen(Qt::yellow);
+  cursorPen.setJoinStyle(Qt::MiterJoin);
+  cursor.setCursorPen(cursorPen);
+  if (cursor.cursorPen() != cursorPen)
     {
-    std::cerr << "ctkCursorPixmapWidget:setCursorColor failed. "
-              << qPrintable(cursor.cursorColor().name()) << std::endl;
+    std::cerr << "ctkCursorPixmapWidget:setCursorPen failed. "
+              << qPrintable(cursor.cursorPen().color().name()) << std::endl;
     return EXIT_FAILURE;
     }
 
@@ -82,6 +87,8 @@ int ctkCursorPixmapWidgetTest1(int argc, char * argv [] )
     }
 
   // Margin color - invalid input
+  QColor transparentBlue(Qt::blue);
+  transparentBlue.setAlphaF(0.25);
   QColor origColor = cursor.marginColor();
   cursor.setMarginColor(QColor());
   if (cursor.marginColor() != origColor)
@@ -91,9 +98,9 @@ int ctkCursorPixmapWidgetTest1(int argc, char * argv [] )
     return EXIT_FAILURE;
     }
 
-  // Margin color - valid input
+  // Margin color - should ignore alpha channel
   cursor.setMarginColor(transparentBlue);
-  if (cursor.marginColor() != transparentBlue)
+  if (cursor.marginColor() != Qt::blue)
     {
       {
       std::cerr << "ctkCursorPixmapWidget:setMarginColor failed - valid input. "
@@ -111,6 +118,13 @@ int ctkCursorPixmapWidgetTest1(int argc, char * argv [] )
     return EXIT_FAILURE;
     }
 
-  return EXIT_SUCCESS;
+  cursor.show();
+  if (argc < 2 || QString(argv[1]) != "-I" )
+    {
+    QTimer::singleShot(200, &app, SLOT(quit()));
+    }
+
+  return app.exec();
+
 }
 

+ 82 - 13
Libs/Widgets/Testing/Cpp/ctkCursorPixmapWidgetTest2.cpp

@@ -101,11 +101,14 @@ int ctkCursorPixmapWidgetTest2(int argc, char * argv [] )
 
   // Create the cursor widget
   ctkCursorPixmapWidget cursor;
-  cursor.setCursorColor(Qt::yellow);
+  QPen cursorPen(Qt::yellow);
+  cursorPen.setJoinStyle(Qt::MiterJoin);
+  cursor.setCursorPen(cursorPen);
   cursor.setMarginColor(Qt::blue);
 
   int time = 200;
   QPixmap pixmap(dataDirectory + "/" + "computerIcon.png");
+
   // Basesize is always odd
   QSize baseSize = pixmap.size();
   if (pixmap.width() % 2 == 0)
@@ -116,46 +119,108 @@ int ctkCursorPixmapWidgetTest2(int argc, char * argv [] )
     {
     baseSize.setHeight(baseSize.height()+1);
     }
+
+  // Odd widget size
   cursor.setMinimumSize(baseSize);
   cursor.setPixmap(pixmap.scaled(baseSize));
   cursor.show();
 
-  // Bullseye cursor
+  // Test bullsEyeWidth and line width with odd widget size
   cursor.setCursorType(ctkCursorPixmapWidget::BullsEyeCursor);
+
+  ///
   cursor.setBullsEyeWidth(15);
+  cursorPen.setWidth(1);
+  cursor.setCursorPen(cursorPen);
   if (!runBaselineTest(time, app, cursor, baselineDirectory,
                        "ctkCursorPixmapWidgetTest2a.png",
-                       "using bulls-eye cursor (odd size, odd bullsEye)"))
+                       "using bulls-eye cursor (odd size, bullsEye 15, width 1)"))
     {
     return EXIT_FAILURE;
     }
-  cursor.setBullsEyeWidth(14);
+
+  ///
+  cursorPen.setWidth(5);
+  cursor.setCursorPen(cursorPen);
   if (!runBaselineTest(time, app, cursor, baselineDirectory,
                        "ctkCursorPixmapWidgetTest2b.png",
-                       "using bulls-eye cursor (odd size, even bullsEye)"))
+                       "using bulls-eye cursor (odd size, bullsEye 15, width 5)"))
     {
     return EXIT_FAILURE;
     }
-  cursor.resize(baseSize.width()+1, baseSize.height()+1);
+
+  ///
+  cursor.setBullsEyeWidth(14);
+  cursorPen.setWidth(0); // equivalent to 1
+  cursor.setCursorPen(cursorPen);
   if (!runBaselineTest(time, app, cursor, baselineDirectory,
                        "ctkCursorPixmapWidgetTest2c.png",
-                       "using bulls-eye cursor (even size, even bullsEye)"))
+                       "using bulls-eye cursor (odd size, bullsEye 14, width 1)"))
     {
     return EXIT_FAILURE;
     }
-  cursor.setBullsEyeWidth(15);
+
+  ///
+  cursorPen.setWidth(4);
+  cursor.setCursorPen(cursorPen);
   if (!runBaselineTest(time, app, cursor, baselineDirectory,
                        "ctkCursorPixmapWidgetTest2d.png",
-                       "using bulls-eye cursor (even size, odd bullsEye)"))
+                       "using bulls-eye cursor (odd size, bullsEye 14, width 4)"))
+    {
+    return EXIT_FAILURE;
+    }
+
+  // Test bullsEyeWidth and line width with even widget size
+  cursor.resize(baseSize.width()+1, baseSize.height()+1);
+
+  ///
+  cursor.setBullsEyeWidth(14);
+  cursorPen.setWidth(1);
+  cursor.setCursorPen(cursorPen);
+  if (!runBaselineTest(time, app, cursor, baselineDirectory,
+                       "ctkCursorPixmapWidgetTest2e.png",
+                       "using bulls-eye cursor (even size, bullsEye 14, width 1)"))
+    {
+    return EXIT_FAILURE;
+    }
+
+  ///
+  cursorPen.setWidth(4);
+  cursor.setCursorPen(cursorPen);
+  if (!runBaselineTest(time, app, cursor, baselineDirectory,
+                       "ctkCursorPixmapWidgetTest2f.png",
+                       "using bulls-eye cursor (even size, bullsEye 14, width 4)"))
+    {
+    return EXIT_FAILURE;
+    }
+
+  ///
+  cursor.setBullsEyeWidth(15);
+  cursorPen.setWidth(0);
+  cursor.setCursorPen(cursorPen);
+  if (!runBaselineTest(time, app, cursor, baselineDirectory,
+                       "ctkCursorPixmapWidgetTest2g.png",
+                       "using bulls-eye cursor (even size, bullsEye 15, width 1)"))
+    {
+    return EXIT_FAILURE;
+    }
+
+  ///
+  cursorPen.setWidth(5);
+  cursor.setCursorPen(cursorPen);
+  if (!runBaselineTest(time, app, cursor, baselineDirectory,
+                       "ctkCursorPixmapWidgetTest2h.png",
+                       "using bulls-eye cursor (even size, bullsEye 15, width 5)"))
     {
     return EXIT_FAILURE;
     }
-  cursor.resize(baseSize);
 
   // Cursor not shown
+  cursor.resize(baseSize);
   cursor.setShowCursor(false);
+  cursor.setCursorPen(cursorPen);
   if (!runBaselineTest(time, app, cursor, baselineDirectory,
-                       "ctkCursorPixmapWidgetTest2e.png",
+                       "ctkCursorPixmapWidgetTest2i.png",
                        "show cursor false"))
     {
     return EXIT_FAILURE;
@@ -164,15 +229,19 @@ int ctkCursorPixmapWidgetTest2(int argc, char * argv [] )
   // Crosshair cursor
   cursor.setShowCursor(true);
   cursor.setCursorType(ctkCursorPixmapWidget::CrossHairCursor);
+  cursorPen.setWidth(0);
+  cursor.setCursorPen(cursorPen);
   if (!runBaselineTest(time, app, cursor, baselineDirectory,
-                       "ctkCursorPixmapWidgetTest2f.png",
+                       "ctkCursorPixmapWidgetTest2j.png",
                        "using cross-hair cursor (odd size)"))
     {
     return EXIT_FAILURE;
     }
   cursor.resize(baseSize.width()+1, baseSize.height()+1);
+  cursorPen.setWidth(1);
+  cursor.setCursorPen(cursorPen);
   if (!runBaselineTest(time, app, cursor, baselineDirectory,
-                       "ctkCursorPixmapWidgetTest2g.png",
+                       "ctkCursorPixmapWidgetTest2k.png",
                        "using cross-hair cursor (even size)"))
     {
     return EXIT_FAILURE;

+ 28 - 0
Libs/Widgets/ctkAxesWidget.cpp

@@ -314,3 +314,31 @@ void ctkAxesWidget::mouseReleaseEvent(QMouseEvent *mouseEvent)
   Q_D(ctkAxesWidget);
   this->setCurrentAxis(d->axisAtPos(mouseEvent->pos()));
 }
+
+// --------------------------------------------------------------------------
+QSize ctkAxesWidget::minimumSizeHint()const
+{
+  // Pretty arbitrary size.
+  return QSize(100, 100);
+}
+
+// --------------------------------------------------------------------------
+QSize ctkAxesWidget::sizeHint()const
+{
+  // Pretty arbitrary size
+  return QSize(100, 100);
+}
+
+//----------------------------------------------------------------------------
+bool ctkAxesWidget::hasHeightForWidth()const
+{
+  return true;
+}
+
+//----------------------------------------------------------------------------
+int ctkAxesWidget::heightForWidth(int width)const
+{
+  // Tends to be square
+  return width;
+}
+

+ 6 - 0
Libs/Widgets/ctkAxesWidget.h

@@ -80,6 +80,12 @@ public slots :
   /// Set the autoReset property to None anytime the currentAxis is changed.
   void setAutoReset(bool reset);
 
+  /// Size hints
+  virtual QSize minimumSizeHint()const;
+  virtual QSize sizeHint()const;
+  virtual bool hasHeightForWidth()const;
+  virtual int heightForWidth(int width)const;
+
 protected: 
   void paintEvent(QPaintEvent *);
   void mousePressEvent(QMouseEvent *mouseEvent);

+ 79 - 18
Libs/Widgets/ctkCursorPixmapWidget.cpp

@@ -51,6 +51,7 @@ public:
   void drawBullsEyeCursor(QPainter& painter);
 
   bool      ShowCursor;
+  QPen      CursorPen;
   ctkCursorPixmapWidget::CursorTypes CursorType;
   int       BullsEyeWidth;
 
@@ -75,6 +76,9 @@ void ctkCursorPixmapWidgetPrivate::init()
   Q_Q(ctkCursorPixmapWidget);
   q->setAutoFillBackground(true);
   q->setAlignment(Qt::AlignCenter);
+  this->CursorPen.setColor(q->palette().color(QPalette::Highlight));
+  this->CursorPen.setWidth(0);
+  this->CursorPen.setJoinStyle(Qt::MiterJoin);
 }
 
 //---------------------------------------------------------------------------
@@ -89,7 +93,7 @@ void ctkCursorPixmapWidgetPrivate::drawCursor()
   // Setup the painter object to paint on the label
   Q_Q(ctkCursorPixmapWidget);
   QPainter painter(q);
-  painter.setPen(q->cursorColor());
+  painter.setPen(this->CursorPen);
 
   // Draw cursor (based on current parameters) onto the label
   switch (this->CursorType)
@@ -125,9 +129,19 @@ void ctkCursorPixmapWidgetPrivate::drawBullsEyeCursor(QPainter& painter)
 
   // Draw rectangle
   double bullsEye = this->BullsEyeWidth;
+  double lineWidth = painter.pen().width();
+  lineWidth = std::max(lineWidth, 1.0);
+  double halfLineWidth = (lineWidth-1.0) / 2.0;
   double x = (size.width()-bullsEye) / 2.0;
   double y = (size.height()-bullsEye) / 2.0;
-  painter.drawRect(QRectF(x, y, bullsEye-1.0, bullsEye-1.0));
+  double rectWidth = bullsEye;
+  if (bullsEye != 1)
+    {
+    rectWidth = rectWidth - lineWidth;
+    }
+  rectWidth = std::max(rectWidth, 0.0);
+  painter.drawRect(
+      QRectF(x+halfLineWidth, y+halfLineWidth, rectWidth, rectWidth));
 
   // Draw the lines
   double halfWidth = (size.width()-1.0) / 2.0;
@@ -174,25 +188,63 @@ void ctkCursorPixmapWidget::setShowCursor(bool newShow)
 }
 
 // --------------------------------------------------------------------------
+CTK_GET_CPP(ctkCursorPixmapWidget, QPen, cursorPen, CursorPen);
+
+// --------------------------------------------------------------------------
+void ctkCursorPixmapWidget::setCursorPen(const QPen& newPen)
+{
+  Q_D(ctkCursorPixmapWidget);
+  if (newPen == d->CursorPen)
+    {
+    return;
+    }
+
+  d->CursorPen = newPen;
+  this->update();
+}
+
+// --------------------------------------------------------------------------
 QColor ctkCursorPixmapWidget::cursorColor() const
-  {
-  return this->palette().color(QPalette::Highlight);
-  }
+{
+  Q_D(const ctkCursorPixmapWidget);
+  return d->CursorPen.color();
+}
 
 // --------------------------------------------------------------------------
 void ctkCursorPixmapWidget::setCursorColor(const QColor& newColor)
 {
-  if (newColor.isValid())
+  Q_D(ctkCursorPixmapWidget);
+  if (d->CursorPen.color() == newColor)
     {
-    QPalette palette(this->palette());
-    palette.setColor(QPalette::Highlight, newColor);
-    this->setPalette(palette);
-    this->update();
+    return;
     }
+
+  d->CursorPen.setColor(newColor);
+  this->update();
 }
 
 // --------------------------------------------------------------------------
-CTK_GET_CPP(ctkCursorPixmapWidget, const ctkCursorPixmapWidget::CursorTypes&, cursorType, CursorType);
+int ctkCursorPixmapWidget::lineWidth() const
+{
+  Q_D(const ctkCursorPixmapWidget);
+  return d->CursorPen.width();
+}
+
+// --------------------------------------------------------------------------
+void ctkCursorPixmapWidget::setLineWidth(int newWidth)
+{
+  Q_D(ctkCursorPixmapWidget);
+  if (d->CursorPen.width() == newWidth || newWidth < 0)
+    {
+    return;
+    }
+
+  d->CursorPen.setWidth(newWidth);
+  this->update();
+}
+
+// --------------------------------------------------------------------------
+CTK_GET_CPP(ctkCursorPixmapWidget, ctkCursorPixmapWidget::CursorTypes, cursorType, CursorType);
 
 // --------------------------------------------------------------------------
 void ctkCursorPixmapWidget::setCursorType(const CursorTypes& newType)
@@ -216,10 +268,17 @@ QColor ctkCursorPixmapWidget::marginColor() const
 // --------------------------------------------------------------------------
 void ctkCursorPixmapWidget::setMarginColor(const QColor& newColor)
 {
-  if (newColor.isValid())
+  if (!newColor.isValid())
+    {
+    return;
+    }
+
+  QPalette palette(this->palette());
+  QColor solidColor(newColor.rgb());
+  if (solidColor != palette.color(QPalette::Window))
     {
-    QPalette palette(this->palette());
-    palette.setColor(QPalette::Window, newColor);
+    // Ignore alpha channel
+    palette.setColor(QPalette::Window, solidColor);
     this->setPalette(palette);
     this->update();
     }
@@ -231,12 +290,14 @@ CTK_GET_CPP(ctkCursorPixmapWidget, int, bullsEyeWidth, BullsEyeWidth);
 // --------------------------------------------------------------------------
 void ctkCursorPixmapWidget::setBullsEyeWidth(int newWidth)
 {
-  if (newWidth >= 0)
+  Q_D(ctkCursorPixmapWidget);
+  if (newWidth == d->BullsEyeWidth || newWidth < 0)
     {
-    Q_D(ctkCursorPixmapWidget);
-    d->BullsEyeWidth = newWidth;
-    this->update();
+    return;
     }
+
+  d->BullsEyeWidth = newWidth;
+  this->update();
 }
 
 // --------------------------------------------------------------------------

+ 39 - 10
Libs/Widgets/ctkCursorPixmapWidget.h

@@ -23,23 +23,34 @@
 
 // QT includes
 #include <QLabel>
+#include <QPen>
 class QColor;
 class QSize;
 
 // CTK includes
 #include "ctkWidgetsExport.h"
+
 class ctkCursorPixmapWidgetPrivate;
 
-// Draws a cursor onto a pixmap
+/// Draws a cursor onto a QLabel.  This widget is designed to be used to show
+/// a cursor overlaid onto an image (the QLabel's pixmap).
+/// Since painting must be done in discrete pixels, this widget looks best
+/// when the widget size and the cursor line width are both either even or odd.
+/// If using a bulls-eye cursor, this widget looks best if the cursor line
+/// width and the bullsEyeWidth are both either even or odd.
 
 class CTK_WIDGETS_EXPORT ctkCursorPixmapWidget : public QLabel
 {
   Q_OBJECT
   Q_FLAGS(CursorType CursorTypes)
   Q_PROPERTY(bool showCursor READ showCursor WRITE setShowCursor)
+  // QT designer does not yet support QPen properties, so we provide the
+  // temporary properties cursorColor and lineWidth.
+  Q_PROPERTY(QPen cursorPen READ cursorPen WRITE setCursorPen DESIGNABLE false)
   Q_PROPERTY(QColor cursorColor READ cursorColor WRITE setCursorColor)
+  Q_PROPERTY(int lineWidth READ lineWidth WRITE setLineWidth)
   Q_PROPERTY(CursorTypes cursorType READ cursorType WRITE setCursorType)
-  Q_PROPERTY(QColor marginColor READ marginColor WRITE setMarginColor);
+  Q_PROPERTY(QColor marginColor READ marginColor WRITE setMarginColor)
   Q_PROPERTY(int bullsEyeWidth READ bullsEyeWidth WRITE setBullsEyeWidth)
 
 public:
@@ -55,28 +66,46 @@ public:
   };
   Q_DECLARE_FLAGS(CursorTypes, CursorType)
 
-  /// Set/get whether or not to draw the cursor (default true)
+  /// Set/get whether or not to draw the cursor.  Default True.
   bool showCursor() const;
   void setShowCursor(bool newShow);
 
-  /// Set/get the color of the cursor.  Default is the color from the widget's
-  /// palette Highlight role.  Note that the Highlight role is overridden if
-  /// you set this property.
+  /// Set/get the pen used to draw the cursor.  Default color is from the
+  /// widget's palette's Highlight role, default width is 0 (which draws
+  /// at 1 pixel wide regardless of painter transformation).  Since painting
+  /// must be done in discrete pixels, this widget looks best when the widget
+  /// size and the cursor pen width are both either even or odd.
+  QPen cursorPen() const;
+  void setCursorPen(const QPen& newPen);
+
+  /// Temporary: Set/get the cursor color (via QPen).  This will be removed once
+  /// Qt Designer supports QPen properties.
   QColor cursorColor() const;
   void setCursorColor(const QColor& newColor);
 
-  /// Set/get the cursor type (default CursorType_CrossHair)
-  const CursorTypes& cursorType() const;
+  /// Temporary: Set/get the cursor line width (via QPen).  This will be removed
+  /// once Qt Designer supports QPen properties.  Since painting
+  /// must be done in discrete pixels, this widget looks best when the widget
+  /// size and the cursor pen width are both either even or odd.
+  int lineWidth() const;
+  void setLineWidth(int newWidth);
+
+  /// Set/get the cursor type.  Default CursorType_CrossHair.
+  CursorTypes cursorType() const;
   void setCursorType(const CursorTypes& newType);
 
   /// Set/get color to set the widget to when not magnifying or when label
   /// size is larger than pixmap size.  Default is the color from the widget's
   /// palette Window role.  Note that the Window role is overridden if you
-  /// set this property.
+  /// set this property.  Does not support transparent colors.
   QColor marginColor() const;
   void setMarginColor(const QColor& newColor);
 
-  /// Set/get the width of the crosshair when in BullsEye mode (default 15)
+  /// Set/get the width of the crosshair when in BullsEye mode.
+  /// Looks best when the BullsEye width and the widget size are both either
+  /// even or odd, preferably odd.  Default 15.  Since painting must be done
+  /// in discrete pixels, this widget looks best if the cursor pen width and
+  /// the bullsEyeWidth are both either even or odd.
   int bullsEyeWidth() const;
   void setBullsEyeWidth(int newWidth);