/***************************************************************************
                          sq_glwidget.cpp  -  description
                             -------------------
    begin                : Mon Mar 15 2004
    copyright            : (C) 2004 by Baryshev Dmitry
    email                : ksquirrel.iv@gmail.com
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <tqeventloop.h>
#include <tqstringlist.h>
#include <tqfileinfo.h>
#include <tqdatetime.h>
#include <tqlabel.h>
#include <tqtimer.h>
#include <tqdatetime.h>
#include <tqslider.h>
#include <tqlabel.h>

#include <tdeapplication.h>
#include <tdeaction.h>
#include <kcursor.h>
#include <kstandarddirs.h>
#include <kstatusbar.h>
#include <tdefiledialog.h>
#include <tdeglobal.h>
#include <tdelocale.h>
#include <kstringhandler.h>
#include <tdemessagebox.h>
#include <kdebug.h>
#include <tdeio/job.h>
#include <tdetempfile.h>
#include <twin.h>
#include <ktabbar.h>

#include <cmath>
#include <cstdlib>

#ifndef KSQUIRREL_PART
#include "ksquirrel.h"
#include "sq_widgetstack.h"
#include "sq_glview.h"
#include "sq_fileiconview.h"
#include "sq_diroperator.h"
#include "sq_hloptions.h"
#include "sq_previewwidget.h"
#include "sq_tabopendialog.h"
#endif

#include "sq_popupmenu.h"
#include "sq_libraryhandler.h"
#include "sq_config.h"
#include "sq_glwidget_helpers.h"
#include "sq_glwidget.h"
#include "sq_glu.h"
#include "sq_codecsettings.h"
#include "sq_glselectionpainter.h"
#include "sq_utils.h"
#include "sq_errorstring.h"
#include "fmt_filters.h"

#include <ksquirrel-libs/fileio.h>
#include <ksquirrel-libs/fmt_codec_base.h>
#include <ksquirrel-libs/fmt_utils.h>
#include <ksquirrel-libs/error.h>

#ifdef SQ_HAVE_KEXIF
#include <libkexif/kexifdata.h>
#endif

// is it enough ?
#define SQ_FLOAT_SIGMA 1e-5

#define ISFLOAT(a,b) (fabs(a - b) < SQ_FLOAT_SIGMA)
#define ISFLOAT1(a) ISFLOAT(a, 1.0)
#define ISFLOAT0(a) ISFLOAT(a, 0.0)

#define SQ_TAB_TEXT_LENGTH 30

/* ***************************************************************************************** */

SQ_GLWidget * SQ_GLWidget::m_instance = 0;

static const int timer_delay_file = 5;
static const double rad_const = 3.14159265358979323846 / 180.0;

static const float SQ_WINDOW_BACKGROUND_POS = -1000.0;
static const float SQ_IMAGE_CHECKER_POS =     -999.0;
static const float SQ_FIRST_FRAME_POS =       -998.0;
static const float SQ_MARKS_POS =             -997.0;
static const float SQ_FIRST_TILE_LAYER =      -995.0;
static const float SQ_ONSCREEN_LAYER =      10000.0;

/* ***************************************************************************************** */

SQ_GLWidget::SQ_GLWidget(TQWidget *parent, const char *name) : TQGLWidget(parent, name)
{
    kdDebug() << "+SQ_GLWidget" << endl;

    // init all variables...
    m_instance = this;

#ifdef KSQUIRREL_PART
    connect(&t_glv, TQ_SIGNAL(message(const TQString &)), this, TQ_SIGNAL(message(const TQString &)));
#endif

    zoomMenu = 0;
    selectionMenu = 0;
    images = 0;
    parts_broken = 0;

    ac = new TDEActionCollection(this, this, "GLWidget actionCollection");
    changed = blocked = decoded = reset_mode = false;
    movetype = -1;
    buffer = new RGBA [512 * 512];
    zoomFactor = 1.0;
    old_id = -1;
    menu = new TQPopupMenu(this);
    hackResizeGL = false;
    lastCopy = KURL::fromPathOrURL("/");
    oldZoom = -1;

    percentsLabel = new TQLabel(this);
    percentsLabel->move(4, 4);
    percentsLabel->hide();

    tabold = tab = &taborig;

    tmp = new KTempFile;
    tmp->setAutoDelete(true);
    tmp->close();

    SQ_Config::instance()->setGroup("GL view");
    zoom_type = SQ_Config::instance()->readNumEntry("zoom type", 2);
    linear = SQ_Config::instance()->readBoolEntry("zoom_nice", true);

    // load background for transparent image
    BGquads = TQImage(locate("data", "images/checker.png"));

    if(BGquads.isNull())
    {
        BGquads = TQImage(32, 32, 32);
        BGquads.setAlphaBuffer(true);
        BGquads.fill(0);
    }

    bindChecker = true;

    zoomfactor = SQ_Config::instance()->readNumEntry("zoom", 25);
    movefactor = SQ_Config::instance()->readNumEntry("move", 5);
    rotatefactor = SQ_Config::instance()->readNumEntry("angle", 90);

    // load cursors
    setCursor(KCursor::arrowCursor());

    setFocusPolicy(TQWidget::WheelFocus);
    setAcceptDrops(true);

    // popup menu with image pages
    images = new TDEPopupMenu;
    images->setCheckable(true);

    // create actions
    createActions();

    // create toolbars
    createToolbar();

    // create tickmarks
    createMarks();

    initAccelsAndMenu();

    enableActions(false);

    KCursor::setAutoHideCursor(this, true);
    KCursor::setHideCursorDelay(2500);

#ifndef KSQUIRREL_PART
    timer_prev = new TQTimer(this);
    timer_next = new TQTimer(this);

    TQ_CHECK_PTR(timer_prev);
    TQ_CHECK_PTR(timer_next);

    connect(timer_prev, TQ_SIGNAL(timeout()), SQ_WidgetStack::instance(), TQ_SLOT(emitPreviousSelected()));
    connect(timer_next, TQ_SIGNAL(timeout()), SQ_WidgetStack::instance(), TQ_SLOT(emitNextSelected()));
#endif

    timer_anim = new TQTimer(this);

    TQ_CHECK_PTR(timer_anim);

    connect(timer_anim, TQ_SIGNAL(timeout()), this, TQ_SLOT(slotAnimateNext()));

    connect(images, TQ_SIGNAL(activated(int)), this, TQ_SLOT(slotSetCurrentImage(int)));
    connect(images, TQ_SIGNAL(aboutToHide()), this, TQ_SLOT(slotImagesHidden()));
    connect(images, TQ_SIGNAL(aboutToShow()), this, TQ_SLOT(slotImagesShown()));

    gls = new SQ_GLSelectionPainter(this);
}

SQ_GLWidget::~SQ_GLWidget()
{
    kdDebug() << "-SQ_GLWidget" << endl;

    delete gls;

    delete parts_broken;

    removeCurrentTabs();

    delete selectionMenu;
    delete zoomMenu;
    delete images;
    delete [] buffer;
    delete tmp;
}

// Initialize OpenGL context. Used internally by TQGLWidget.
void SQ_GLWidget::initializeGL()
{
    setClearColor();
    glClearDepth(1.0);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_ALPHA_TEST);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glShadeModel(GL_FLAT);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glMatrixMode(GL_MODELVIEW);

    // finally, clear
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // create "broken" image
    initBrokenImage();

    initMarks();
}

// Resize OpenGL context. Used internally by TQGLWidget.
void SQ_GLWidget::resizeGL(int width, int height)
{
    gls->setSourceSize(width, height);

    // set new viewport
    glViewport(0, 0, width, height);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    // O(0,0) will be in the center of the window
    glOrtho(-width/2, width/2, -height/2, height/2, 0.1f, 10000.0);

    // camera setup
    SQ_GLU::gluLookAt(0,0,1, 0,0,0, 0,1,0);
    glMatrixMode(GL_MODELVIEW);

    if(decoded && !hackResizeGL)
        slotZoomIfLess();

    hackResizeGL = false;
}

/*
 *  Fill a w x h region with texture. Generate texture if needed.
 */
void SQ_GLWidget::draw_background(void *bits, unsigned int *tex, int dim, GLfloat w, GLfloat h, bool &bind, bool deleteOld)
{
    float half_w, half_h;

    half_w = w / 2.0;
    half_h = h / 2.0;

    // bind texture ?
    if(bind)
    {
        // delete old texture before binding ?
        if(deleteOld)
            glDeleteTextures(1, tex);

        // generate and bind texture
        glGenTextures(1, tex);
        glBindTexture(GL_TEXTURE_2D, *tex);

        // setup parameters
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

        // assign image to texture
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dim, dim, 0, GL_RGBA, GL_UNSIGNED_BYTE, bits);

        bind = false;
    }
    else
        glBindTexture(GL_TEXTURE_2D, *tex);

    // draw
    glBegin(GL_QUADS);
        glTexCoord2f(0.0, 0.0);                          glVertex2f(-half_w, half_h);
        glTexCoord2f(w/(GLfloat)dim, 0.0);               glVertex2f(half_w, half_h);
        glTexCoord2f(w/(GLfloat)dim, h/(GLfloat)dim);    glVertex2f(half_w, -half_h);
        glTexCoord2f(0.0, h/(GLfloat)dim);               glVertex2f(-half_w, -half_h);
    glEnd();
}

// Redraw OpenGL context. Used internally by TQGLWidget.
void SQ_GLWidget::paintGL()
{
    int z;

    // clear
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    if(gls->valid())
    {
        matrix_push();
        matrix_pure_reset();
        TQPoint p = gls->center();

        // move to selection center
        MATRIX_X = p.x();
        MATRIX_Y = p.y();
        write_gl_matrix();

        gls->draw();

        matrix_pop();
        write_gl_matrix();
    }

    glEnable(GL_TEXTURE_2D);

    // draw window background ?
    SQ_Config::instance()->setGroup("GL view");
    if(SQ_Config::instance()->readNumEntry("GL view background type", 1) == 2)
    {
        static bool del = false;
        matrix_push();
        matrix_pure_reset();
        matrix_move_z(SQ_WINDOW_BACKGROUND_POS);
        draw_background(BGpixmap.bits(), &texPixmap, BGpixmap.width(), width(), height(), changed, del);
        del = true;
        matrix_pop();
        write_gl_matrix();
    }

    // draw image
    if(!reset_mode && decoded)
    {
        SQ_Config::instance()->setGroup("GL view");

        fmt_image *im = &tab->finfo.image[tab->current];

        // if the image is transparent, and we should draw background for image
        if(im->hasalpha && SQ_Config::instance()->readBoolEntry("alpha_bkgr", true))
        {
            GLfloat w = (float)im->w / 2.0, h = (float)im->h / 2.0;

            static const GLdouble eq[4][4] =
            {
                {0.0, 1.0, 0.0, 0.0},
                {1.0, 0.0, 0.0, 0.0},
                {0.0, -1.0, 0.0, 0.0},
                {-1.0, 0.0, 0.0, 0.0}
            };

            // we will draw background for transparent image (quads) within
            // entire window, and cut off useless regions with clip planes
            glPushMatrix();
            glTranslatef(-w, -h, 0.0);
            glClipPlane(GL_CLIP_PLANE0, eq[0]);
            glClipPlane(GL_CLIP_PLANE1, eq[1]);
            glEnable(GL_CLIP_PLANE0);
            glEnable(GL_CLIP_PLANE1);
            glPopMatrix();
            glPushMatrix();
            glTranslatef(w, h, 0.0);
            glClipPlane(GL_CLIP_PLANE2, eq[2]);
            glClipPlane(GL_CLIP_PLANE3, eq[3]);
            glEnable(GL_CLIP_PLANE2);
            glEnable(GL_CLIP_PLANE3);
            glPopMatrix();

            // draw background
            matrix_push();
            matrix_pure_reset();
            matrix_move_z(SQ_IMAGE_CHECKER_POS);
            draw_background(BGquads.bits(), &texQuads, 32, width(), height(), bindChecker, !bindChecker);
            matrix_pop();
            write_gl_matrix();

            // don't need planes any more...
            glDisable(GL_CLIP_PLANE3);
            glDisable(GL_CLIP_PLANE2);
            glDisable(GL_CLIP_PLANE1);
            glDisable(GL_CLIP_PLANE0);
        }

        matrix_move_z(SQ_FIRST_FRAME_POS);

        Parts *pt = tab->broken ? parts_broken : &tab->parts[tab->current];

        // draw current image
        int toy = pt->tilesy.size();
        int tox = pt->tilesx.size();
        for(z = 0;z < toy;z++)
            if(glIsList(pt->m_parts[z * tox].list))
                glCallList(pt->m_parts[z * tox].list);

        // draw tickmarks ("broken" image won't have tickmarks)
        if(!tab->broken && marks && SQ_Config::instance()->readBoolEntry("marks", true))
        {
            GLfloat zum = getZoom();
            GLfloat x = fabsf(pt->m_parts[0].x1) * zum, y = pt->m_parts[0].y1 * zum;
            GLfloat X = MATRIX_X, Y = MATRIX_Y;

            if(x < 0.0)
                x = -x;

            const GLfloat ly = y+16, ry = -y-16;
            const GLfloat lx = x+16, rx = -x-16;

            matrix_push();
            matrix_pure_reset();
            MATRIX_X = X;
            MATRIX_Y = Y;
            matrix_rotate2(tab->curangle);
            matrix_move_z(SQ_MARKS_POS);

            GLfloat coords[4][8] =
            {
                {rx, ly,      -x, ly,      -x, y,       rx, y},
                {x, ly,       lx, ly,      lx, y,       x, y},
                {x, -y,       lx, -y,      lx, ry,      x, ry},
                {rx, -y,      -x, -y,      -x, ry,      rx, ry}
            };

            for(z = 0;z < 4;z++)
            {
                glBindTexture(GL_TEXTURE_2D, mark[z]);

                glBegin(GL_QUADS);
                    glTexCoord2f(0.0, 0.0);    glVertex2f(coords[z][0], coords[z][1]);
                    glTexCoord2f(1.0, 0.0);    glVertex2f(coords[z][2], coords[z][3]);
                    glTexCoord2f(1.0, 1.0);    glVertex2f(coords[z][4], coords[z][5]);
                    glTexCoord2f(0.0, 1.0);    glVertex2f(coords[z][6], coords[z][7]);
                glEnd();
            }

            matrix_pop();
            write_gl_matrix();
        }
    }

    glDisable(GL_TEXTURE_2D);

    matrixChanged();

    if(!tab->broken && tab->total > 1)
        frameChanged();
}

/*
 *  Change statusbar info according with
 *  current matrix (it shows current zoom & angle values).
 */
void SQ_GLWidget::matrixChanged()
{
    TQString str;

    float m = getZoom();
    float fzoom = m * 100.0;
    float z = (m < 1.0) ? 1.0/m : m;

    // construct zoom
    str = TQString::fromLatin1("%1% [%2:%3]")
            .arg(fzoom, 0, 'f', 1)
            .arg((m < 1.0)?1.0:z, 0, 'f', 1)
            .arg((m > 1.0)?1.0:z, 0, 'f', 1);

#ifndef KSQUIRREL_PART
    SQ_GLView::window()->sbarWidget("SBGLZoom")->setText(str);
#else
    t_glv.sbarWidget("SBGLZoom")->setText(str);
#endif

    // construct rotation angle
    str = TQString::fromLatin1("%1%2 %3 deg")
            .arg((tab->isflippedV)?"V":"")
            .arg((tab->isflippedH)?"H":"")
            .arg(tab->curangle, 0, 'f', 1);

#ifndef KSQUIRREL_PART
    SQ_GLView::window()->sbarWidget("SBGLAngle")->setText(str);
#else
    t_glv.sbarWidget("SBGLAngle")->setText(str);
#endif
}

/*
 *  Mouse wheel event. Let's load next/previous image, or
 *  zoom in/zoom out (depends on settings).
 */
void SQ_GLWidget::wheelEvent(TQWheelEvent *e)
{
    if(e->delta() < 0 && e->state() == TQt::NoButton)
    {
#ifndef KSQUIRREL_PART
        SQ_Config::instance()->setGroup("GL view");

        // load next file in current directory
        if(SQ_Config::instance()->readNumEntry("scroll", 0))
            slotNext();
        else
#endif

            slotZoomPlus();
    }
    else if(e->delta() > 0 && e->state() == TQt::NoButton)
    {
#ifndef KSQUIRREL_PART
        SQ_Config::instance()->setGroup("GL view");

        if(SQ_Config::instance()->readNumEntry("scroll", 0))
            slotPrev();
        else
#endif

            slotZoomMinus();
    }
    // some special bindings:
    // if CTRL key is pressed, zoom 2x or 0.5x
    else if(e->delta() < 0 && e->state() == TQt::ControlButton)
        matrix_zoom(2.0);
    else if(e->delta() > 0 && e->state() == TQt::ControlButton)
        matrix_zoom(0.5f);
    else if(e->delta() < 0 && e->state() == TQt::ShiftButton)
        slotZoomPlus();
    else if(e->delta() > 0 && e->state() == TQt::ShiftButton)
        slotZoomMinus();
}

// User pressed mouse button down.
void SQ_GLWidget::mousePressEvent(TQMouseEvent *e)
{
    setFocus();

    // left button, update cursor
    if(e->button() == TQt::LeftButton && e->state() == TQt::NoButton && tab->glselection == -1)
    {
#ifndef KSQUIRREL_PART
        TQTime t = TQTime::currentTime();

        SQ_Config::instance()->setGroup("GL view");
        int dc = SQ_Config::instance()->readNumEntry("double_click", 0);

        if(dc && clickTime.isValid() && clickTime.msecsTo(t) <= TDEApplication::doubleClickInterval())
        {
            if(dc == 1)
                KSquirrel::app()->closeGLWidget();
            else
                toggleFullScreen();

            return;
        }
        else
            clickTime = t;

#endif
        setCursor(KCursor::sizeAllCursor());

        xmoveold = e->x();
        ymoveold = e->y();

        movetype = 1;
    }
    // left button + SHIFT, let's start drawing zoom frame
    else if(e->button() == TQt::LeftButton && (e->state() == TQt::ShiftButton || tab->glselection != -1))
    {
        // stop animation!
        stopAnimation();

        // update cursor to crosshair
        setCursor(KCursor::crossCursor());

        if(tab->glselection == SQ_GLSelectionPainter::Rectangle || tab->glselection == SQ_GLSelectionPainter::Ellipse)
            gls->begin(static_cast<SQ_GLSelectionPainter::Type>(tab->glselection), e->x(), e->y());
        else
            gls->begin(SQ_GLSelectionPainter::Rectangle, e->x(), e->y());

        movetype = 2;
    }
    // right button - show context menu
    else if(e->button() == TQt::RightButton)
        menu->popup(TQCursor::pos());
    // middle button - toggle fullscreen state
    else if(e->button() == TQt::MidButton)
        toggleFullScreen();
}

// User moved mouse.
void SQ_GLWidget::mouseMoveEvent(TQMouseEvent *e)
{
    // user didn't press any mouse button before ?
    if(movetype == -1 && fullscreen())
    {
#if 0
//        bool tvis = SQ_GLView::window()->toolbar()->isShown();
//        bool svis = SQ_GLView::window()->statusbar()->isShown();

        int h = SQ_GLView::window()->toolbar()->height() +
                SQ_GLView::window()->tabbar()->height();

#warning FIXME
#ifndef KSQUIRREL_PART
        hackResizeGL = true;
        SQ_GLView::window()->boxBar()->setShown((/*tvis ? false:*/(e->y() < h)));
        SQ_GLView::window()->statusbar()->setShown((/*svis ? false:*/(e->y() >
                        height()-SQ_GLView::window()->statusbar()->height())));
#endif
#endif
        return;
    }

    // left mouse button, lets move image (or matrix :-) )
    if(movetype == 1)
    {
        xmove = e->x();
        ymove = e->y();

        matrix_move(xmove-xmoveold, -ymove+ymoveold);

        xmoveold = e->x();
        ymoveold = e->y();
    }
    // left + SHIFT
    else if(movetype == 2)
        gls->move(e->x(), e->y());
}

// User released some mouse button.
void SQ_GLWidget::mouseReleaseEvent(TQMouseEvent *)
{
    if(movetype == -1)
        return;

    // left button - restore cursor
    if(movetype == 1 || (movetype == 2 && tab->glselection != -1)) // permanent selection
        setCursor(KCursor::arrowCursor());
    // left button + SHIFT - zoom to selected rectangle (if needed)
    else if(movetype == 2 && tab->glselection == -1)
    {
        setCursor(KCursor::arrowCursor());

        TQSize sz = gls->size();
        TQPoint pt = gls->pos();
        TQRect lastRect(pt.x(), pt.y(), sz.width(), sz.height());

        gls->end();

        TQPoint lastC = lastRect.center();
        TQPoint O(width() / 2, height() / 2);

        if(lastRect.width() > 2 && lastRect.height() > 2)
        {
            bool lastReset = reset_mode;
            reset_mode = true;
            float X = MATRIX_X, Y = MATRIX_Y;
            matrix_move(O.x() - lastC.x(), lastC.y() - O.y());
            reset_mode = lastReset;

            // try to zoom
            bool zoomed = tab->broken ? false : zoomRect(lastRect);

            // not zoomed ? (zoom > maximum zoom)
            if(!zoomed)
            {
                MATRIX_X = X;
                MATRIX_Y = Y;
                write_gl_matrix();
            }
        }

        updateGL();

        // start animation, if needed
        if(!manualBlocked())
            startAnimation();
    }

    movetype = -1;
}

/*
 *  Zoom to 'r'. Will be called after somebody used right mouse button
 *  to select zoom region.
 */
bool SQ_GLWidget::zoomRect(const TQRect &r)
{
    // calculate zoom factor
    float factor = 1.0;
    float w = (float)width(), h = (float)height();
    float factorw = w / (float)r.width();
    float factorh = h / (float)r.height();
    float t = w / h;

    if(t > (float)r.width() / (float)r.height())
        factor = factorh;
    else
        factor = factorw;

    // try to zoom
    return matrix_zoom(factor);
}

// Fit width.
void SQ_GLWidget::slotZoomW()
{
    zoom_type = 0;
    pAZoomW->setChecked(true);

    // no image decoded
    if(tab->broken || tab->finfo.image.empty()) return;

    // calculate zoom factor
    float factor = (float)width() / (tab->rotate ? (float)tab->finfo.image[tab->current].h : (float)tab->finfo.image[tab->current].w);

    // "Ignore, if the image is less than window" - restore zoom factor to 1.0
    if(pAIfLess->isChecked() && (tab->finfo.image[tab->current].w < width() && tab->finfo.image[tab->current].h < height()))
        factor = 1.0;

    // zoom...
    internalZoom(factor);
}

// Fit height.
void SQ_GLWidget::slotZoomH()
{
    zoom_type = 1;
    pAZoomH->setChecked(true);

    if(tab->broken || tab->finfo.image.empty()) return;

    float factor = (float)height() / (tab->rotate ? (float)tab->finfo.image[tab->current].w : (float)tab->finfo.image[tab->current].h);

    if(pAIfLess->isChecked() && (tab->finfo.image[tab->current].w < width() && tab->finfo.image[tab->current].h < height()))
        factor = 1.0;

    internalZoom(factor);
}

// Fit image (e.g. width and height).
void SQ_GLWidget::slotZoomWH()
{
    zoom_type = 2;
    pAZoomWH->setChecked(true);

    if(tab->broken || tab->finfo.image.empty()) return;

    float factor = 1.0;
    float w = (float)width(), h = (float)height();
    float factorw = w / (tab->rotate  ? (float)tab->finfo.image[tab->current].h : (float)tab->finfo.image[tab->current].w);
    float factorh = h / (tab->rotate ? (float)tab->finfo.image[tab->current].w : (float)tab->finfo.image[tab->current].h);
    float t = w / h;
    float F = tab->rotate ? ((float)tab->finfo.image[tab->current].h / (float)tab->finfo.image[tab->current].w) : ((float)tab->finfo.image[tab->current].w / (float)tab->finfo.image[tab->current].h);

    if(t > F)
        factor = factorh;
    else
        factor = factorw;

    if(pAIfLess->isChecked() && (tab->finfo.image[tab->current].w < width() && tab->finfo.image[tab->current].h < height()))
        factor = 1.0;

    internalZoom(factor);
}

// Previous zoom.
void SQ_GLWidget::slotZoomLast()
{
    zoom_type = 4;
    pAZoomLast->setChecked(true);

    if(tab->broken || tab->finfo.image.empty()) return;

    internalZoom(zoomFactor);
}

// Zoom 100%.
void SQ_GLWidget::slotZoom100()
{
    zoom_type = 3;
    pAZoom100->setChecked(true);

    if(tab->broken || tab->finfo.image.empty()) return;

    internalZoom(1.0);
}

// "Ignore, if the image is less than window"
void SQ_GLWidget::slotZoomIfLess()
{
    if(tab->broken || tab->finfo.image.empty()) return;

    switch(zoom_type)
    {
        case 0: slotZoomW(); break;
        case 1: slotZoomH(); break;
        case 2: slotZoomWH(); break;
        case 3: break;

        default:
            slotZoomLast();
    }
}

// Zoom+
void SQ_GLWidget::slotZoomPlus()
{
    matrix_zoom(1.0+zoomfactor/100.0);
}

// Zoom-
void SQ_GLWidget::slotZoomMinus()
{
    matrix_zoom(1.0/(1.0+zoomfactor/100.0));
}

// Rotate left.
void SQ_GLWidget::slotRotateLeft()
{
    matrix_rotate(-rotatefactor);
}

// Rotate right.
void SQ_GLWidget::slotRotateRight()
{
    matrix_rotate(rotatefactor);
}

/*
     operations with matrices are taken from GLiv =)

     thanks to Guillaume Chazarian.
*/
void SQ_GLWidget::flip(int id, bool U)
{
    GLfloat x = MATRIX_X, y = MATRIX_Y;
    MATRIX_X = 0;
    MATRIX_Y = 0;

    tab->matrix[id] *= -1.0;
    tab->matrix[id + 1] *= -1.0;
    tab->matrix[id + 3] *= -1.0;

    MATRIX_X = x;
    MATRIX_Y = y;

    write_gl_matrix();

    if(!reset_mode && U)
        updateGL();
}

void SQ_GLWidget::slotFlipH()
{
    if(!tab->broken)
    {
        tab->isflippedH = !tab->isflippedH;
        flip(0);
    }
}

void SQ_GLWidget::slotFlipV()
{
    if(!tab->broken)
    {
        tab->isflippedV = !tab->isflippedV;
        flip(4);
    }
}

void SQ_GLWidget::slotMatrixReset()
{
    if(!tab->broken)
    {
        oldZoom = getZoom();
        matrix_reset(false);
        matrix_zoom(1.0);
    }
}

void SQ_GLWidget::write_gl_matrix()
{
    GLfloat transposed[16] =
    {
        1.0, 0.0, 0.0, 0.0,
        0.0, 1.0, 0.0, 0.0,
        0.0, 0.0, 1.0, 0.0,
        0.0, 0.0, 0.0, 1.0
    };

    transposed[0] = MATRIX_C1;
    transposed[5] = MATRIX_C2;
    transposed[4] = MATRIX_S1;
    transposed[1] = MATRIX_S2;
    transposed[12] = MATRIX_X;
    transposed[13] = MATRIX_Y;
    transposed[14] = MATRIX_Z;

    glLoadMatrixf(transposed);
}

void SQ_GLWidget::matrix_move(GLfloat x, GLfloat y)
{
    if(tab->broken) return;

    MATRIX_X += x;
    MATRIX_Y += y;

    write_gl_matrix();

    if(!reset_mode)
        updateGL();
}

void SQ_GLWidget::matrix_move_z(GLfloat z)
{
    MATRIX_Z = z;

    write_gl_matrix();
}

void SQ_GLWidget::matrix_push()
{
    memcpy(saved, tab->matrix, sizeof(tab->matrix));
}

void SQ_GLWidget::matrix_pop()
{
    memcpy(tab->matrix, saved, sizeof(tab->matrix));
}

void SQ_GLWidget::matrix_reset(bool U)
{
    tab->nullMatrix();

    tab->curangle = 0.0;
    tab->isflippedH = tab->isflippedV = false;

    if(decoded)
        exifRotate(U);
}

void SQ_GLWidget::matrix_pure_reset()
{
    tab->nullMatrix();
    write_gl_matrix();
}

bool SQ_GLWidget::matrix_zoom(GLfloat ratio)
{
    if(tab->broken) return false;

    SQ_Config::instance()->setGroup("GL view");

    int zoom_lim = SQ_Config::instance()->readNumEntry("zoom limit", 1);
    GLfloat zoom_min, zoom_max, zoom_tobe;

    zoom_tobe = hypot(MATRIX_C1 * ratio, MATRIX_S1 * ratio) * 100.0;

    switch(zoom_lim)
    {
        case 2:
            zoom_min = (float)SQ_Config::instance()->readNumEntry("zoom_min", 1);
            zoom_max = (float)SQ_Config::instance()->readNumEntry("zoom_max", 10000);
        break;

        default: // "case 1:" too
            zoom_min = 1.0;
            zoom_max = 10000.0;
    }

    if(zoom_lim)
    {
        float z = getZoomPercents();

        // zoom limit exceeded - do nothing
        if((z >= zoom_max && ratio > 1.0) || (z <= zoom_min && ratio < 1.0))
            return false;

        // if the new zoom will be greater (smaller) than maximum
        // (minimum) zoom - scale it.
        if(ratio < 1.0 && zoom_min >= zoom_tobe)
            ratio = ratio * zoom_min / zoom_tobe;
        else if(ratio > 1.0 && zoom_max <= zoom_tobe)
            ratio = ratio * zoom_max / zoom_tobe;
    }

    GLfloat oldz = (oldZoom == -1 ? getZoom() : oldZoom);

    MATRIX_C1 *= ratio;
    MATRIX_S1 *= ratio;
    MATRIX_X  *= ratio;
    MATRIX_S2 *= ratio;
    MATRIX_C2 *= ratio;
    MATRIX_Y  *= ratio;

    hackMatrix();

    GLfloat z = getZoom();
    int filter = -1;

    if(ISFLOAT1(oldz) && !ISFLOAT1(z))
        filter = linear ? GL_LINEAR : GL_NEAREST;
    else if(ISFLOAT1(z))
        filter = GL_NEAREST;

    // update all textures
    if(filter != -1)
    {
        for(int i = 0;i < tab->total;i++)
        {
            int toxy = tab->parts[i].m_parts.size();

            for(int j = 0;j < toxy;j++)
            {
                glBindTexture(GL_TEXTURE_2D, tab->parts[i].m_parts[j].tex);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter);
            }
        }
    }

    oldZoom = -1;
    write_gl_matrix();

    changeSlider(z);

    if(!reset_mode)
        updateGL();

    return true;
}

/*
 *  Floating point operations are not 100% exact.
 *  We should correct matrix values.
 */
void SQ_GLWidget::hackMatrix()
{
    if(ISFLOAT1(MATRIX_C1)) MATRIX_C1 = MATRIX_C1 < 0 ? -1.0 : 1.0;
    else if(ISFLOAT0(MATRIX_C1)) MATRIX_C1 = 0.0;

    if(ISFLOAT1(MATRIX_C2)) MATRIX_C2 = MATRIX_C2 < 0 ? -1.0 : 1.0;
    else if(ISFLOAT0(MATRIX_C2)) MATRIX_C2 = 0.0;

    if(ISFLOAT1(MATRIX_S1)) MATRIX_S1 = MATRIX_S1 < 0 ? -1.0 : 1.0;
    else if(ISFLOAT0(MATRIX_S1)) MATRIX_S1 = 0.0;

    if(ISFLOAT1(MATRIX_S2)) MATRIX_S2 = MATRIX_S2 < 0 ? -1.0 : 1.0;
    else if(ISFLOAT0(MATRIX_S2)) MATRIX_S2 = 0.0;

    if(ISFLOAT0(MATRIX_X)) MATRIX_X = 0.0;
    if(ISFLOAT0(MATRIX_Y)) MATRIX_Y = 0.0;
}

GLfloat SQ_GLWidget::getZoom() const
{
    return hypot(MATRIX_C1, MATRIX_S1);
}

GLfloat SQ_GLWidget::getZoomPercents() const
{
    return getZoom() * 100.0;
}

void SQ_GLWidget::matrix_rotate(GLfloat angle, bool U)
{
    if(tab->broken) return;

    GLfloat c1 = MATRIX_C1, c2 = MATRIX_C2, s1 = MATRIX_S1, s2 = MATRIX_S2;

    double rad = angle * rad_const;
    double cosine = cos(rad);
    double sine = sin(rad);

    MATRIX_C1 = c1  * cosine + s2 * sine;
    MATRIX_S1 = s1  * cosine + c2 * sine;
    MATRIX_S2 = -c1 * sine   + s2 * cosine;
    MATRIX_C2 = -s1 * sine   + c2 * cosine;

    hackMatrix();

    tab->curangle += angle;

    if(tab->curangle == 360.0 || tab->curangle == -360.0)
        tab->curangle = 0.0;
    else if(tab->curangle > 360.0)
        tab->curangle -= 360.0;
    else if(tab->curangle < -360.0)
        tab->curangle += 360.0;

    write_gl_matrix();

    if(U)
        updateGL();
}

void SQ_GLWidget::matrix_rotate2(GLfloat angle)
{
    GLfloat c1 = MATRIX_C1, c2 = MATRIX_C2, s1 = MATRIX_S1, s2 = MATRIX_S2;

    double rad = angle * rad_const;
    double cosine = cos(rad);
    double sine = sin(rad);

    MATRIX_C1 = c1  * cosine + s2 * sine;
    MATRIX_S1 = s1  * cosine + c2 * sine;
    MATRIX_S2 = -c1 * sine   + s2 * cosine;
    MATRIX_C2 = -s1 * sine   + c2 * cosine;

    write_gl_matrix();
}

/*
 *  Bind textures, draw them and create GL lists.
 *  Also show textures by executing GL lists, if 'swap' = true.
 */
bool SQ_GLWidget::showFrames(int i, Parts *p, bool swap)
{
    int z, k = 0;
    const int a = p->tilesx.size() * i, b = a + p->tilesx.size();
    int filter = linear ? GL_LINEAR : GL_NEAREST;

    // for safety...
    makeCurrent();

    glEnable(GL_TEXTURE_2D);

    float zm = getZoom();

    for(z = a;z < b;z++)
    {
        glBindTexture(GL_TEXTURE_2D, p->m_parts[z].tex);

        // setup texture parameters
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, ISFLOAT1(zm) ? GL_NEAREST : filter);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, ISFLOAT1(zm) ? GL_NEAREST : filter);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

        setupBits(p, buffer, i, k);

        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, p->tilesx[k], p->tilesy[i], 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer);

        k++;
    }

    // create new display list
    glNewList(p->m_parts[a].list, swap ? GL_COMPILE_AND_EXECUTE : GL_COMPILE);

    // bind & draw textures (if needed)
    for(z = a;z < b;z++)
    {
        glBindTexture(GL_TEXTURE_2D, p->m_parts[z].tex);

        glBegin(GL_QUADS);
            glTexCoord2f(p->m_parts[z].tx1, p->m_parts[z].ty1); glVertex2f(p->m_parts[z].x1, p->m_parts[z].y1);
            glTexCoord2f(p->m_parts[z].tx2, p->m_parts[z].ty1); glVertex2f(p->m_parts[z].x2, p->m_parts[z].y1);
            glTexCoord2f(p->m_parts[z].tx2, p->m_parts[z].ty2); glVertex2f(p->m_parts[z].x2, p->m_parts[z].y2);
            glTexCoord2f(p->m_parts[z].tx1, p->m_parts[z].ty2); glVertex2f(p->m_parts[z].x1, p->m_parts[z].y2);
        glEnd();
    }

    glEndList();

    glDisable(GL_TEXTURE_2D);

    // swap buffers...
    if(swap)
        swapBuffers();

    return GL_TRUE;
}

void SQ_GLWidget::setupBits(Parts *p, RGBA *_buffer, int y, int x)
{
    TQPair<int, int> pair = SQ_GLWidget::calcRealDimensions(*p, y, x);

    int offs = p->realw * pair.second + pair.first;
    RGBA *orig = p->buffer->data() + offs;

    int toy = p->tilesy[y];
    int tox = p->tilesx[x];

    for(int j = 0;j < toy;j++)
        memcpy(_buffer + tox*j, orig + p->realw*j, tox*sizeof(RGBA));
}

/*
 *  Start decoding given image. We can call it from anywhere.
 */
void SQ_GLWidget::startDecoding(const TQString &file)
{
#ifndef KSQUIRREL_PART
    if(SQ_PreviewWidget::instance()->cancel())
        SQ_WidgetStack::instance()->diroperator()->stopPreview();
#endif

    if(reset_mode)
        return;

    started.start();

    tabold = tab;
    tmptab.empty();
    tab = &tmptab;
    reset_mode = true;

    timer_anim->stop();
    images->clear();

    tab->m_File = file;                         // original name
    tab->File = TQFile::encodeName(tab->m_File); // translated name
    tab->m_original = m_original;

    TQFileInfo fm(file);
    tab->fmt_ext = fm.extension(false);
    tab->fmt_size = fm.size();

#ifndef KSQUIRREL_PART
    // show window with image
    KSquirrel::app()->raiseGLWidget();
#endif

    if(m_expected.isEmpty())
        TDEApplication::eventLoop()->processEvents(TQEventLoop::ExcludeUserInput | TQEventLoop::ExcludeSocketNotifiers);

    decode();
}

/*
 *  Prepare decoding. It will find proper library for decoding,
 *  clear old memory buffers, etc.
 */
bool SQ_GLWidget::prepare()
{
    TQString status;

    tab = &tmptab;

    // get library from cache
    SQ_LIBRARY *m_lib = SQ_LibraryHandler::instance()->libraryForFile(tab->m_File);

    if(!m_lib)
    {
        KMessageBox::error(this, i18n("Codec for %1 format not found").arg(tab->fmt_ext));
        reset_mode = false;
        tab = tabold;
        return false;
    }
    else
        tmptab.lib = m_lib;

    enableActions(true);

    int already = -1, result = -1, i = 0;
    SQ_Config::instance()->setGroup("GL view");

#ifndef KSQUIRREL_PART
    bool use_tabs = SQ_GLView::window()->tabs();
#endif

    bool useCurrent = false;

#ifndef KSQUIRREL_PART
    if(!use_tabs)
    {
        removeCurrentTabs();

        SQ_GLView::window()->removeTabs();
    }
    else
    {
        if(m_expected != m_original)
        {
            std::vector<Tab>::iterator it = tabs.begin();
            std::vector<Tab>::iterator itEnd = tabs.end();
            for(;it != itEnd;++it, ++i)
            {
                if((*it).m_original == m_original)
                {
                    already = i;
                    useCurrent = true;
                    break;
                }
            }
        }

        // 0  - new page
        // 1  - replace current tab
        // 2  - close all and open in a new tab
        // -1 - ignore 'result' at all
        if(already == -1
            && decoded
            && m_expected != m_original
            && !useCurrent
            && SQ_Config::instance()->readBoolEntry("tabs_ask", false))
        {
            SQ_TabOpenDialog tbo(this);
            tbo.exec();
            result = tbo.result();
        }
    }
#else
    removeCurrentTabs();
#endif

#ifndef KSQUIRREL_PART
    int curtab = SQ_GLView::window()->tabbar()->indexOf(SQ_GLView::window()->tabbar()->currentTab());
#else
    int curtab = -1;
#endif

    int curindex = (already == -1) ? curtab : already;

    if(curindex != -1 && (m_expected == m_original || result == 1 || useCurrent))
    {
        if(curtab != curindex)
            tabs[curtab].removeParts();

        tabs[curindex].clearParts();
        tabs[curindex] = tmptab;
        tab = &tabs[curindex];

#ifndef KSQUIRREL_PART
        SQ_GLView::window()->tabbar()->blockSignals(true);
        SQ_GLView::window()->tabbar()->setCurrentTab(SQ_GLView::window()->tabbar()->tabAt(curindex));
        SQ_GLView::window()->tabbar()->tabAt(curindex)->setText(KStringHandler::csqueeze(tab->m_original.fileName(), SQ_TAB_TEXT_LENGTH));
        SQ_GLView::window()->tabbar()->blockSignals(false);
#endif
    }
    else
    {
        if(curindex != -1)
        {
            if(result == 2)
                closeAllTabs();
            else
                tabs[curindex].removeParts();
        }

        tabs.push_back(tmptab);
        int lastId = tabs.size() - 1;
        tab = &tabs[lastId];

#ifndef KSQUIRREL_PART
        SQ_GLView::window()->tabbar()->blockSignals(true);
        SQ_GLView::window()->addPage(KStringHandler::csqueeze(tab->m_original.fileName(), SQ_TAB_TEXT_LENGTH));
        SQ_GLView::window()->tabbar()->setCurrentTab(SQ_GLView::window()->tabbar()->tabAt(lastId));

        // TQTabBar::show will emit selected(int),
        // we don't want it
        emit tabCountChanged();
        SQ_GLView::window()->tabbar()->blockSignals(false);
#endif
    }

    gls->setVisible(false);

    SQ_CodecSettings::applySettings(tab->lib, SQ_CodecSettings::ImageViewer);

    // determine codec
    tab->codeK = tab->lib->codec;

    // start decoding!
    i = tab->codeK->read_init(tab->File.ascii());

    // oops, error...
    if(i != SQE_OK)
    {
        decodeFailedOn0(i);
        m_expected = KURL();
        return false;
    }

    return true;
}

void SQ_GLWidget::decode()
{
    // prepare decoding...
    if(!prepare())
        return;

#ifndef KSQUIRREL_PART
    KSquirrel::app()->setCaption(originalURL());
#endif

    zoomFactor = getZoom();
    matrix_pure_reset();
    matrixChanged();

#ifdef SQ_HAVE_KEXIF
    KExifData d;
    d.readFromFile(tab->m_File);
    tab->orient = d.getImageQt::Orientation();
    tab->wm = SQ_Utils::exifGetMatrix(TQString(), tab->orient);

    tab->rotate = (tab->orient == KExifData::ROT_90_HFLIP || tab->orient == KExifData::ROT_90
                || tab->orient == KExifData::ROT_90_VFLIP || tab->orient == KExifData::ROT_270);
#else
    tab->orient = -1;
    tab->rotate = false;
#endif

    errors = 0;

/* *********************************************************** */

    int i, j, id;
    int line, res, first_id = 0;
    fmt_image *im;
    memoryPart *pt;
    bool progr;

    SQ_Config::instance()->setGroup("GL view");

    // in fullscreen mode progressive loading is disabled anyway
    if(fullscreen())
       progr = false;
    else
       progr = SQ_Config::instance()->readBoolEntry("progressiv", true);

    int allpages = SQ_Config::instance()->readNumEntry("load_pages", 0);
    int pages_num = SQ_Config::instance()->readNumEntry("load_pages_number", 1);

    if(pages_num < 1) pages_num = 1;

#ifndef KSQUIRREL_PART
    SQ_GLView::window()->sbarWidget("SBFile")->setText(tab->m_original.fileName());
#else
    t_glv.sbarWidget("SBFile")->setText(tab->m_original.fileName());
#endif

    tab->current = 0;

    bool notexpected = m_expected.isEmpty();
    m_expected = KURL();

    // start time counting
    while(true)
    {
        if((allpages == 1 && tab->current) || (allpages == 2 && tab->current == pages_num))
            break;

        // absolute evil, but should do...
        if(notexpected)
            TDEApplication::eventLoop()->processEvents(TQEventLoop::ExcludeUserInput | TQEventLoop::ExcludeSocketNotifiers);

        i = tab->codeK->read_next();

        // something went wrong. SQE_NOTOK is a special type of error. It means
        // that we decoded all pages.
        if(i != SQE_OK)
        {
            if(i == SQE_NOTOK || tab->current)
                break;
            else
            {
                decodeFailedOn0(i);
                return;
            }
        }

        im = tab->codeK->image(tab->current);

        Parts pp;

        // find tile size
        SQ_GLWidget::findCloserTiles(im->w, im->h, pp.tilesx, pp.tilesy);
        TQPair<int, int> pair = SQ_GLWidget::calcRealDimensions(pp);
        pp.realw = pair.first;
        pp.realh = pair.second;

        // setup current Part
        pp.w = im->w;
        pp.h = im->h;

        // create textures and display lists
        if(!pp.makeParts())
        {
            if(tab->current)
                break;
            else
            {
                KMessageBox::error(this,
                            i18n("Memory allocation failed for %1 of memory")
                            .arg(TDEIO::convertSize(pp.realw * pp.realh * sizeof(RGBA))));

                decodeFailedOn0(SQE_R_NOMEMORY);
                return;
            }
        }

        pt = new memoryPart(pp.realw * pp.realh);
        pt->create();

        if(!pt->valid())
        {
            pp.removeParts();

            if(tab->current)
                break;
            else
            {
                KMessageBox::error(this, i18n("Memory allocation failed"));
                decodeFailedOn0(SQE_R_NOMEMORY);
                return;
            }
        }

        line = 0;

        pp.computeCoords();
        pp.buffer = pt;

        tab->finfo = tab->codeK->information();

        if(!tab->current)
        {
            tab->isflippedH = tab->isflippedV = false;
            slotZoomIfLess();
            matrixChanged();
            updateCurrentFileInfo();
        }

        matrix_move_z(SQ_FIRST_TILE_LAYER+tab->current);

        for(int pass = 0;pass < im->passes;pass++)
        {
            if(tab->codeK->read_next_pass() != SQE_OK)
                break;

            bool flip = tab->finfo.image[tab->current].needflip;
            line = 0;
            int tlsy = pp.tilesy.size();
            int tlsyval, offs = 0, O, iA;

            for(i = 0;i < tlsy;i++)
            {
                iA = flip ? (tlsy-i-1) : i;
                tlsyval = pp.tilesy[iA];

                for(j = 0;j < tlsyval;j++)
                {
                    O = flip ? (pp.realw*(im->h - offs - j-1)) : (offs + j)*pp.realw;
                    res = tab->codeK->read_scanline(pp.buffer->data() + O);
                    errors += (int)(res != SQE_OK);

                    if(++line == im->h)
                        break;
                }

                offs += tlsyval;

                // last pass
                if(pass == im->passes-1)
                {
//                    if(!tab->current)
                    {
                        bool b = showFrames(iA, &pp, progr);

                        if(!b)
                            kdWarning() << "Showframes failed for image " << tab->current << ", tiley " << i << endl;
                    }
                }

            }
        }

        id = images->insertItem(TQString::fromLatin1("#%1 [%2x%3@%4]").arg(tab->current+1).arg(im->w).arg(im->h).arg(im->bpp));

        images->setItemParameter(id, tab->current);

        if(!tab->current)
        {
#ifndef KSQUIRREL_PART
            SQ_GLView::window()->sbarWidget("SBDecodedI")->setPixmap(tab->lib->mime);
#endif
            old_id = first_id = id;
        }

        tab->parts.push_back(pp);

        calcFrameLabelWidth();
        frameChanged();
        tab->current++;
    }

    tab->finfo = tab->codeK->information();
    tab->codeK->read_close();
    tab->total = tab->finfo.image.size();
    tab->current = 0;
    frameChanged();

    enableSettingsButton(!tab->lib->config.isEmpty());

    decoded = true;
    reset_mode = false;
    updateGL();

    tab->quickImageInfo = tab->lib->quickinfo;
    tab->elapsed = started.elapsed();

#ifndef KSQUIRREL_PART
    SQ_GLView::window()->sbarWidget("SBLoaded")->setText(TDEGlobal::locale()->formatLong(tab->elapsed) + i18n(" ms."));
#else
    t_glv.sbarWidget("SBLoaded")->setText(TDEGlobal::locale()->formatLong(tab->elapsed) + i18n(" ms."));
#endif

    images->setItemChecked(first_id, true);

    if(tab->finfo.animated)
        TQTimer::singleShot(tab->finfo.image[tab->current].delay, this, TQ_SLOT(slotAnimateNext()));
}

/*
 *  Palette changed. Let's update tickmarks and background color.
 */
void SQ_GLWidget::paletteChange(const TQPalette &oldPalette)
{
    TQGLWidget::paletteChange(oldPalette);

    SQ_Config::instance()->setGroup("GL view");

    if(SQ_Config::instance()->readNumEntry("GL view background type", 1) == 0)
    {
        TQColor color = colorGroup().color(TQColorGroup::Base);
        qglClearColor(color);
        updateGL();
    }
}

void SQ_GLWidget::slotFirst()
{
#ifndef KSQUIRREL_PART
    if(!reset_mode)
        SQ_WidgetStack::instance()->firstFile();
#endif
}

void SQ_GLWidget::slotLast()
{
#ifndef KSQUIRREL_PART
    if(!reset_mode)
        SQ_WidgetStack::instance()->lastFile();
#endif
}

void SQ_GLWidget::slotNext()
{
#ifndef KSQUIRREL_PART
    timer_next->stop();
    timer_prev->stop();

    timer_next->start(timer_delay_file, true);
#endif
}

void SQ_GLWidget::slotPrev()
{
#ifndef KSQUIRREL_PART
    timer_prev->stop();
    timer_next->stop();

    timer_prev->start(timer_delay_file, true);
#endif
}

void SQ_GLWidget::slotZoomMenu()
{
    zoomMenu->exec(TQCursor::pos());
}

void SQ_GLWidget::slotAnimateNext()
{
    // Some time ago we started to decode new image, but it is
    // not guaranteed that animation stopped!
    //
    // We should return now to avoid segfaults...
    if(reset_mode)
        return;

//    tab->parts[tab->current].removeParts();

    tab->current++;

    if(tab->current >= (int)tab->finfo.image.size())
        tab->current = 0;

    updateCurrentFileInfo();
    updateGL();

    int delay = tab->finfo.image[tab->current].delay;

    timer_anim->start(delay, true);
}

void SQ_GLWidget::startAnimation()
{
    if(!tab->finfo.animated) return;

    timer_anim->start(tab->finfo.image[tab->current].delay, true);
}

void SQ_GLWidget::stopAnimation()
{
    timer_anim->stop();
}

void SQ_GLWidget::slotToggleAnimate()
{
    if(!tab->finfo.animated || gls->valid()) return;

    if(!timer_anim->isActive())
    {
        tab->manualBlocked = false;
        startAnimation();
    }
    else
    {
        tab->manualBlocked = true;
        stopAnimation();
    }
}

/*
 *  Next image in animated sequence. Called by user (with F3).
 */
void SQ_GLWidget::nextImage()
{
    // if the image has only one page - do nothing
    if(tab->total == 1)
        return;

    tab->current++;

    if(tab->current >= tab->total)
        tab->current = 0;

    updateGL();
    updateCurrentFileInfo();
}

/*
 *  Previous image in animated sequence. Called by user (with F2).
 */
void SQ_GLWidget::prevImage()
{
    if(tab->total == 1)
        return;

    tab->current--;

    if(tab->current < 0)
        tab->current = tab->total - 1;

    updateGL();
    updateCurrentFileInfo();
}

/*
 *  Jump to first/last image page.
 */
void SQ_GLWidget::jumpToImage(bool last)
{
    // if the image has only one page - do nothing
    if(tab->total == 1)
        return;

    tab->current = (last) ? tab->finfo.image.size() - 1 : 0;

    updateGL();
    updateCurrentFileInfo();
}

void SQ_GLWidget::initMarks()
{
    for(int i = 0;i < 4;i++)
    {
        glGenTextures(1, &mark[i]);

        glBindTexture(GL_TEXTURE_2D, mark[i]);

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);

        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, mm[i].bits());
    }
}

void SQ_GLWidget::internalZoom(const GLfloat &zF)
{
    tab->curangle = 0.0;

    oldZoom = getZoom();
    matrix_pure_reset();

    exifRotate(false);

    matrix_zoom(zF);
}

void SQ_GLWidget::updateFilter(bool nice)
{
    if(nice == linear)
        return;

    // store
    linear = nice;

    int filter = nice ? GL_LINEAR : GL_NEAREST;
    Parts *pt;

    // update all textures
    for(int i = 0;i < tab->total;i++)
    {
        pt = tab->broken ? parts_broken : &tab->parts[i];
        int toxy = pt->m_parts.size();

        for(int j = 0;j < toxy;j++)
        {
            glBindTexture(GL_TEXTURE_2D, pt->m_parts[j].tex);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter);
        }
    }

    updateGL();
}

/*
 *  Cleanup method.
 */
void SQ_GLWidget::decodeFailedOn0(const int err_code)
{
    tab->codeK->read_close();
    tab->finfo.image.clear();
    tab->finfo.meta.clear();
    tab->total = 0;
    decoded = (bool)tabs.size();
    reset_mode = false;
    tab->broken = true;
    tab->lib = 0;

    useBrokenImage(err_code);
}

/*
 *  Force using broken image + update context.
 *  Show appropriate error message in statusbar.
 */
void SQ_GLWidget::useBrokenImage(const int err_index)
{
    enableSettingsButton(false);
    enableActions(false);

    // save "broken" image information in 'tab->finfo'
    tab->finfo.image.push_back(image_broken);

    // reset statusbar widgets
    SQ_GLView::window()->resetStatusBar();

#ifndef KSQUIRREL_PART
    // show error message instead of file name
    SQ_GLView::window()->sbarWidget("SBFile")->setText(SQ_ErrorString::instance()->string(err_index));
    KSquirrel::app()->setCaption(TQString());
#else
    t_glv.sbarWidget("SBFile")->setText(SQ_ErrorString::instance()->string(err_index));
#endif

    matrix_pure_reset();
    tab->curangle = 0;
    tab->isflippedH = tab->isflippedV = false;

    changeSlider(1.0);

    // update context and show "broken" image
    updateGL();
}

/*
 *  Are we in fullscreen state ?
 */
bool SQ_GLWidget::fullscreen() const
{
#ifndef KSQUIRREL_PART
    KWin::WindowInfo wi = KWin::windowInfo(SQ_GLView::window()->winId());

    if(wi.valid())
        pAFull->setChecked((wi.state() & NET::FullScreen));

    return pAFull->isChecked();
#else
    return false;
#endif
}

/*
 *  Toggle fullscreen state.
 */
void SQ_GLWidget::toggleFullScreen()
{
#ifndef KSQUIRREL_PART
    bool fs = !fullscreen();

    pAFull->setChecked(fs);
    pAToolFull->setOn(fs);

    KSquirrel::app()->slotFullScreen(fs);
#endif
}

void SQ_GLWidget::slotSetZoomPercents(int perc)
{
    if(tab->broken || tab->finfo.image.empty()) return;

    GLfloat z = (perc <= 20) ? (GLfloat)perc / 20 : ((GLfloat)perc - 20)/2 + 1.0;

    internalZoom(z);
}

void SQ_GLWidget::updateFactors()
{
    zoomfactor = SQ_Config::instance()->readNumEntry("zoom", 25);
    movefactor = SQ_Config::instance()->readNumEntry("move", 5);
    rotatefactor = SQ_Config::instance()->readNumEntry("angle", 90);
}

void SQ_GLWidget::slotSelectionRect()
{
    stopAnimation();
    tab->glselection = SQ_GLSelectionPainter::Rectangle;
    gls->end();
}

void SQ_GLWidget::slotSelectionEllipse()
{
    stopAnimation();
    tab->glselection = SQ_GLSelectionPainter::Ellipse;
    gls->end();
}

void SQ_GLWidget::slotSelectionClear()
{
    tab->glselection = -1;
    gls->end();

    pASelectionEllipse->setChecked(false);
    pASelectionRect->setChecked(false);

    if(!manualBlocked())
        startAnimation();
}

bool SQ_GLWidget::manualBlocked()
{
    // selection is also blocks animation
    return tab->manualBlocked || gls->valid();
}

void SQ_GLWidget::setDownloadPercents(int p)
{
    if(p < 0)
        percentsLabel->hide();
    else
    {
        percentsLabel->setText(i18n("Downloading...") + ' ' + TDEIO::convertSize(p));
        percentsLabel->adjustSize();
        percentsLabel->show();
    }
}

#include "sq_glwidget.moc"
