You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
kipi-plugins/kipi-plugins/imageviewer/viewerwidget.cpp

691 lines
17 KiB

/***************************************************************************
* Copyright (C) 2007 by Markus Leuthold *
* <kusi (+at) forum.titlis.org> *
* *
* 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 <tqgl.h>
#include <iostream>
#include <tqlabel.h>
#include <tdelocale.h>
#include <tqdragobject.h>
#include <tqbitmap.h>
#include <kdebug.h>
#include <kiconloader.h>
#include <kstandarddirs.h>
#include <tdemessagebox.h>
// 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;i<CACHESIZE;i++) {
cache[i].file_index=EMPTY;
cache[i].texture=new Texture(kipiInterface);
}
// define zoomfactors for one zoom step
zoomfactor_scrollwheel = 1.1;
zoomfactor_mousemove = 1.03;
zoomfactor_keyboard = 1.05;
// load cursors for zooming and panning
TQString file;
file = locate( "data", "kipiplugin_imageviewer/pics/zoom.png" );
zoomCursor=TQCursor(file);
file = locate( "data", "kipiplugin_imageviewer/pics/hand.png" );
moveCursor=TQCursor(file);
// get path of nullImage in case TQImage can't load the image
nullImage = locate( "data", "kipiplugin_imageviewer/pics/nullImage.png" );
showFullScreen();
// let the cursor dissapear after 2sec of inactivity
connect( &timerMouseMove, TQ_SIGNAL(timeout()),this, TQ_SLOT( timeoutMouseMove()) );
timerMouseMove.start(2000);
setMouseTracking(true);
// while zooming is performed, the image is downsampled to zoomsize. This seems to
// be the optimal way for a PentiumM 1.4G, Nvidia FX5200. For a faster setup, this might
// not be necessary anymore
zoomsize=TQSize(1024,768);
// other initialisations
wheelAction = changeImage;
}
/*!
\fn ViewerWidget::initializeGL()
\todo blending
*/
void ViewerWidget::initializeGL() {
glEnable(GL_TEXTURE_RECTANGLE_ARB);
// Clear The Background Color
glClearColor(0.0, 0.0, 0.0, 1.0f);
// Turn Blending On
glEnable(GL_BLEND);
// Blending Function For Translucency Based On Source Alpha Value
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
// Enable perspective vision
glClearDepth(1.0f);
// Generate texture
glGenTextures(1, tex);
}
/*!
\fn ViewerWidget::paintGL()
*/
void ViewerWidget::paintGL() {
//prepare 1st image
//this test has to be performed here since TQWidget::width() is only updated now
if (firstImage) {
texture=loadImage(file_idx);
texture->reset();
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;
}