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