Vidalia 0.2.10
|
00001 /* 00002 ** This file is part of Vidalia, and is subject to the license terms in the 00003 ** LICENSE file, found in the top level directory of this distribution. If you 00004 ** did not receive the LICENSE file with this file, you may obtain it from the 00005 ** Vidalia source package distributed by the Vidalia Project at 00006 ** http://www.vidalia-project.net/. No part of Vidalia, including this file, 00007 ** may be copied, modified, propagated, or distributed except according to the 00008 ** terms described in the LICENSE file. 00009 */ 00010 00011 /* 00012 ** \file ZImageView.cpp 00013 ** \version $Id: ZImageView.cpp 3735 2009-04-28 20:28:01Z edmanm $ 00014 ** \brief Displays an image and allows zooming and panning 00015 */ 00016 00017 #include "ZImageView.h" 00018 00019 #include <QPainter> 00020 #include <QMouseEvent> 00021 00022 #include <cmath> 00023 00024 #if QT_VERSION >= 0x040200 00025 #define CURSOR_NORMAL QCursor(Qt::OpenHandCursor) 00026 #define CURSOR_MOUSE_PRESS QCursor(Qt::ClosedHandCursor) 00027 #else 00028 #define CURSOR_NORMAL QCursor(Qt::CrossCursor) 00029 #define CURSOR_MOUSE_PRESS QCursor(Qt::SizeAllCursor) 00030 #endif 00031 00032 00033 /** Constructor. */ 00034 ZImageView::ZImageView(QWidget *parent) 00035 : QWidget(parent) 00036 { 00037 /* Initialize members */ 00038 _zoom = 0.0; 00039 _desiredX = 0.0; 00040 _desiredY = 0.0; 00041 _maxZoomFactor = 2.0; 00042 _padding = 60; 00043 00044 setCursor(CURSOR_NORMAL); 00045 updateViewport(); 00046 resetZoomPoint(); 00047 repaint(); 00048 } 00049 00050 /** Sets the displayed image. */ 00051 void 00052 ZImageView::setImage(QImage& img) 00053 { 00054 _image = img.copy(); 00055 updateViewport(); 00056 resetZoomPoint(); 00057 00058 if (isVisible()) { 00059 repaint(); 00060 } 00061 } 00062 00063 /** Draws the scaled image on the widget. */ 00064 void 00065 ZImageView::drawScaledImage() 00066 { 00067 if (!isVisible()) { 00068 return; 00069 } 00070 00071 QBrush background(QColor("#fdfdfd")); 00072 if (_image.isNull()) { 00073 QPainter p(this); 00074 p.fillRect(rect(), background); 00075 return; 00076 } 00077 00078 QRect sRect = rect(); 00079 QRect iRect = _image.rect(); 00080 QRect r = _view; 00081 00082 // Think of the _view as being overlaid on the image. The _view has the same 00083 // aspect ratio as the screen, so we cut the _view region out of the _image 00084 // and scale it to the screen dimensions and paint it. 00085 00086 // There is a slight catch in that the _view may be larger than the image in 00087 // one or both directions. In that case, we need to reduce the _view region 00088 // to lie within the image, then paint the background around it. Copying 00089 // a region from an image where the region is bigger than the image results 00090 // in the parts outside the image being black, which is not what we want. 00091 00092 // The view has the same aspect ratio as the screen, so the vertical and 00093 // horizontal scale factors will be equal. 00094 00095 double scaleFactor = double(sRect.width()) / double(_view.width()); 00096 00097 // Constrain r to lie entirely within the image. 00098 if (r.top() < 0) { 00099 r.setTop(0); 00100 } 00101 if (iRect.bottom() < r.bottom()) { 00102 r.setBottom(iRect.bottom()); 00103 } 00104 if (r.left() < 0) { 00105 r.setLeft(0); 00106 } 00107 if (iRect.right() < r.right()) { 00108 r.setRight(iRect.right()); 00109 } 00110 00111 // Figure out the size that the 'r' region will be when drawn to the screen. 00112 QSize scaleTo(int(double(r.width()) * scaleFactor), 00113 int(double(r.height()) * scaleFactor)); 00114 00115 /** Make a copy of the image so we don't ruin the original */ 00116 QImage i = _image.copy(); 00117 00118 /** Create a QPainter that draws directly on the copied image and call the 00119 * virtual function to draw whatever the subclasses need to on the image. */ 00120 QPainter painter; 00121 painter.begin(&i); 00122 paintImage(&painter); 00123 painter.end(); 00124 00125 /** Rescale the image copy */ 00126 i = i.copy(r).scaled(scaleTo, 00127 Qt::KeepAspectRatioByExpanding, 00128 Qt::SmoothTransformation); 00129 00130 int extraWidth = int(double(sRect.width() - i.width()) / 2.0); 00131 int extraHeight = int(double(sRect.height() - i.height()) / 2.0); 00132 00133 // We don't want to paint the background 00134 // because this isn't double buffered and that would flicker. 00135 // We could double buffer it, but that would cost ~3 MB of memory. 00136 00137 QPainter p(this); 00138 if (extraWidth > 0) { 00139 p.fillRect(0, 0, extraWidth, sRect.height(), background); 00140 p.fillRect(sRect.width() - extraWidth, 0, 00141 sRect.width(), sRect.height(), background); 00142 } 00143 00144 if (extraHeight > 0) { 00145 p.fillRect(0, 0, sRect.width(), extraHeight, background); 00146 p.fillRect(0, sRect.height() - extraHeight, 00147 sRect.width(), sRect.height(), background); 00148 } 00149 00150 // Finally, paint the image copy. 00151 p.drawImage(extraWidth, extraHeight, i); 00152 } 00153 00154 /** Updates the displayed viewport. */ 00155 void 00156 ZImageView::updateViewport(int screendx, int screendy) 00157 { 00158 /* The gist of this is to find the biggest and smallest possible viewports, 00159 * then use the _zoom factor to interpolate between them. Also pan the 00160 * viewport, but constrain each dimension to lie within the image or to be 00161 * centered if the image is too small in that direction. */ 00162 00163 QRect sRect = rect(); 00164 QRect iRect = _image.rect(); 00165 00166 float sw = float(sRect.width()); 00167 float sh = float(sRect.height()); 00168 float iw = float(iRect.width()); 00169 float ih = float(iRect.height()); 00170 00171 // Get the initial max and min sizes for the viewport. These won't have the 00172 // correct aspect ratio. They will actually be the least upper bound and 00173 // greatest lower bound of the set containing the screen and image rects. 00174 float maxw = float(std::max<int>(sRect.width(), iRect.width())) + _padding; 00175 float maxh = float(std::max<int>(sRect.height(), iRect.height())) + _padding; 00176 float minw = std::ceil(float(sRect.width()) / _maxZoomFactor); 00177 float minh = std::ceil(float(sRect.height()) / _maxZoomFactor); 00178 00179 // Now that we have the glb and the lub, we expand/shrink them until 00180 // the aspect ratio is that of the screen. 00181 float aspect = sw / sh; 00182 00183 // Fix the max rect. 00184 float newmaxh = maxh; 00185 float newmaxw = aspect * newmaxh; 00186 if (newmaxw < maxw) { 00187 newmaxw = maxw; 00188 newmaxh = maxw / aspect; 00189 } 00190 00191 // Fix the min rect. 00192 float newminh = minh; 00193 float newminw = aspect * newminh; 00194 if (minw < newminw) { 00195 newminw = minw; 00196 newminh = newminw / aspect; 00197 } 00198 00199 // Now interpolate between max and min. 00200 float vw = (1.0f - _zoom) * (newmaxw - newminw) + newminw; 00201 float vh = (1.0f - _zoom) * (newmaxh - newminh) + newminh; 00202 00203 _view.setWidth(int(vw)); 00204 _view.setHeight(int(vh)); 00205 00206 // Now pan the view 00207 00208 // Convert the pan delta from screen coordinates to view coordinates. 00209 float vdx = vw * (float(screendx) / sw); 00210 float vdy = vh * (float(screendy) / sh); 00211 00212 // Constrain the center of the viewport to the image rect. 00213 _desiredX = qBound(0.0f, _desiredX + vdx, iw); 00214 _desiredY = qBound(0.0f, _desiredY + vdy, ih); 00215 _view.moveCenter(QPoint(int(_desiredX), int(_desiredY))); 00216 00217 QPoint viewCenter = _view.center(); 00218 float vx = viewCenter.x(); 00219 float vy = viewCenter.y(); 00220 00221 // The viewport may be wider than the height and/or width. In that case, 00222 // center the view over the image in the appropriate directions. 00223 // 00224 // If the viewport is smaller than the image in either direction, then make 00225 // sure the edge of the viewport isn't past the edge of the image. 00226 00227 vdx = 0; 00228 vdy = 0; 00229 00230 if (iw <= vw) { 00231 vdx = (iw / 2.0f) - vx; // Center horizontally. 00232 } else { 00233 // Check that the edge of the view isn't past the edge of the image. 00234 float vl = float(_view.left()); 00235 float vr = float(_view.right()); 00236 if (vl < 0) { 00237 vdx = -vl; 00238 } else if (vr > iw) { 00239 vdx = iw - vr; 00240 } 00241 } 00242 00243 if (ih <= vh) { 00244 vdy = (ih / 2.0f) - vy; // Center vertically. 00245 } else { 00246 // Check that the edge of the view isn't past the edge of the image. 00247 float vt = float(_view.top()); 00248 float vb = float(_view.bottom()); 00249 if (vt < 0) { 00250 vdy = -vt; 00251 } else if (vb > ih) { 00252 vdy = ih - vb; 00253 } 00254 } 00255 00256 _view.translate(int(vdx), int(vdy)); 00257 } 00258 00259 /** Resets the zoom point back to the center of the viewport. */ 00260 void 00261 ZImageView::resetZoomPoint() 00262 { 00263 QPoint viewCenter = _view.center(); 00264 _desiredX = viewCenter.x(); 00265 _desiredY = viewCenter.y(); 00266 } 00267 00268 /** Handles repainting this widget by updating the viewport and drawing the 00269 * scaled image. */ 00270 void 00271 ZImageView::paintEvent(QPaintEvent*) 00272 { 00273 updateViewport(); 00274 drawScaledImage(); 00275 } 00276 00277 /** Sets the current zoom percentage to the given value and scrolls the 00278 * viewport to center the given point. */ 00279 void 00280 ZImageView::zoom(QPoint zoomAt, float pct) 00281 { 00282 _desiredX = zoomAt.x(); 00283 _desiredY = zoomAt.y(); 00284 zoom(pct); 00285 } 00286 00287 /** Sets the current zoom percentage to the given value. */ 00288 void 00289 ZImageView::zoom(float pct) 00290 { 00291 _zoom = qBound(0.0f, pct, 1.0f); 00292 repaint(); 00293 } 00294 00295 /** Zooms into the image by 10% */ 00296 void 00297 ZImageView::zoomIn() 00298 { 00299 zoom(_zoom + .1); 00300 } 00301 00302 /** Zooms away from the image by 10% */ 00303 void 00304 ZImageView::zoomOut() 00305 { 00306 zoom(_zoom - .1); 00307 } 00308 00309 /** Responds to the user pressing a mouse button. */ 00310 void 00311 ZImageView::mousePressEvent(QMouseEvent *e) 00312 { 00313 e->accept(); 00314 setCursor(CURSOR_MOUSE_PRESS); 00315 _mouseX = e->x(); 00316 _mouseY = e->y(); 00317 } 00318 00319 /** Responds to the user releasing a mouse button. */ 00320 void 00321 ZImageView::mouseReleaseEvent(QMouseEvent *e) 00322 { 00323 e->accept(); 00324 setCursor(CURSOR_NORMAL); 00325 updateViewport(); 00326 resetZoomPoint(); 00327 } 00328 00329 /** Responds to the user double-clicking a mouse button on the image. A left 00330 * double-click zooms in on the image and a right double-click zooms out. 00331 * Zooming is centered on the location of the double-click. */ 00332 void 00333 ZImageView::mouseDoubleClickEvent(QMouseEvent *e) 00334 { 00335 e->accept(); 00336 00337 QPoint center = rect().center(); 00338 int dx = e->x() - center.x(); 00339 int dy = e->y() - center.y(); 00340 updateViewport(dx, dy); 00341 resetZoomPoint(); 00342 00343 Qt::MouseButton btn = e->button(); 00344 if (btn == Qt::LeftButton) 00345 zoomIn(); 00346 else if (btn == Qt::RightButton) 00347 zoomOut(); 00348 } 00349 00350 /** Responds to the user moving the mouse. */ 00351 void 00352 ZImageView::mouseMoveEvent(QMouseEvent *e) 00353 { 00354 e->accept(); 00355 int dx = _mouseX - e->x(); 00356 int dy = _mouseY - e->y(); 00357 _mouseX = e->x(); 00358 _mouseY = e->y(); 00359 00360 updateViewport(dx, dy); 00361 if (0.001 <= _zoom) { 00362 repaint(); 00363 } 00364 } 00365 00366 void 00367 ZImageView::wheelEvent(QWheelEvent *e) 00368 { 00369 if (e->delta() > 0) { 00370 zoomIn(); 00371 } else { 00372 zoomOut(); 00373 } 00374 }