File indexing completed on 2025-03-09 03:52:05

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2007-02-11
0007  * Description : a tool to show image using an OpenGL interface.
0008  *
0009  * SPDX-FileCopyrightText: 2007-2008 by Markus Leuthold <kusi at forum dot titlis dot org>
0010  * SPDX-FileCopyrightText: 2008-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0011  *
0012  * SPDX-License-Identifier: GPL-2.0-or-later
0013  *
0014  * ============================================================ */
0015 
0016 #include "glviewerwidget.h"
0017 
0018 // Qt includes
0019 
0020 #include <QApplication>
0021 #include <QScreen>
0022 #include <QWindow>
0023 #include <QIcon>
0024 #include <QPointer>
0025 #include <QMimeType>
0026 #include <QMimeDatabase>
0027 #include <QStandardPaths>
0028 
0029 // KDE includes
0030 
0031 #include <klocalizedstring.h>
0032 
0033 // Local includes
0034 
0035 #include "glviewertimer.h"
0036 #include "dpluginaboutdlg.h"
0037 #include "digikam_debug.h"
0038 
0039 // OpenGL headers is not included automatically with ARM targets
0040 
0041 #ifdef Q_PROCESSOR_ARM
0042 #   include <GL/gl.h>
0043 #endif
0044 
0045 #ifndef GL_TEXTURE_RECTANGLE_ARB
0046 #   define GL_TEXTURE_RECTANGLE_ARB   0x84F5
0047 #endif
0048 
0049 #ifndef GL_TEXTURE_RECTANGLE_NV
0050 #   define GL_TEXTURE_RECTANGLE_NV    0x84F5
0051 #endif
0052 
0053 namespace DigikamGenericGLViewerPlugin
0054 {
0055 
0056 class Q_DECL_HIDDEN GLViewerWidget::Private
0057 {
0058 public:
0059 
0060     struct Cache
0061     {
0062         int              file_index = 0;
0063         GLViewerTexture* texture    = nullptr;
0064     };
0065 
0066 public:
0067 
0068     enum WheelAction
0069     {
0070         zoomImage,
0071         changeImage
0072     };
0073 
0074 public:
0075 
0076     explicit Private()
0077       :
0078         file_idx                (0),                    ///< index of picture to be displayed
0079 
0080         texture                 (nullptr),
0081         ratio_view_y            (0.0F),
0082         ratio_view_x            (0.0F),
0083         delta                   (0.0F),
0084         vertex_height           (0.0F),
0085         vertex_width            (0.0F),
0086         vertex_left             (0.0F),
0087         vertex_top              (0.0F),
0088         vertex_right            (0.0F),
0089         vertex_bottom           (0.0F),
0090         wheelAction             (zoomImage),
0091         firstImage              (true),
0092 
0093         /**
0094          * while zooming is performed, the image is downsampled to d->zoomsize. This seems to
0095          * be the optimal way for a PentiumM 1.4G, Nvidia FX5200. For a faster setup, this might
0096          * not be necessary anymore
0097          */
0098         zoomsize                (QSize(1024, 768)),
0099 
0100         /// load cursors for zooming and panning
0101         moveCursor              (QCursor(Qt::PointingHandCursor)),
0102         zoomCursor              (QCursor(QIcon::fromTheme(QLatin1String("zoom-in")).pixmap(64))),
0103 
0104         /// define zoomfactors for one zoom step
0105         zoomfactor_scrollwheel  (1.1F),
0106         zoomfactor_mousemove    (1.03F),
0107         zoomfactor_keyboard     (1.05F),
0108 
0109         /// get path of nullImage in case QImage can't load the image
0110         nullImage               (QIcon::fromTheme(QLatin1String("image-jpeg")).pixmap(256)),
0111 
0112         iface                   (nullptr),
0113         plugin                  (nullptr)
0114     {
0115         for (int i = 0 ; i < CACHESIZE ; ++i)
0116         {
0117             cache[i].file_index = 0;
0118             cache[i].texture    = nullptr;
0119         }
0120     }
0121 
0122     QStringList      files;
0123     unsigned int     file_idx;
0124     Cache            cache[CACHESIZE];
0125     GLViewerTexture* texture;
0126 
0127     float            ratio_view_y;
0128     float            ratio_view_x;
0129     float            delta;
0130     float            vertex_height;
0131     float            vertex_width;
0132     float            vertex_left;
0133     float            vertex_top;
0134     float            vertex_right;
0135     float            vertex_bottom;
0136 
0137     QPoint           startdrag;
0138     QPoint           previous_pos;
0139     WheelAction      wheelAction;
0140     bool             firstImage;
0141     QSize            zoomsize;
0142     QTimer           timerMouseMove;
0143     QCursor          moveCursor;
0144     QCursor          zoomCursor;
0145     float            zoomfactor_scrollwheel;
0146     float            zoomfactor_mousemove;
0147     float            zoomfactor_keyboard;
0148     QPixmap          nullImage;
0149     QSize            screenSize;
0150 
0151     DInfoInterface*  iface;
0152     DPlugin*         plugin;
0153 };
0154 
0155 GLViewerWidget::GLViewerWidget(DPlugin* const plugin, DInfoInterface* const iface,
0156                                const QList<QUrl>& myfiles, const QString& selectedImage)
0157     : QOpenGLWidget(),
0158       d            (new Private)
0159 {
0160     setAttribute(Qt::WA_DeleteOnClose);
0161 
0162     d->plugin       = plugin;
0163     d->iface        = iface;
0164 
0165     // Determine screen size for isReallyFullScreen
0166 
0167     QScreen* screen = qApp->primaryScreen();
0168 
0169     if (QWidget* const widget = qApp->activeWindow())
0170     {
0171         if (QWindow* const window = widget->windowHandle())
0172         {
0173             screen = window->screen();
0174         }
0175     }
0176 
0177     d->screenSize         = screen->size();
0178 
0179     // populate QStringList::d->files
0180 
0181     int foundNumber       = 0;
0182 
0183     Q_FOREACH (const QUrl& url, myfiles)
0184     {
0185         // find selected image in album in order to determine the first displayed image
0186         // in case one image was selected and the entire album was loaded
0187 
0188         QString s = url.toLocalFile();
0189 
0190         if (s == selectedImage)
0191         {
0192             qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "selected img" << selectedImage << "has idx=" << foundNumber;
0193             d->file_idx = foundNumber;
0194         }
0195 
0196         // only add images to d->files
0197 
0198         QString mimeTypeName = QMimeDatabase().mimeTypeForUrl(QUrl::fromLocalFile(s)).name();
0199         bool isImage         = mimeTypeName.contains(QString::fromLatin1("image"), Qt::CaseInsensitive);
0200 
0201         if (isImage)
0202         {
0203             d->files.append(s);
0204             foundNumber++;      // counter for searching the start image in case one image is selected
0205             qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << s << "type=" << mimeTypeName;
0206         }
0207     }
0208 
0209     qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << d->files.count() << "images loaded";
0210 
0211     showFullScreen(); // krazy:exclude=qmethods
0212 
0213     // let the cursor disappear after 2sec of inactivity
0214 
0215     connect(&d->timerMouseMove, SIGNAL(timeout()),
0216             this, SLOT(slotTimeoutMouseMove()));
0217 
0218     d->timerMouseMove.start(2000);
0219     setMouseTracking(true);
0220 
0221     // other initialisations
0222 
0223     d->wheelAction = GLViewerWidget::Private::changeImage;
0224 }
0225 
0226 GLViewerWidget::~GLViewerWidget()
0227 {
0228     for (int i = 0 ; i < CACHESIZE ; ++i)
0229     {
0230         d->cache[i].file_index = EMPTY;
0231         delete d->cache[i].texture;
0232     }
0233 
0234     delete d;
0235 }
0236 
0237 /**
0238  * \todo blending
0239  */
0240 void GLViewerWidget::initializeGL()
0241 {
0242     glEnable(GL_TEXTURE_RECTANGLE_ARB);
0243 
0244     // Clear The Background Color
0245     glClearColor(0.0, 0.0, 0.0, 1.0f);
0246 
0247     // Turn Blending On
0248     glEnable(GL_BLEND);
0249 
0250     // Blending Function For Translucency Based On Source Alpha Value
0251     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
0252 
0253     // Enable perspective vision
0254     glClearDepth(1.0f);
0255 
0256     // initialize cache
0257     for (int i = 0 ; i < CACHESIZE ; ++i)
0258     {
0259         d->cache[i].file_index = EMPTY;
0260         d->cache[i].texture    = new GLViewerTexture(d->iface, this);
0261     }
0262 }
0263 
0264 void GLViewerWidget::paintGL()
0265 {
0266     // this test has to be performed here since QWidget::width() is only updated now
0267 /*
0268     qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "enter paintGL: isReallyFullscreen=" << isReallyFullScreen();
0269 */
0270     // prepare 1st image
0271 
0272     if (d->firstImage && isReallyFullScreen())
0273     {
0274 /*
0275         qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "first image";
0276 */
0277         d->texture = loadImage(d->file_idx);
0278         d->texture->reset();
0279         downloadTexture(d->texture);
0280 /*
0281         qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "width=" << width();
0282 */
0283         glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
0284         glLoadIdentity();
0285         glTranslatef(0.0f, 0.0f, -5.0f);
0286         drawImage(d->texture);
0287 
0288         // trigger a redraw NOW. the user wants to see a picture as soon as possible
0289         // only load the second image after the first is displayed
0290 
0291         glFlush();
0292 
0293         // preload the 2nd image
0294 
0295         if (d->firstImage)
0296         {
0297             if (d->file_idx < (unsigned int)(d->files.count() - 1))
0298             {
0299                 loadImage(d->file_idx+1);
0300             }
0301 
0302             d->firstImage = false;
0303         }
0304     }
0305 
0306     if (!d->firstImage)
0307     {
0308 /*
0309         qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "width=" << width();
0310 */
0311         glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
0312         glLoadIdentity();
0313         glTranslatef(0.0f, 0.0f, -5.0f);
0314         drawImage(d->texture);
0315     }
0316 /*
0317     qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "exit paintGL";
0318 */
0319 }
0320 
0321 void GLViewerWidget::resizeGL(int w, int h)
0322 {
0323 /*
0324     qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "resizeGL,w=" << w;
0325 */
0326     glViewport(0, 0, (GLint)w, (GLint)h);
0327     glMatrixMode(GL_PROJECTION);
0328     glLoadIdentity();
0329 
0330     if (h > w)
0331     {
0332         d->ratio_view_x = 1.0;
0333         d->ratio_view_y = h / float(w);
0334     }
0335     else
0336     {
0337         d->ratio_view_x = w / float(h);
0338         d->ratio_view_y = 1.0;
0339     }
0340 
0341     glFrustum(-d->ratio_view_x, d->ratio_view_x, -d->ratio_view_y, d->ratio_view_y, 5, 5000.0);
0342     glMatrixMode(GL_MODELVIEW);
0343     glLoadIdentity();
0344 
0345     if (d->texture == nullptr)
0346     {
0347         return;
0348     }
0349 
0350     if (d->firstImage)
0351     {
0352         d->texture->setViewport(w, h);
0353     }
0354 }
0355 
0356 /**
0357  * render the image
0358  */
0359 void GLViewerWidget::drawImage(GLViewerTexture* const tex)
0360 {
0361 /*
0362     qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "enter drawImage: target=" << d->texture->textureId()
0363                                          << "dim=" << d->texture->height() << d->texture->width();
0364 */
0365     glBindTexture(GL_TEXTURE_RECTANGLE_NV, tex->textureId());
0366     glBegin(GL_QUADS);
0367     glTexCoord2f(0, 0);
0368     glVertex3f(tex->vertex_left(), tex->vertex_bottom(), 0);
0369 
0370     glTexCoord2f(tex->width(), 0);
0371     glVertex3f(d->texture->vertex_right(), tex->vertex_bottom(), 0);
0372 
0373     glTexCoord2f(tex->width(), tex->height());
0374     glVertex3f(tex->vertex_right(), tex->vertex_top(), 0);
0375 
0376     glTexCoord2f(0, tex->height());
0377     glVertex3f(tex->vertex_left(), tex->vertex_top(), 0);
0378     glEnd();
0379 }
0380 
0381 /**
0382  * Handle all keyboard events. All events which are not handled trigger
0383  * a help window.
0384  */
0385 void GLViewerWidget::keyPressEvent(QKeyEvent* e)
0386 {
0387     QPoint middlepoint;
0388 
0389     switch (e->key())
0390     {
0391         // next image
0392 
0393         case Qt::Key_N:
0394         case Qt::Key_Right:
0395         case Qt::Key_Down:
0396         case Qt::Key_PageDown:
0397         case Qt::Key_Space:
0398         {
0399             nextImage();
0400             break;
0401         }
0402 
0403         // previous image
0404 
0405         case Qt::Key_P:
0406         case Qt::Key_Left:
0407         case Qt::Key_Up:
0408         case Qt::Key_PageUp:
0409         {
0410             prevImage();
0411             break;
0412         }
0413 
0414         // rotate image
0415 
0416         case Qt::Key_R:
0417         {
0418             d->texture->rotate();
0419             downloadTexture(d->texture);
0420             update();
0421             break;
0422         }
0423 
0424         // terminate image viewer
0425 
0426         case Qt::Key_Escape:
0427         {
0428             // clean up: where does this have to be done?
0429 
0430             close();
0431             break;
0432         }
0433 
0434         // full screen
0435 
0436         case Qt::Key_F:
0437         {
0438             // according to QT documentation, showFullScreen() has some
0439             // serious issues on window managers that do not follow modern
0440             // post-ICCCM specifications
0441 
0442             if (isFullScreen())
0443             {
0444                 d->texture->reset();
0445                 showNormal();     // krazy:exclude=qmethods
0446             }
0447             else
0448             {
0449                 d->texture->reset();
0450                 showFullScreen(); // krazy:exclude=qmethods
0451             }
0452 
0453             break;
0454         }
0455 
0456         // reset size and redraw
0457 
0458         case Qt::Key_Z:
0459         {
0460             d->texture->reset(true);
0461             update();
0462 
0463             break;
0464         }
0465 
0466         // toggle permanent between "show next image" and "zoom" on mousewheel change
0467 
0468         case Qt::Key_C:
0469         {
0470             if (d->wheelAction == GLViewerWidget::Private::zoomImage)
0471             {
0472                 d->wheelAction = GLViewerWidget::Private::changeImage;
0473             }
0474             else
0475             {
0476                 d->wheelAction = GLViewerWidget::Private::zoomImage;
0477             }
0478 
0479             break;
0480         }
0481 
0482         // zoom in
0483 
0484         case Qt::Key_Plus:
0485         {
0486             middlepoint = QPoint(width() / 2, height() / 2);
0487 
0488             if (d->texture && d->texture->setNewSize(d->zoomsize))
0489             {
0490                 downloadTexture(d->texture); // load full resolution image
0491             }
0492 
0493             zoom(-1, middlepoint, d->zoomfactor_keyboard);
0494 
0495             break;
0496         }
0497 
0498         // zoom out
0499 
0500         case Qt::Key_Minus:
0501         {
0502             middlepoint = QPoint(width() / 2, height() / 2);
0503 
0504             if (d->texture && d->texture->setNewSize(d->zoomsize))
0505             {
0506                 downloadTexture(d->texture); // load full resolution image
0507             }
0508 
0509             zoom(1, middlepoint, d->zoomfactor_keyboard);
0510 
0511             break;
0512         }
0513 
0514         // zoom to original size
0515 
0516         case Qt::Key_O:
0517         {
0518             if (d->texture)
0519             {
0520                 d->texture->loadFullSize();
0521 
0522                 if (d->texture->setNewSize(QSize(0, 0)))
0523                 {
0524                     downloadTexture(d->texture); // load full resolution image
0525                 }
0526 
0527                 d->texture->zoomToOriginal();
0528             }
0529 
0530             update();
0531 
0532             break;
0533         }
0534 
0535         // toggle temporarily between "show next image" and "zoom" on mousewheel change
0536 
0537         case Qt::Key_Control:
0538         {
0539             if (d->wheelAction == GLViewerWidget::Private::zoomImage)
0540             {
0541                 // scrollwheel changes to the next image
0542 
0543                 d->wheelAction = GLViewerWidget::Private::changeImage;
0544             }
0545             else
0546             {
0547                 // scrollwheel does zoom
0548 
0549                 d->wheelAction = GLViewerWidget::Private::zoomImage;
0550                 setCursor(d->zoomCursor);
0551                 d->timerMouseMove.stop();
0552             }
0553 
0554             break;
0555         }
0556 
0557         // F1 show help dialog to enlighten the user
0558 
0559         case Qt::Key_F1:
0560         {
0561             QPointer<DPluginAboutDlg> help = new DPluginAboutDlg(d->plugin);
0562             help->exec();
0563             break;
0564         }
0565 
0566         default:
0567         {
0568             break;
0569         }
0570     }
0571 }
0572 
0573 void GLViewerWidget::keyReleaseEvent(QKeyEvent* e)
0574 {
0575     switch (e->key())
0576     {
0577         case Qt::Key_Plus:
0578         case Qt::Key_Minus:
0579         {
0580             if (!e->isAutoRepeat())
0581             {
0582                 unsetCursor();
0583 
0584                 if (d->texture && d->texture->setNewSize(QSize(0, 0)))
0585                 {
0586                     downloadTexture(d->texture); // load full resolution image
0587                 }
0588 
0589                 update();
0590             }
0591             else
0592             {
0593                 e->ignore();
0594             }
0595 
0596             break;
0597         }
0598 
0599         case Qt::Key_Control:
0600         {
0601             if (d->wheelAction == GLViewerWidget::Private::zoomImage)
0602             {
0603                 d->wheelAction = GLViewerWidget::Private::changeImage;
0604             }
0605             else
0606             {
0607                 d->wheelAction = GLViewerWidget::Private::zoomImage;
0608                 unsetCursor();
0609                 d->timerMouseMove.start(2000);
0610             }
0611 
0612             break;
0613         }
0614 
0615         default:
0616         {
0617             e->ignore();
0618             break;
0619         }
0620     }
0621 }
0622 
0623 /**
0624  * download texture to video memory
0625  */
0626 void GLViewerWidget::downloadTexture(GLViewerTexture* const tex)
0627 {
0628     glBindTexture(GL_TEXTURE_RECTANGLE_NV, tex->textureId());
0629     // glTexParameterf(GL_TEXTURE_RECTANGLE_NV, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER_ARB);
0630     // glTexParameterf(GL_TEXTURE_RECTANGLE_NV, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER_ARB);
0631 
0632     // uncomment the following line to enable flat shading of texels -> debugging
0633     // glTexParameterf(GL_TEXTURE_RECTANGLE_NV, GL_TEXTURE_MAG_FILTER,GL_NEAREST);
0634 
0635     // glTexImage2D(GL_TEXTURE_RECTANGLE_NV, 0, GL_RGBA, tex->width(), tex->height(), 0,
0636     //              GL_RGBA, GL_UNSIGNED_BYTE, tex->data());
0637 }
0638 
0639 /**
0640  * load d->files[file_index] into a texture object if it is not already cached
0641  * \param file_index index to QStringList d->files
0642  */
0643 GLViewerTexture* GLViewerWidget::loadImage(int file_index) const
0644 {
0645     int imod = file_index % CACHESIZE; //index for cache
0646 
0647     if (d->cache[imod].file_index == file_index)
0648     {
0649         // image is already cached
0650 
0651         qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "image" << file_index << "is already in cache@" << imod;
0652 
0653         return d->cache[imod].texture;
0654     }
0655     else
0656     {
0657         // image is net yet loaded
0658 
0659         QString f                 = d->files[file_index];
0660         qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "loading image" << f << "(idx=" << file_index << ") to cache@" << imod;
0661         d->cache[imod].file_index = file_index;
0662 
0663         // when loadImage is called the first time, the frame is not yet fullscreen
0664 
0665         QSize size = d->firstImage ? d->screenSize : QSize(width(), height());
0666 
0667         // handle non-loadable images
0668 
0669         if (!d->cache[imod].texture->load(f, size))
0670         {
0671             d->cache[imod].texture->load(d->nullImage.toImage());
0672         }
0673 
0674         d->cache[imod].texture->setViewport(size.width(), size.height());
0675 
0676         return d->cache[imod].texture;
0677     }
0678 }
0679 
0680 void GLViewerWidget::wheelEvent(QWheelEvent* e)
0681 {
0682     switch (d->wheelAction)
0683     {
0684         // mousewheel triggers zoom
0685 
0686         case GLViewerWidget::Private::zoomImage:
0687         {
0688             setCursor(d->zoomCursor);
0689             zoom(e->angleDelta().y(), e->position().toPoint(), d->zoomfactor_scrollwheel);
0690 
0691             break;
0692         }
0693 
0694         // mousewheel triggers image change
0695 
0696         case GLViewerWidget::Private::changeImage:
0697         {
0698             if      (e->angleDelta().y() < 0)
0699             {
0700                 nextImage();
0701             }
0702             else if (e->angleDelta().y() > 0)
0703             {
0704                 prevImage();
0705             }
0706 
0707             break;
0708         }
0709     }
0710 }
0711 
0712 void GLViewerWidget::mousePressEvent(QMouseEvent* e)
0713 {
0714     // begin zoom
0715     // scale down d->texture  for fast zooming
0716     // d->texture will be set to original size on mouse up
0717 
0718     if (d->texture && d->texture->setNewSize(d->zoomsize))
0719     {
0720         // load downsampled image
0721 
0722         downloadTexture(d->texture);
0723     }
0724 
0725     d->timerMouseMove.stop(); // user is something up to, therefore keep the cursor
0726 
0727     if (e->button() == Qt::LeftButton)
0728     {
0729         setCursor(d->moveCursor);
0730     }
0731 
0732     if (e->button() == Qt::RightButton)
0733     {
0734         setCursor(d->zoomCursor);
0735     }
0736 
0737     d->startdrag    = e->pos();
0738     d->previous_pos = e->pos();
0739 }
0740 
0741 void GLViewerWidget::mouseMoveEvent(QMouseEvent* e)
0742 {
0743     if      (e->buttons() == Qt::LeftButton)
0744     {
0745         // panning
0746 
0747         setCursor(d->moveCursor);
0748         QPoint diff  = e->pos()-d->startdrag;
0749         d->texture->move(diff);
0750         update();
0751         d->startdrag = e->pos();
0752     }
0753     else if (e->buttons() == Qt::RightButton)
0754     {
0755         int mdelta = 0;
0756 
0757         // zooming
0758         //
0759         // if mouse pointer reached upper or lower boder, special treatment in order
0760         // to keep zooming enabled in that special case
0761 
0762 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
0763 
0764         if (d->previous_pos.y() == e->position().toPoint().y())
0765         {
0766             if (e->position().toPoint().y() == 0)
0767 
0768 #else
0769 
0770         if (d->previous_pos.y() == e->y())
0771         {
0772             if (e->y() == 0)
0773 
0774 #endif
0775 
0776             {
0777                 // mouse pointer is at upper edge, therefore assume zoom in
0778 
0779                 mdelta = 1;
0780             }
0781             else
0782             {
0783                 // mouse pointer is as lower edge, therefore assume zoom out
0784 
0785                 mdelta = -1;
0786             }
0787         }
0788         else
0789         {
0790             // mouse pointer is in the middle of the screen, normal operation
0791 
0792 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
0793 
0794             mdelta = d->previous_pos.y() - e->position().toPoint().y();
0795 
0796 #else
0797 
0798             mdelta = d->previous_pos.y() - e->y();
0799 
0800 #endif
0801 
0802         }
0803 
0804         setCursor(d->zoomCursor);
0805         zoom(mdelta, d->startdrag, d->zoomfactor_mousemove);
0806         d->previous_pos = e->pos();
0807     }
0808     else
0809     {
0810         // no key is pressed while moving mouse
0811         // don't do anything if ctrl is pressed
0812 
0813         if (d->timerMouseMove.isActive())
0814         {
0815             // ctrl is not pressed, no zooming, therefore restore and hide cursor in 2 sec
0816 
0817             unsetCursor();
0818             d->timerMouseMove.start(2000);
0819         }
0820     }
0821 
0822     return;
0823 }
0824 
0825 void GLViewerWidget::mouseReleaseEvent(QMouseEvent*)
0826 {
0827     d->timerMouseMove.start(2000);
0828     unsetCursor();
0829 
0830     if (d->texture && d->texture->setNewSize(QSize(0, 0)))
0831     {
0832         // load full resolution image
0833 
0834         downloadTexture(d->texture);
0835     }
0836 
0837     update();
0838 }
0839 
0840 /**
0841  * a double click resets the view (zoom and move)
0842  */
0843 void GLViewerWidget::mouseDoubleClickEvent(QMouseEvent*)
0844 {
0845     d->texture->reset(true);
0846     update();
0847 }
0848 
0849 void GLViewerWidget::prevImage()
0850 {
0851 
0852 #ifdef PERFORMANCE_ANALYSIS
0853 
0854     GLViewerTimer timer;
0855 
0856 #endif
0857 
0858     if (d->file_idx > 0)
0859     {
0860         d->file_idx--;
0861     }
0862     else
0863     {
0864         return;
0865     }
0866 
0867 #ifdef PERFORMANCE_ANALYSIS
0868 
0869     timer.start();
0870 
0871 #endif
0872 
0873     d->texture = loadImage(d->file_idx);
0874     d->texture->reset(true);
0875 
0876 #ifdef PERFORMANCE_ANALYSIS
0877 
0878     timer.at("loadImage");
0879 
0880 #endif
0881 
0882     downloadTexture(d->texture);
0883 
0884 #ifdef PERFORMANCE_ANALYSIS
0885 
0886     timer.at("downloadTexture");
0887 
0888 #endif
0889 
0890     update();
0891 
0892 #ifdef PERFORMANCE_ANALYSIS
0893 
0894     timer.at("update");
0895 
0896 #endif
0897 
0898     //image preloading
0899 
0900     if (d->file_idx > 0)
0901     {
0902         loadImage(d->file_idx - 1);
0903     }
0904 }
0905 
0906 void GLViewerWidget::nextImage()
0907 {
0908 #ifdef PERFORMANCE_ANALYSIS
0909 
0910     GLViewerTimer timer;
0911 
0912 #endif
0913 
0914     if (d->file_idx < (unsigned int)(d->files.count() - 1))
0915     {
0916         d->file_idx++;
0917     }
0918     else
0919     {
0920         return;
0921     }
0922 
0923 #ifdef PERFORMANCE_ANALYSIS
0924 
0925     timer.start();
0926 
0927 #endif
0928 
0929     d->texture = loadImage(d->file_idx);
0930     d->texture->reset(true);
0931 
0932 #ifdef PERFORMANCE_ANALYSIS
0933 
0934     timer.at("loadImage");
0935 
0936 #endif
0937 
0938     downloadTexture(d->texture);
0939 
0940 #ifdef PERFORMANCE_ANALYSIS
0941 
0942     timer.at("downloadTexture");
0943 
0944 #endif
0945 
0946     update();
0947 
0948 #ifdef PERFORMANCE_ANALYSIS
0949 
0950     timer.at("updateGL");
0951 
0952 #endif
0953 
0954     //image preloading
0955     if (d->file_idx < ((unsigned int)d->files.count() - 1))
0956     {
0957         loadImage(d->file_idx + 1);
0958 
0959 #ifdef PERFORMANCE_ANALYSIS
0960 
0961         timer.at("preloading");
0962 
0963 #endif
0964 
0965     }
0966 }
0967 
0968 /**
0969  * \param mdelta delta of mouse movement:
0970  *                              mdelta>0: zoom in
0971  *                              mdelta<0: zoom out
0972  *                              mdelta=0: do nothing
0973  * \param pos position of mouse
0974  * \param factor zoom factor:scrollwheel needs a higher factor that right click mouse move. factor=1 -> no zoom
0975  */
0976 void GLViewerWidget::zoom(int mdelta, const QPoint& pos, float factor)
0977 {
0978     if (mdelta == 0)
0979     {
0980         // do nothing
0981         return;
0982     }
0983 
0984     if (mdelta > 0)
0985     {
0986         // multiplicator for zooming in
0987         d->delta = factor;
0988     }
0989 
0990     if (mdelta < 0)
0991     {
0992         // multiplicator for zooming out
0993         d->delta = 2.0 - factor;
0994     }
0995 
0996     d->texture->zoom(d->delta, pos);
0997     update();
0998 }
0999 
1000 /**
1001  * being called if user didn't move the mouse for longer than 2 sec
1002  */
1003 void GLViewerWidget::slotTimeoutMouseMove()
1004 {
1005     setCursor(Qt::BlankCursor);
1006 }
1007 
1008 /**
1009  * check if OpenGL engine is ready. This function is called from outside the widget.
1010  * If OpenGL doesn't work correctly, the widget can be destroyed
1011  * \return OGLstate::oglNoContext No OpenGl context could be retrieved
1012  * \return OGLstate::oglNoRectangularTexture GLGL_ARB_texture_rectangle is not supported
1013  * \return OGLstate::oglOK all is fine
1014  */
1015 OGLstate GLViewerWidget::getOGLstate() const
1016 {
1017     // No OpenGL context is found. Are the drivers ok?
1018 
1019     if (!isValid())
1020     {
1021         return oglNoContext;
1022     }
1023 
1024     // GL_ARB_texture_rectangle is not supported
1025 
1026     QString s = QString::fromLatin1(reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS)));
1027 
1028     if (!s.contains(QString::fromLatin1("GL_ARB_texture_rectangle"), Qt::CaseInsensitive))
1029     {
1030         return oglNoRectangularTexture;
1031     }
1032 
1033     // Everything is ok!
1034 
1035     return oglOK;
1036 }
1037 
1038 /**
1039  * QGLWidget::isFullscreen() returns true if the internal state is already true
1040  * but the actually displayed size is still windowed. isReallyFullscreen() returns the
1041  * value of the visible size.
1042  * \return true if (screenwidth == widgedwidth)
1043  */
1044 bool GLViewerWidget::isReallyFullScreen() const
1045 {
1046     return (width() == d->screenSize.width());
1047 }
1048 
1049 } // namespace DigikamGenericGLViewerPlugin
1050 
1051 #include "moc_glviewerwidget.cpp"