/*************************************************************************** * Copyright (C) 2007 by Markus Leuthold * * * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. * ***************************************************************************/ #undef PERFORMANCE_ANALYSIS // global includes #include #include #include #include #include #include #include #include #include #include // local includes. #include "viewerwidget.h" #include "texture.h" #include "help.h" #ifdef PERFORMANCE_ANALYSIS #include "timer.h" #endif #include "viewerwidget.moc" // using namespace std; using namespace KIPIviewer; ViewerWidget::ViewerWidget(KIPI::Interface* i) { kipiInterface = i; KIPI::ImageCollection selection = kipiInterface->currentSelection(); KIPI::ImageCollection album = kipiInterface->currentAlbum(); KURL::List myfiles; //pics which are displayed in imageviewer TQString selectedImage; //selected pic in hostapp int foundNumber=0; file_idx=0; //index of picture to be displayed if ( selection.images().count()==0 ) { kdDebug(51000) << "no image selected, load entire album" << endl; myfiles = album.images(); } else if ( selection.images().count()==1 ) { kdDebug(51000) << "one image selected, load entire album and start with selected image" << endl; selectedImage = selection.images().first().path(); myfiles = album.images(); } else if ( selection.images().count()>1 ) { kdDebug(51000) << "load " << selection.images().count() << " selected images" << endl; myfiles = selection.images(); } // populate TQStringList::files for(KURL::List::Iterator it = myfiles.begin(); it != myfiles.end();it++) { // find selected image in album in order to determine the first displayed image // in case one image was selected and the entire album was loaded TQString s = (*it).path(); if ( s==selectedImage ) { kdDebug(51000) << "selected img " << selectedImage << " has idx=" << foundNumber << endl; file_idx=foundNumber; } // only add images to files KMimeType::Ptr type = KMimeType::findByURL(s); bool isImage=type->name().find("image")>=0; if ( isImage ) { files.append(s); foundNumber++; //counter for searching the start image in case one image is selected kdDebug(51000) << s << " type=" << type->name() << endl; } } firstImage=true; kdDebug(51000) << files.count() << "images loaded" << endl; // initialize cache for(int i=0;ireset(); downloadTex(texture); } glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glLoadIdentity(); glTranslatef(0.0f,0.0f,-5.0f); drawImage(texture); //preload the 2nd image if (firstImage) { if (file_idx<(files.count()-1)) { loadImage(file_idx+1); } firstImage=false; } } /*! \fn ViewerWidget::resizeGL(int w, int h) */ void ViewerWidget::resizeGL(int w, int h) { glViewport(0, 0, (GLint)w, (GLint)h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); if (h>w) { ratio_view_x=1.0; ratio_view_y=h/float(w); } else { ratio_view_x=w/float(h); ratio_view_y=1.0; } glFrustum( -ratio_view_x, ratio_view_x, -ratio_view_y, ratio_view_y,5, 5000.0 ); glMatrixMode( GL_MODELVIEW ); glLoadIdentity(); if (!firstImage) { texture->setViewport(w,h); } } /*! \fn ViewerWidget::drawImage(Texture * texture) \brief render the image */ void ViewerWidget::drawImage(Texture * texture) { // cout << "enter drawImage: target=" << texture->texnr() << " dim=" << texture->height() << " " << texture->width() << endl; glBindTexture(GL_TEXTURE_RECTANGLE_NV, texture->texnr()); glBegin(GL_QUADS); glTexCoord2f(0, 0); glVertex3f(texture->vertex_left(), texture->vertex_bottom(), 0); glTexCoord2f(texture->width(), 0); glVertex3f(texture->vertex_right(), texture->vertex_bottom(), 0); glTexCoord2f(texture->width(), texture->height()); glVertex3f(texture->vertex_right(), texture->vertex_top(), 0); glTexCoord2f(0, texture->height()); glVertex3f(texture->vertex_left(), texture->vertex_top(), 0); glEnd(); } /*! \fn ViewerWidget::keyPressEvent(TQKeyEvent *k) Handle all keyboard events. All events which are not handled trigger a help window. */ void ViewerWidget::keyPressEvent(TQKeyEvent *k) { TQPoint middlepoint; switch (k->key()) { // next image case Key_N: case Key_Right: case Key_Down: case Key_PageDown: case Key_Space: nextImage(); break; // previous image case Key_P: case Key_Left: case Key_Up: case Key_PageUp: prevImage(); break; // rotate image case Key_R: texture->rotate(); downloadTex(texture); updateGL(); break; // terminate image viewer case Key_Escape: // clean up: where does this have to be done? close(true); break; // full screen case Key_F: // according to QT documentation, showFullScreen() has some // serious issues on window managers that do not follow modern // post-ICCCM specifications if (isFullScreen()) { texture->reset(); showNormal(); } else { texture->reset(); showFullScreen(); } break; // reset size and redraw case Key_Z: texture->reset(); updateGL(); break; // toggle permanent between "show next image" and "zoom" on mousewheel change case Key_C: if (wheelAction==zoomImage) wheelAction=changeImage; else wheelAction=zoomImage; break; // zoom in case Key_Plus: middlepoint = TQPoint(width()/2,height()/2); if (texture->setSize( zoomsize )) { downloadTex(texture); //load full resolution image }; zoom(-1, middlepoint, zoomfactor_keyboard); break; // zoom out case Key_Minus: middlepoint = TQPoint(width()/2,height()/2); if (texture->setSize( zoomsize )) downloadTex(texture); //load full resolution image zoom(1, middlepoint, zoomfactor_keyboard); break; // zoom to original size case Key_O: texture->zoomToOriginal(); updateGL(); break; // toggle temorarily between "show next image" and "zoom" on mousewheel change case Key_Control: if (wheelAction==zoomImage) //scrollwheel changes to the next image wheelAction=changeImage; else { //scrollwheel does zoom wheelAction=zoomImage; setCursor (zoomCursor); timerMouseMove.stop(); } break; //do noting, don't trigger the help dialog case Key_Shift: break; //key is not bound to any action, therefore show help dialog to enlighten the user default: TQDialog * h = new HelpDialog(0,0,TQt::WStyle_StaysOnTop); h->show(); } } /*! \fn ViewerWidget::keyReleaseEvent ( TQKeyEvent * e ) */ void ViewerWidget::keyReleaseEvent ( TQKeyEvent * e ) { switch (e->key()) { case Key_Plus: case Key_Minus: if (!e->isAutoRepeat()) { unsetCursor(); if (texture->setSize(TQSize(0,0))) { downloadTex(texture); //load full resolution image } updateGL(); } else e->ignore(); break; case Key_Control: if (wheelAction==zoomImage) wheelAction=changeImage; else wheelAction=zoomImage; unsetCursor(); timerMouseMove.start(2000); break; default: e->ignore(); break; } } /*! \fn ViewerWidget::downloadTex(Texture * tex) download texture to video memory */ void ViewerWidget::downloadTex(Texture * tex) { glBindTexture(GL_TEXTURE_RECTANGLE_NV, tex->texnr()); // glTexParameterf(GL_TEXTURE_RECTANGLE_NV, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER_ARB); // glTexParameterf(GL_TEXTURE_RECTANGLE_NV, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER_ARB); // uncomment the following line to enable flat shading of texels -> debugging // glTexParameterf(GL_TEXTURE_RECTANGLE_NV, GL_TEXTURE_MAG_FILTER,GL_NEAREST); glTexImage2D( GL_TEXTURE_RECTANGLE_NV, 0, GL_RGBA, tex->width(), tex->height(), 0,GL_RGBA, GL_UNSIGNED_BYTE, tex->data()); } /*! \fn ViewerWidget::loadImage(int file_index) \param file_index index to TQStringList files load files[file_index] into a texture object if it is not already cached */ Texture * ViewerWidget::loadImage(int file_index) { int imod=file_index%CACHESIZE; //index for cache if (cache[imod].file_index==file_index){ //image is already cached kdDebug(51000) << "image " << file_index << " is already in cache@" << imod << endl; return cache[imod].texture; } else { // image is net yet loaded TQString f = files[file_index]; kdDebug(51000) << "loading image " << f << "(idx=" << file_index << ") to cache@" << imod << endl; cache[imod].file_index=file_index; // handle non-loadable images if (!cache[imod].texture->load(f,TQSize(width(),height()),tex[0])) { cache[imod].texture->load(nullImage,TQSize(width(),height()),tex[0]); } cache[imod].texture->setViewport(width(),height()); return cache[imod].texture; } } /*! \fn ViewerWidget::wheelEvent ( TQWheelEvent * e ) */ void ViewerWidget::wheelEvent ( TQWheelEvent * e ) { switch(wheelAction) { // mousewheel triggers zoom case zoomImage: setCursor(zoomCursor); zoom(e->delta(), e->pos(), zoomfactor_scrollwheel); break; // mousewheel triggers image change case changeImage: if (e->delta() < 0) nextImage(); else prevImage(); break; } } /*! \fn ViewerWidget::mousePressEvent ( TQMouseEvent * e ) */ void ViewerWidget::mousePressEvent ( TQMouseEvent * e ) { // begin zoom // scale down texture for fast zooming // texture will be set to original size on mouse up if (texture->setSize( zoomsize )) { //load downsampled image downloadTex(texture); } timerMouseMove.stop(); //user is something up to, therefore keep the cursor if ( e->button() == LeftButton ) { setCursor (moveCursor); //defined in constructor } if ( e->button() == RightButton ) { setCursor (zoomCursor); } startdrag=e->pos(); previous_pos=e->pos(); } /*! \fn ViewerWidget::mouseMoveEvent ( TQMouseEvent * e ) */ void ViewerWidget::mouseMoveEvent ( TQMouseEvent * e ) { if ( e->state() == LeftButton ) { //panning TQPoint diff=e->pos()-startdrag; texture->move(diff); updateGL(); startdrag=e->pos(); } else if ( e->state() == RightButton ) { //zooming zoom(previous_pos.y()-e->y(), startdrag, zoomfactor_mousemove ); previous_pos=e->pos(); } else { //no key is pressed while moving mouse //don't do anything if ctrl is pressed if (timerMouseMove.isActive()) { //ctrl is not pressed, no zooming, therefore restore and hide cursor in 2 sec unsetCursor(); timerMouseMove.start(2000); } } return; } /*! \fn ViewerWidget::prevImage() */ void ViewerWidget::prevImage() { #ifdef PERFORMANCE_ANALYSIS Timer timer; #endif if (file_idx>0) file_idx--; else return; #ifdef PERFORMANCE_ANALYSIS timer.start(); #endif texture = loadImage(file_idx); texture->reset(); #ifdef PERFORMANCE_ANALYSIS timer.at("loadImage"); #endif downloadTex(texture); #ifdef PERFORMANCE_ANALYSIS timer.at("downloadTex"); #endif updateGL(); #ifdef PERFORMANCE_ANALYSIS timer.at("updateGL"); #endif //image preloading if (file_idx>0) loadImage(file_idx-1); } /*! \fn ViewerWidget::nextImage() */ void ViewerWidget::nextImage() { #ifdef PERFORMANCE_ANALYSIS Timer timer; #endif if (file_idx<(files.count()-1)) file_idx++; else return; #ifdef PERFORMANCE_ANALYSIS timer.start(); #endif texture = loadImage(file_idx); texture->reset(); #ifdef PERFORMANCE_ANALYSIS timer.at("loadImage"); #endif downloadTex(texture); #ifdef PERFORMANCE_ANALYSIS timer.at("downloadTex"); #endif updateGL(); #ifdef PERFORMANCE_ANALYSIS timer.at("updateGL"); #endif //image preloading if (file_idx<(files.count()-1)) { loadImage(file_idx+1); #ifdef PERFORMANCE_ANALYSIS timer.at("preloading"); #endif } } /*! \fn ViewerWidget::zoom(int mdelta, TQPos pos) \param mdelta delta of mouse movement: mdelta>0: zoom in mdelta<0: zoom out mdelta=0: do nothing \param pos position of mouse \param factor zoom factor:scrollwheel needs a higher factor that right click mouse move. factor=1 -> no zoom */ void ViewerWidget::zoom(int mdelta, TQPoint pos, float factor) { if (mdelta==0) { //do nothing return; } if (mdelta > 0) { //multiplicator for zooming in delta=factor; } if (mdelta < 0) { //multiplicator for zooming out delta=2.0-factor; } texture->zoom(delta,pos); updateGL(); } /*! \fn ViewerWidget::mouseDoubleClickEvent(TQMouseEvent * e ) a double click resets the view (zoom and move) */ void ViewerWidget::mouseDoubleClickEvent(TQMouseEvent * ) { texture->reset(); updateGL(); } /*! \fn ViewerWidget::mouseReleaseEvent(TQMouseEvent * e) */ void ViewerWidget::mouseReleaseEvent(TQMouseEvent * ) { timerMouseMove.start(2000); unsetCursor(); if (texture->setSize(TQSize(0,0))) { //load full resolution image downloadTex(texture); } updateGL(); } /*! \fn ViewerWidget::timeoutMouseMove() being called if user didn't move the mouse for longer than 2 sec */ void ViewerWidget::timeoutMouseMove() { setCursor (TQCursor (blankCursor)); } /*! \fn ViewerWidget::getOGLstate() check if OpenGL engine is ready. This function is called from outside the widget. If OpenGL doen't work correctly, the widget can be destroyed \return OGLstate::oglNoContext No OpenGl context could be retrieved \return OGLstate::oglNoRectangularTexture GLGL_ARB_texture_rectangle is not supported \return OGLstate::oglOK all is fine */ OGLstate ViewerWidget::getOGLstate() { //no OpenGL context is found. Are the drivers ok? if ( !isValid() ) { return oglNoContext; } //GL_ARB_texture_rectangle is not supported TQString s = TQString ( ( char* ) glGetString ( GL_EXTENSIONS ) ); if ( !s.contains ( "GL_ARB_texture_rectangle",false ) ) { return oglNoRectangularTexture; } //everything is ok! return oglOK; }