/***************************************************************************
 *                       Control.cpp  -  description
 *                               -------------------
 *  begin                : Tue March 1 10:40:21 BST 2003
 *  copyright            : (C) 2002 by Dmitri Skachkov
 *  email                : d_skachkov@yahoo.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.                                   *
 *                                                                         *
 ***************************************************************************/
#include "Control.h"

int elapsedtime()
{
	static struct timeval oldtime;
	struct timeval newtime;
	int diff;
	gettimeofday(&newtime,NULL);
	
	 if (newtime.tv_usec < oldtime.tv_usec) {
	   int nsec = (oldtime.tv_usec - newtime.tv_usec) / 1000000 + 1;
	   oldtime.tv_usec -= 1000000 * nsec;
	   oldtime.tv_sec += nsec;
	 }
	 if (newtime.tv_usec - oldtime.tv_usec > 1000000) {
	   int nsec = (newtime.tv_usec - oldtime.tv_usec) / 1000000;
	   oldtime.tv_usec += 1000000 * nsec;
	   oldtime.tv_sec -= nsec;
	 }
  diff=((newtime.tv_sec-oldtime.tv_sec)*1000000+(newtime.tv_usec-oldtime.tv_usec))/1000;
  
	oldtime=newtime;
	return diff;
}

Control::Control(QMainWindow *w, const char *name):QWidget(w, name)
{
	  elapsedtime();
    docIsOpen = false;
    translating = false;
    paintDevice = this;
    parent = w;
    stream = 0;
    dict = 0;
    docPosition = 0;
    scrolling = false;
    
    QFont defaultFont = QApplication::font();
    
    config.display.fontName = defaultFont.family();     // "helvetica";
    config.display.fontSize = defaultFont.pointSize();  // 12;
    config.display.fontBold = defaultFont.bold();       // false;
    config.display.fontItalic = defaultFont.italic();   // false;
    config.display.fakeBold = false;
    config.display.scrollDelay = 200;
    config.display.imageScaling = 100;
    config.display.barVisible = true;
//    config.display.fontSize = 12;
    config.display.defaultAlign = ALIGN_LEFT;
    config.display.align = config.display.defaultAlign;
    config.display.overlap = 0;
    config.display.imageAsWord = false;
    oldDocFormat = config.format.docFormat = FORMAT_AUTO;
    oldPalmFormat = config.format.palmFormat = FORMAT_PALM_DOC; 
    config.format.reparagraph = true;
    config.format.stripCR = true;
    config.format.separateParagraphs = false;
    config.format.reorderPlucker = true;
    config.format.encoding = "ISO 8859-1";
    config.misc.intDict = true;
    config.colors.bgColor = Qt::white;
    config.colors.normalColor = Qt::black;
    config.colors.linkColor = Qt::blue;
    config.colors.barFgColor = Qt::darkGray;
    config.colors.barBgColor = Qt::lightGray;
    config.colors.barTextColor = Qt::black;
    config.colors.highlightColor = Qt::yellow;
    
    config.keys.scroll = Key_S;
    config.keys.bar = Key_B;
    config.keys.fullScreen = Key_Space;
    config.keys.rotate = Key_R;
    config.keys.settings = Key_T;
    config.keys.fileopen = Key_O;
    config.keys.bookmark = Key_M;
    config.keys.find = Key_F;
    config.keys.back = Key_Backspace;
    config.keys.joypad = true;
    config.keys.up = Key_Up;
    config.keys.down = Key_Down;
    config.keys.left = Key_Left;
    config.keys.right = Key_Right;
    
    oldDictionary = config.misc.dictionary = "";
    setFocusPolicy(QWidget::StrongFocus);
    maxPagesCacheSize = sizeof(pagesAttrsCache)/sizeof(pagesAttrsCache[0]);
    //maxLinksCacheSize = sizeof(linesAttrCache)/sizeof(linesAttrCache[0]);
    maxLinksCacheSize = sizeof(linksCache)/sizeof(linksCache[0]);
    linksCachePointer = 0;
    linksCacheSize = 0;
    word2search = "";
    lastFoundPos = -1;
    frame = new Frame();
    frame->create();
    frame->setDefaultFont(defaultFont);
    frame->setFont(defaultFont);
    frame->setFontSize(config.display.fontSize);
    frame->setRotation(0);
    frame->setSize(paintDevice->width(),paintDevice->height());
    frame->clear(true);
    frame->setIndent(false);
    frame->setIndentPoints(8);
    frame->setBottomMargin(2);
    frame->setSpacing(2);
    frame->setPenColor(config.colors.normalColor);
    frame->setMargin(4);
    frame->setLineAlign(config.display.defaultAlign);
}

Control::~Control()
{
    if (stream)
    {
	stream->closeFile();
	stream->~Stream();
    }
    if (dict) dict->~IntDict();
    frame->~Frame();
}

// Enumerator for direction pad transformation for rotation
enum K {
    K_UP = 0,
    K_DOWN = 1,
    K_LEFT = 2,
    K_RIGHT = 3
};

static int keytransUp[] = {K_UP,K_RIGHT,K_LEFT,K_DOWN};
static int keytransDown[] = {K_DOWN,K_LEFT,K_RIGHT,K_UP};
static int keytransLeft[] = {K_LEFT,K_UP,K_DOWN,K_RIGHT};
static int keytransRight[] = {K_RIGHT,K_DOWN,K_UP,K_LEFT};

void Control::keyPressed(QKeyEvent * e)
{
    if (!stream) return;
    if (!isVisible()) return;
    int key;
    int rot_to_int;
    bool fontchanged;
    fontchanged = false;
    key = -1;
    if (config.display.rotation == 0) rot_to_int = 0;
    else if (config.display.rotation == -90) rot_to_int = 1;
    else if (config.display.rotation == 90) rot_to_int = 2;
    else rot_to_int = 3;
    if (e->key() == 0x200f) // fn-1 magnify minus
        key = K_LEFT;
    else if (e->key() == 0x2010) // fn-2 magnify plus
        key = K_RIGHT;
    else if (config.keys.joypad)
    {
    	switch (e->key())
    	{
            case Key_Up:
	        key = keytransUp[rot_to_int];
	        break;
	    case Key_Down:
	        key = keytransDown[rot_to_int];
	        break;
	    case Key_Left:
	        key = keytransLeft[rot_to_int];
	        break;
	    case Key_Right:
	        key = keytransRight[rot_to_int];
	        break;
	    default:
	        e->ignore();
	        break;
	}
    } else {
	if (e->key() == config.keys.up) key = K_UP;
	else if (e->key() == config.keys.down) key = K_DOWN;
	else if (e->key() == config.keys.left) key = K_LEFT;
	else if (e->key() == config.keys.right) key = K_RIGHT;
    }
    if (translating)
    {
	switch(key)
	{
	    case K_LEFT:
	    case K_UP:
		dict->prevWord();
		wordToTranslate = dict->getWord();
		translate();
		break;
	    case K_RIGHT:
	    case K_DOWN:
		dict->nextWord();
		wordToTranslate = dict->getWord();
		translate();
		break;
	    default:
		break;
	}
	switch(e->key())
	{
	    case Key_Return:
	    case Key_F33:
		translating = false;
		pickUpSettings();
		break;
	    case Key_Backspace:
		wordToTranslate.replace(QRegExp(".$"),"");
		translate();
		break;
	    default:
		wordToTranslate.append(e->text());
		translate();
		break;
	}
	e->accept();
	return;
    }
    if (e->key() == config.keys.scroll)
    {
        toggleScrolling();
	return;
    } 
    if (!scrolling)
    {
	if (e->key() == config.keys.rotate)
	{
	    config.display.rotation -= 90;
	    if (config.display.rotation < -90)
	    {
		config.display.rotation = 180;
	    }
	    pickUpSettings();
	    return;
	} else if (e->key() == config.keys.bar)
	{
	    config.display.barVisible = !config.display.barVisible;
	    pickUpSettings();
	    return;
	} else if (e->key() == config.keys.fullScreen)
	{
	    emit toggleFullScreen();
	    return;
	} else if (e->key() == config.keys.back)
	{
	    goLinkBack();
	    return;
	} else if (e->key() == config.keys.find)
	{
	    findWord();
	    return;
	} else if (e->key() == config.keys.fileopen)
	{
	    emit fileOpenSignal();
	    return;
	} else if (e->key() == config.keys.settings)
	{
	    emit settingsSignal();
	    return;
	} else if (e->key() == config.keys.bookmark)
	{
	    emit bookmarkSignal();
	    return;
	} else {
	    e->ignore();
	}
    }
    switch(key)
    {
        case K_UP:
	    if (scrolling)
	    {
		config.display.scrollDelay -= 5;
		if (config.display.scrollDelay<5) config.display.scrollDelay = 5;
		break;
	    }
	    if (docPosition == 0) break;
	    readPrevPage();
	    break;
	case K_DOWN:
		if (elapsedtime()<500) return;
	    if (scrolling)
	    {
		config.display.scrollDelay += 5;
		if (config.display.scrollDelay>1000) config.display.scrollDelay = 1000;
		break;
	    }
	    if (stream->atEnd()) break;
	    readNextPage();
	    break;
	case K_LEFT:
	    if (scrolling) return;
	    --config.display.fontSize;
	    if (config.display.fontSize < 8) config.display.fontSize = 8;
	    fontchanged = true;
	    break;
	case K_RIGHT:
	    if (scrolling) return;
	    ++config.display.fontSize;
	    if (config.display.fontSize > MAX_FONT_SIZE) config.display.fontSize = MAX_FONT_SIZE;
	    fontchanged = true;
	    break;
	default:
	    if (scrolling) return;
	    break;
    }
    if (scrolling) return;
    if (fontchanged) pickUpSettings();
}

void Control::mousePressEvent ( QMouseEvent * e )
{
    if (scrolling) return;
    if (translating)
    {
	    translating = false;
	    pickUpSettings();
	    return;
    }
    int x,y;
    if (config.display.rotation == 0)
    {	
	x = e->x();
	y = e->y();
    } else if (config.display.rotation == -90)
    {
        x = height() - e->y();
        y = e->x();
    } else if (config.display.rotation == 90)
    {
        x = e->y();
        y = width() - e->x();
    } else
    {
        x = width() - e->x();
        y = height() - e->y();
    }
    actionForXY(x,y);
}

void Control::paintEvent(QPaintEvent *)
{
    showIt();
}

void Control::resizeEvent(QResizeEvent *)
{
    pickUpSettings();
}

void Control::resize()
{
    pickUpSettings();
}

void Control::pickUpSettings()
{
    if ((oldDocFormat != config.format.docFormat) || (oldPalmFormat != config.format.palmFormat))
    {
	oldDocFormat = config.format.docFormat;
	oldPalmFormat = config.format.palmFormat;
	if (fileName)
	{
	    readBook(fileName);
	    gotoPosition(docPosition);
	}
    }
    if (format == FORMAT_PALM_PLUCKER)
    {
	if (config.format.reorderPlucker != stream->reorderPlucker)
	{
	     stream->reorderPlucker = config.format.reorderPlucker;
	     readBook(fileName);
	     gotoPosition(0);
	}
    }
    setSettings();
    if (!stream) return;
    pagesCacheSize = 0;
    getThisPageAttrs();
    readNextPage();
}

void Control::setSettings()
{
    frame->setRotation(int(config.display.rotation));
    frame->setSize(paintDevice->width(),paintDevice->height());
    showIt();
    frame->setFont(QFont(config.display.fontName,config.display.fontSize,config.display.fontBold?QFont::Bold:QFont::Normal,config.display.fontItalic));
    frame->fakeBold = config.display.fakeBold;
    frame->setDefaultFont(QFont(config.display.fontName,config.display.fontSize,config.display.fontBold?QFont::Bold:QFont::Normal,config.display.fontItalic));
    frame->setProgressBarVisible(config.display.barVisible,config.display.barPercent);
    frame->setSpacing(config.display.spacing);
    frame->setMargin(config.display.margin);
    frame->setIndentPoints(config.display.indentPoints);
    frame->imageAsWord = config.display.imageAsWord;
    if (config.display.imageScaling<10) config.display.imageScaling = 10;
    frame->setImageScaling(double(config.display.imageScaling)/100);
    frame->setBgColor(config.colors.bgColor);
    frame->setHighlightColor(config.colors.highlightColor);
    
    
    if(config.misc.intDict)
    {
	if (config.misc.dictionary != oldDictionary)
	{
	    if (dict) dict->~IntDict();
	    dict = 0;
	}
	if (!dict) 
	{
	    dict = new IntDict();
	    dict->setDictName(config.misc.dictionary);
	    oldDictionary = config.misc.dictionary;
	}
    } else {
	if (dict)
	{
	    dict->~IntDict();
	    dict = 0;
	}
    }
    if (config.display.overlap > 10) config.display.overlap = 10;
    if ((config.display.defaultAlign < 0) || (config.display.defaultAlign>ALIGN_JUSTIFY)) config.display.defaultAlign = ALIGN_LEFT;
    config.display.align = config.display.defaultAlign;
    if (!stream) return;
    stream->reparagraph = config.format.reparagraph;
    stream->stripCR = config.format.stripCR;
    stream->setEncoding(config.format.encoding);    
    stream->removeSpacePads = config.format.removeSpacePads;
    //if (!stream->fileOpened) return;
}

void Control::fileOpen(QString f)
{
    if (readBook(f))
    {
	gotoPosition(0);
	readNextPage();
    }
}

void Control::resume()
{
    if (readBook(fileName))
    {
	gotoPosition(docPosition);
    	pickUpSettings();
    }
}

bool Control::readBook(QString b)
{
    QString tempFile;
    Stream *tempStream;
    tempFile = b;
    QString ext;
    ext = QFileInfo::QFileInfo(b).extension(false).lower();
    if ((ext == "gz") || (ext == "zip"))
    {
	Unzip u(b);
	if (ext == "gz")
	{
	    if (!u.ungzip())
	    {
		QMessageBox::information(this,"Warining!","Error openning .gz file!");
		return false;
	    }
	} else {
	    if (!u.uncompress())
	    {
		QMessageBox::information(this,"Warining!","Error openning .zip file!");
		return false;
	    }
	}
	tempFile = u.uncompressed;
    }
    setFileFormat(tempFile);
    switch(format)
    {
	case FORMAT_HTML:
	    tempStream = new StreamHTML();
	    break;
	case FORMAT_PALM_PLUCKER:
	    tempStream = new StreamPlucker();
	    tempStream->reorderPlucker = config.format.reorderPlucker;
	    break;
	case FORMAT_PALM_DOC:
	    tempStream = new StreamPalmDoc();
	    break;
	case FORMAT_PALM_TXT:
	    tempStream = new StreamPalmTXT();
    	    tempStream->removeSpacePads = config.format.removeSpacePads;
	    break;
	case FORMAT_PALM_HTML:
	    tempStream = new StreamPalmHTML();
	    break;
	case FORMAT_PALM_MOBIPOCKET:
	    tempStream = new StreamPalmMP();
	    break;
	case FORMAT_TXT:
	default:
	    tempStream = new StreamTXT();
	    break;
    }
    //stream->closeFile();
    if (tempStream->openFile(tempFile))
    {
	if (stream) stream->~Stream();
	stream = tempStream;
	fileName = b;
	frame->clearImage();
	pageSize = 0;
	newParagraph = false;
	newLine = true;
	pagesCacheSize = 0;
	pagesCachePointer = 0;
	tagApplied = true;
	docIsOpen = true;
	parent->setCaption("JustReader - "+QFileInfo::QFileInfo(b).fileName());
	setSettings();
	return true;
    }
    QMessageBox::information(this,"Warining!","File is missing, damaged\nor not recognized!");
    tempStream->~Stream();
    return false;
}

void Control::setFileFormat(QString f)
{
    QString ext;
    ext = QFileInfo::QFileInfo(f).extension(false).lower();
    if ((ext == "htm") || (ext == "html"))
    {
	if (config.format.docFormat) format = config.format.docFormat;
	else format = FORMAT_HTML;
	return;
    }
    if (ext == "xml")
    {
	if (config.format.docFormat) format = config.format.docFormat;
	else format = FORMAT_XML;
	return;
    }
    if ((ext == "prc") || (ext == "pdb"))
    {
	format = config.format.palmFormat;
	return;
    }
    if (config.format.docFormat) format = config.format.docFormat;
    else format = FORMAT_TXT;
}

void Control::getLine()
{
    bool firstWord;
    tagsOnly = true;
    frame->setIndent(newLine);
    //if (stream->tag.pre) frame->setIndent(false);
    //if (stream->tag.pre&!config.format.htmlReparagraph) frame->setIndent(false);
    if (stream->tag.pre&!config.format.reparagraph) frame->setIndent(false);
    applyTag();
    if (newParagraph)
    {
	newParagraph = false;
	tagsOnly = false;
	if (config.format.separateParagraphs)
	{
	    frame->wordIn("");
	    return;
	}
    }
    firstWord = true;
    while (feedLine(firstWord))
    {
	firstWord = false;
    }
}

bool Control::feedLine(bool firstWord)
{
    QString w;
    QChar ch;
    int pos;
    bool accepted;
    if (!config.display.imageAsWord)
    {
	if (stream->tag.img)
	{
	    if (!frame->imgIn(link2filePath(stream->tag.imgSrc),stream->tag.imgAlt,frame->imageHeightProcessed)) stream->tag.img = false;
	}
	if (frame->processingImage())
	{
	    tagsOnly = false;
	    if (frame->imageHeightProcessed > -1) return false;
	    tagsOnly = true;
	    stream->tag.img = false;
	    return false;
	}
    }
    pos = stream->getPosition();
    if (stream->atEnd())
    {
	newLine = true;
	return false;
    }
    w = stream->readWordForward(true);
    if (!frame->lineHeightFit) return false;
    if (stream->tagChanged)
    {
	if (stream->tag.a) applyTag();
	if (stream->tag.img)
	{
	    if (config.display.imageAsWord)
	    {
		if (!tagApplied) applyTag();
		stream->tag.img = false;
		if (frame->imgIn(link2filePath(stream->tag.imgSrc),stream->tag.imgAlt,0))
		{
		    tagsOnly = false;
		    return true;
		} else {
		    stream->setPosition(pos);
		    return false;
		}
	    } else {
		newLine = true;
		return false;
	    }
	}
	if (stream->checkNewParagraph())
	{
	   newParagraph = true;
	   newLine = true;
	   stream->checkNewLine();
	   return false;
	}
	if (stream->checkNewLine())
	{
	    newLine = true;
	    return false;
	}
	tagApplied = false;
	return true;
    }
    newLine = false;
    newParagraph = false;
    if (!tagApplied) applyTag();
    accepted = frame->wordIn(w);
    if (accepted)
    {
	tagsOnly = false;
	return true;
    } else {
	stream->setPosition(pos);
    }
    if (firstWord) {
	tagsOnly = false;
	while (1)
	{
	    pos = stream->getPosition();
	    ch = stream->readNextChar();
	    if (stream->atEnd()) break;
	    if (stream->tagChanged)
	    {
		stream->setPosition(pos);
		tagApplied = false;
		break;
	    }
	    //if (ch.isSpace() && !stream->tag.pre) continue;
	    //if (ch.isSpace() && !(stream->tag.pre&!config.format.htmlReparagraph)) continue;
	    if (ch.isSpace() && !(stream->tag.pre&!config.format.reparagraph)) continue;
	    accepted = frame->charIn(ch);
	    if (accepted)
	    {
	        continue;
	    }
	    stream->setPosition(pos);
	    break;
	}
    }
    return false;
}

bool Control::prepareNextPage()
{
    if (stream->atEnd()) return false;
    docPosition = stream->getPosition();
    setThisPageAttrs();
    setPageCacheAttrs();
    linesPointer = 0;
    while (frame->lineHeightFit)
    {
	if (stream->atEnd()) break;
	setThisLineAttrs(linesPointer);
	thisLineAlign = config.display.align;
	getLine();
	if (frame->lineHeightFit)
	{
	    if ((newLine) && (thisLineAlign == ALIGN_JUSTIFY)) thisLineAlign = ALIGN_LEFT;
	    frame->setLineAlign(thisLineAlign);
	    if (!tagsOnly) frame->printLine();
	} else {
	    getThisLineAttrs(linesPointer-config.display.overlap);
	}
	++linesPointer;
    }

    return true;
}

void Control::readNextPage()
{
    if (!stream->fileOpened) return;
    //docPosition = stream->getPosition();
    int s;
    frame->clear(true);
    s = stream->getPosition();
    int page = stream->getPageNumber();
    if (prepareNextPage())
    {
	frame->setProgressBarProps(config.colors.barFgColor,config.colors.barBgColor,config.colors.barTextColor,stream->getPosition()*100/stream->docSize,page,stream->numberOfPages);
	showIt();
	//frame->printPage(paintDevice);
	//frame->clear(true);
	pageSize = stream->getPosition() - s;
    }
}

void Control::showIt()
{
    frame->printPage(paintDevice,0);
}

void Control::startTimer(int t)
{
    timerStatus = true;
    QTimer::singleShot( t, this, SLOT(timerStopped()));
}

void Control::timerStopped()
{
    timerStatus = false;
}

void Control::delay()
{
    while (timerStatus) { qApp->processEvents();}
}

void Control::toggleSuspend(bool enable)
{
#ifndef __FOR_QT__
    QCopEnvelope e("QPE/System", "setScreenSaverIntervals(int,int,int)" );
    if (enable)
    {
	//printf ("Enabling suspend with %d %d %d\n",config.display.intervalDim,config.display.intervalLightOff,config.display.intervalSuspend);
	e << config.display.intervalDim << config.display.intervalLightOff << config.display.intervalSuspend;
    } else {
	//printf ("Disabling suspend\n");
	e << 0 << 0 << 0;
    }
#endif
}

void Control::toggleScrolling()
{
    if (scrolling)
    {
	stopScrolling = true;
	return;
    }
    pagesCacheSize = 0;
    int pageHeight;
    stopScrolling = false;
    scrolling = true;
    // Disable Suspend, Dim and LightOff intervals for scrolling
    toggleSuspend(false);
 
    frame->setProgressBarVisible(false,config.display.barPercent);
    pageHeight = frame->getPageHeight();
    frame->createScrollBuffer();
    getThisPageAttrs();
    frame->clear(true);
    linesPointer = 0;
    startTimer(config.display.scrollDelay);
    while (1)
    {
	if (stream->atEnd()) break;
	setLineAttrs();
	setThisLineAttrs(linesPointer);
	thisLineAlign = config.display.align;
	getLine();
	if (frame->lineHeightFit)
	{
	    if ((newLine) && (thisLineAlign == ALIGN_JUSTIFY)) thisLineAlign = ALIGN_LEFT;
	    frame->setLineAlign(thisLineAlign);
	    if (!tagsOnly) 
	    {
		    frame->printLine();
		    linesHeights[linesPointer] = frame->getLineHeight();
	    } else {
		    linesHeights[linesPointer] = 0;
	    }
	    ++linesPointer;
	} else {
	    int j,sumShift;
	    sumShift = 0;
	    int i = 0;
	    while (sumShift<=pageHeight)
	    {
		j = linesHeights[i];
		for (int k = 0;k<j;k++)
		{
		    delay();
		    frame->printPage(paintDevice,sumShift + k + 1); 
		    startTimer(config.display.scrollDelay);
		    if (k<10) continue;	//Continue scrolling if less than 5 points left in the line
		    if (stopScrolling) break;
		}
		sumShift += j;
		++i;
		if (stopScrolling) break;
	    }
	    getThisLineAttrs(i);
	    linesPointer = 0;
	    frame->clear(true);
	    if (stopScrolling) break;
	}
    }
    // Re-enable Suspend, Dim and LightOff intervals
    toggleSuspend(true);
    frame->destroyScrollBuffer();
    frame->setProgressBarVisible(config.display.barVisible,config.display.barPercent);
    stream->checkNewLine();
    stream->checkNewParagraph();
    readNextPage();
    scrolling = false;
}

int Control::getBookPosition()
{
    return thisPageAttrs.position;
}

void Control::readPrevPage()
{
    if (!stream->fileOpened) return;
    int lastPos,pos;
    bool found;
    QString imgSrc = "";
    int imgH = -1;
    if (thisPageAttrs.tag.img)
    {
	imgSrc=thisPageAttrs.tag.imgSrc;
        imgH = thisPageAttrs.imageHeightProcessed;
    }
    int pageHeight, sumLineHeight;
    frame->clearImage();
    //frame->imageHeightProcessed = -1;
    if (!getPageCacheAttrs())
    {
	if (thisPageAttrs.position == 0) return;
	found = false;
	lastPos = thisPageAttrs.position;
	linesPointer = 0;
	if (pageSize<750) pos = lastPos - 2500;
	else pos = lastPos - (pageSize+pageSize);
	pos = lastPos - 2500;
	if (pos < 0) pos = 0;
	stream->resetTags();
	stream->setPosition(pos);
	//findLastAttrs();
	while(stream->getPosition() <= lastPos)
	{
	    setThisLineAttrs(linesPointer);
	    frame->clear(false);
	    getLine();
	    if (!tagsOnly)
	    {
		linesHeights[linesPointer] = frame->getLineHeight();
	    } else {
	    	linesHeights[linesPointer] = 0; 
	    }
	    ++linesPointer;
	    if (stream->getPosition() == lastPos)
	    {
		if (stream->tag.img)
		{
		    if (stream->tag.imgSrc == imgSrc)
		    {
			if (frame->imageHeightProcessed >= imgH)
			{
			    ++linesPointer;
			    break;
			}
		    }
		}
	    }
	}
	frame->clearImage();
	--linesPointer;
	pageHeight = frame->getPageHeight();
	sumLineHeight = 0;
	while (sumLineHeight <= pageHeight)
	{
	    --linesPointer;
	    if (linesPointer<0) break;
	    sumLineHeight += linesHeights[linesPointer];
	}
	++linesPointer;
	if (linesPointer <0) linesPointer = 0;
	getThisLineAttrs(linesPointer);
	frame->clear(false);
    }
    //frame->setIndent(newParagraph);
    readNextPage();
}

void Control::findLastAttrs()
{
    //return;
    int p,b;
    QChar ch;
    //bool l,g = false;;
    p = stream->getPosition();
    b = p - 200;
    if (b<0) b = 0;
    stream->setPosition(b);
    while (stream->getPosition()<p)
    {
	ch = stream->readNextChar();
	if (!stream->tagChanged)
	{
	    newLine = newParagraph = false;
	}
	if (stream->checkNewLine()) newLine = true;
	if (stream->checkNewParagraph()) newParagraph = true;
    }
    newParagraph = false;
    setThisPageAttrs();
    applyTag();
    stream->setPosition(p);
}

void Control::applyTag()
{
    frame->setBold(stream->tag.b|stream->tag.strong|stream->tag.em|stream->tag.h|config.display.fontBold);
    frame->setItalic(stream->tag.i|stream->tag.code|stream->tag.em|stream->tag.dfn|stream->tag.var|stream->tag.cite|config.display.fontItalic);
    frame->setStrikeOut(stream->tag.del|stream->tag.strike);
    frame->setUnderline(stream->tag.u|stream->tag.a);
    frame->setFontFixedWidth(stream->tag.tt|stream->tag.kbd|stream->tag.samp);
    //if (stream->tag.pre) {config.display.align = ALIGN_LEFT;} else { config.display.align = config.display.defaultAlign;}
    //if (stream->tag.pre&!config.format.htmlReparagraph) {config.display.align = ALIGN_LEFT;} else { config.display.align = config.display.defaultAlign;}
    if (stream->tag.pre&!config.format.reparagraph) {config.display.align = ALIGN_LEFT;} else { config.display.align = config.display.defaultAlign;}
    if (stream->tag.a)
    {
	frame->setPenColor(config.colors.linkColor);
	frame->setHref(stream->tag.href);
    } else {
	frame->setPenColor(config.colors.normalColor);
	frame->setHref("");
    }
    frame->setFontSize(config.display.fontSize + stream->tag.h + (stream->tag.big?2:0) - (stream->tag.small?2:0));
    frame->applyFont();
    tagApplied = true;
}


void Control::setThisLineAttrs(int i)
{
    linesAttrCache[i].position = stream->getPosition();
    linesAttrCache[i].newParagraph = newParagraph;
    linesAttrCache[i].align = config.display.align;
    linesAttrCache[i].tag = stream->tag;
    linesAttrCache[i].imageHeightProcessed = frame->imageHeightProcessed;
    linesAttrCache[i].newLine = newLine;
}

void Control::getThisLineAttrs(int i)
{
    stream->setPosition(linesAttrCache[i].position);
    newParagraph = linesAttrCache[i].newParagraph;
    config.display.align = linesAttrCache[i].align;
    stream->tag = linesAttrCache[i].tag;
    frame->imageHeightProcessed = linesAttrCache[i].imageHeightProcessed;
    newLine = linesAttrCache[i].newLine;
    applyTag();
}

void Control::setLineAttrs()
{
    thisLineAttrs.position = stream->getPosition();
    thisLineAttrs.newParagraph = newParagraph;
    thisLineAttrs.align = config.display.align;
    thisLineAttrs.tag = stream->tag;
    thisLineAttrs.imageHeightProcessed = frame->imageHeightProcessed;
    thisLineAttrs.newLine = newLine;
}

void Control::getLineAttrs()
{
    stream->setPosition(thisLineAttrs.position);
    newParagraph = thisLineAttrs.newParagraph;
    config.display.align = thisLineAttrs.align;
    stream->tag = thisLineAttrs.tag;
    frame->imageHeightProcessed = thisLineAttrs.imageHeightProcessed;
    newLine = thisLineAttrs.newLine;
    applyTag();
}

void Control::setThisPageAttrs()
{
    thisPageAttrs.position = stream->getPosition();
    thisPageAttrs.newParagraph = newParagraph;
    thisPageAttrs.align = config.display.align;
    thisPageAttrs.tag = stream->tag;
    thisPageAttrs.imageHeightProcessed = frame->imageHeightProcessed;
    thisPageAttrs.newLine = newLine;
}

void Control::getThisPageAttrs()
{
    stream->setPosition(thisPageAttrs.position);
    newParagraph = thisPageAttrs.newParagraph;
    config.display.align = thisPageAttrs.align;
    stream->tag = thisPageAttrs.tag;
    frame->imageHeightProcessed = thisPageAttrs.imageHeightProcessed;
    newLine = thisPageAttrs.newLine;
    applyTag();
}

void Control::setPageCacheAttrs()
{
    pagesAttrsCache[pagesCachePointer] = thisPageAttrs;
    ++pagesCachePointer;
    if (pagesCachePointer == maxPagesCacheSize) pagesCachePointer = 0;
    ++pagesCacheSize;
    if (pagesCacheSize > maxPagesCacheSize) pagesCacheSize = maxPagesCacheSize;
}

bool Control::getPageCacheAttrs()
{
    --pagesCachePointer;
    --pagesCachePointer;
    if (pagesCachePointer < 0) pagesCachePointer += maxPagesCacheSize;
    --pagesCacheSize;
    --pagesCacheSize;
    if (pagesCacheSize < 0)
    {
	pagesCacheSize = 0;
	return false;
    }
    stream->setPosition(pagesAttrsCache[pagesCachePointer].position);
    newParagraph = pagesAttrsCache[pagesCachePointer].newParagraph;
    config.display.align = pagesAttrsCache[pagesCachePointer].align;
    stream->tag = pagesAttrsCache[pagesCachePointer].tag;
    frame->imageHeightProcessed = pagesAttrsCache[pagesCachePointer].imageHeightProcessed;
    newLine = pagesAttrsCache[pagesCachePointer].newLine;
    applyTag();
    return true;
}

void Control::actionForXY(int x,int y)
{
    QString l,link,linkedFile,newFile;
    int p;
    int t;
    if (frame->inBarArea(y))
    {
	gotoPosition(int(stream->docSize*frame->barPartForX(x)));
	readNextPage();
	return;
    }
    l = frame->wordPropsForXY(x,y,1);
    if (l.length() > 0)
    {
	linkedFile = "";
	link = "";
	t = l.find('#');
	if (t>-1)
	{
	    linkedFile = l.left(t);
	    link = l.mid(t+1);
	} else {
	    linkedFile = l;
	}
        linksCache[linksCachePointer].fileName = fileName;
        linksCache[linksCachePointer].attrs = thisPageAttrs;

	if (linkedFile.length()>0)
	{
	    newFile = link2filePath(linkedFile);
	    if (!readBook(newFile))
	    {
		return;
	    }
	}
	p = -1;
	if (link.length() > 0) p = stream->getInLinkPosition(link);
	if (p<0) p = 0;
	++linksCachePointer;
	if (linksCachePointer>=maxLinksCacheSize) linksCachePointer = 0;
	++linksCacheSize;
	if (linksCacheSize>maxLinksCacheSize) linksCacheSize = maxLinksCacheSize;
	goBackAction->setEnabled(true);
	gotoPosition(p);
	readNextPage();
    } else {
    	l = frame->wordPropsForXY(x,y,0);
	if (l.length()<1) return;
	wordToTranslate = l;
	translate();
    }
}

void Control::translate()
{
    if (!dict) return;
    translating = true;
    wordToTranslate.replace(QRegExp("^[!\"£$%^&*()\\-_+=¬`:;@'~#|<,>.?/{}\\[\\]\\d\\\\]+"),"");
    wordToTranslate.replace(QRegExp("[!\"£$%^&*()\\-_+=¬`:;@'~#|<,>.?/{}\\[\\]\\d\\\\]+$"),"");
    dict->findWord(wordToTranslate);
    if (dict->getError() == "")
    {
	//wordToTranslate = dict->getWord();
	wordSense = dict->getSense();
	wordInfo = dict->getInfo();
    } else {
	wordInfo = "Error for dictionary: "+config.misc.dictionary+" -> "+dict->getError();
	wordSense = "";
    }
    showTranslation();
}

void Control::showTranslation()
{
    QFont defaultFont = QApplication::font();
    
    frame->setLineAlign(ALIGN_LEFT);
    frame->clear(true);
    frame->setIndent(false);
    //frame->setFontSize(10); 
    //frame->setBold(false);
    //frame->setItalic(false);
    
    frame->setFont(defaultFont);
    frame->setFontSize(defaultFont.pointSize() - 1);
    frame->setPenColor(config.colors.normalColor);
    frame->applyFont();
    stringToFrame("'Tap' or press 'Ok' to exit, Left/Right - previous/next word, Backspace and letters to edit the WORD");
    frame->printLine();
    frame->setPenColor(config.colors.linkColor);
    frame->setFontSize(defaultFont.pointSize());
    frame->applyFont();
    stringToFrame(wordToTranslate);
    frame->setPenColor(config.colors.normalColor);
    frame->applyFont();
    frame->printLine();
    stringToFrame(wordInfo);
    stringToFrame(wordSense);
    showIt();
    frame->setLineAlign(config.display.defaultAlign);
}

void Control::stringToFrame(QString s)
{
    QChar ch;
    QString tag;
    bool tagStart;
    unsigned int i,l;
    l = s.length();
    i = 0;
    while (1)
    {
	if (i>= l) break;
	ch = s.at(i);
	if (ch == '<')
	{
	    tagStart = true;
	    tag = "";
	    ++i;
	    ch = s.at(i).lower();
	    if (ch == '/')
	    {
	        tagStart = false;
	        ++i;
		ch = s.at(i).lower();
	    }
	    while (s.at(i) != '>')
	    {
		tag += s.at(i);
		++i;
		if (i>=l) break;
	    }
	    if (tag == "b")
	    {
		frame->wordCounterUp();
	        frame->setBold(tagStart);
		frame->applyFont();
	    } else if (tag == "i") {
		frame->wordCounterUp();
	        frame->setItalic(tagStart);
		frame->applyFont();
	    } else if (tag == "p") {
	        while(frame->charIn(' ')) continue;
		//frame->wordCounterUp();
	        frame->printLine();
		frame->printLine();
	        frame->setIndent(tagStart);
	    }
	    ++i;
	    continue;
    
	} else {
	    if (frame->charIn(ch))
	    {
		++i;
		continue;
	    }
	}
	frame->printLine();
    }
    while(frame->charIn(' ')) continue;
    //frame->wordCounterUp();
    frame->printLine();
} 

void Control::gotoPosition(int p)
{
    if (!stream->fileOpened) return;
    if (p > stream->getSize()) p = 0;
    stream->setPosition(p);
    pagesCacheSize = 0;
    stream->resetTags();
    findLastAttrs();
    frame->clearImage();
    frame->imageHeightProcessed = -1;
    setThisPageAttrs();
}

QString Control::link2filePath(QString l)
{
    if (l.find(QRegExp("^file:"))>-1)
    {
	l = l.mid(5);
    }
    if (l.at(0) == '/')
    {
	return l;
    }
    return stream->getFileDir()+"/"+l;
}

void Control::findWord()
{
    int p, foundPos;
    bool found = false;
    bool i;
    QString w;
    QDialog * find = new QDialog(this, 0,true,WStyle_Customize | WStyle_NormalBorder | WStyle_Title | WStyle_SysMenu);
    //QDialog * find = new QDialog(this, 0,true,WStyle_Customize | WStyle_NormalBorder | WStyle_Title | WStyle_Dialog | WStyle_SysMenu);
    find->setCaption(tr("Search text"));
    find->setSizeGripEnabled(true);
    
    
    QGridLayout* layout = new QGridLayout(find, 4, 1, 5, 5);
    
    //QVBox * boxAll = new QVBox(find);
    //QHBox * boxWord = new QHBox(boxAll);
    //find->resize(150,100);
    QLineEdit * wordToSearch = new QLineEdit(find);
    wordToSearch->setMinimumWidth(QApplication::desktop()->width() / 3);
    layout->addWidget(wordToSearch, 0, 0);
    
    //wordToSearch->setFont(QFont::QFont(config.display.fontName,12,QFont::Normal,false));
    //QVBox * boxOptions = new QVBox(boxAll);
    QCheckBox * caseIgnore = new QCheckBox(tr("Ignore case"), find, 0);
    caseIgnore->setChecked(true);
    layout->addWidget(caseIgnore, 1, 0);
    
    QCheckBox * fromStart = new QCheckBox(tr("From beginning"), find, 0);
    fromStart->setChecked(false);
    layout->addWidget(fromStart, 2, 0);
    
    wordToSearch->setText(word2search);
    
    QPushButton* b = new QPushButton(tr("Search"), find);
    layout->addWidget(b, 3, 0);
    b->setDefault(true);
    connect(b, SIGNAL(clicked()), find, SLOT(accept()));
    
    //boxAll->resize(find->size());
    if (!find->exec()) 
    {
        frame->highlightWord(-1);
        return;
    }
    word2search = wordToSearch->text();
    p = thisPageAttrs.position + 1;
    foundPos = p;
    if (fromStart->isChecked()) stream->setPosition(0);
    else stream->setPosition(p);
    i = caseIgnore->isChecked();
    find->~QDialog();
    if (wordToSearch->text().isEmpty())
    {
        frame->highlightWord(-1);
    	readNextPage();
    	return;
    }
    while(!found)
    {
	if (stream->atEnd()) break;
	foundPos = stream->getPosition();
	w = stream->readWordForward(true);
	if (w == "") continue;
	if (i)
	{
	    if (w.lower().find(QRegExp(word2search.lower()),0) < 0) continue;
	} else {
	    if (w.find(QRegExp(word2search),0) < 0) continue;
	}
	if (int(foundPos) < int((lastFoundPos+w.length())))
	{
	    //stream->setPosition(foundPos+1);
	    continue;
	}
	lastFoundPos = foundPos;
	found = true;
	break;
    }
    if (found)
    {
    	//stream->resetTags();
    	gotoPosition(foundPos);
    	stream->tag.img = false;
    	frame->clearImage();
        frame->highlightWord(0);
    	readNextPage();
	} else {
    	stream->setPosition(p - 1);
    	QMessageBox::information(this, tr("Oops!"), tr("Word not found!"));
    }
    findWord();
}

void Control::goLinkBack()
{
    --linksCacheSize;
    if (linksCacheSize<0)
    {
	linksCacheSize = 0;
	return;
    }
    --linksCachePointer;
    if (linksCachePointer<0) linksCachePointer = maxLinksCacheSize - 1;
    if (fileName != linksCache[linksCachePointer].fileName)
    {
    	readBook(linksCache[linksCachePointer].fileName);
    }
    thisPageAttrs = linksCache[linksCachePointer].attrs;
    if (linksCacheSize == 0) goBackAction->setEnabled(false);
    gotoPosition(thisPageAttrs.position);
    pickUpSettings();
}

QString Control::getBookmarkName()
{
    setLineAttrs();
    stream->setPosition(docPosition);
    QString n;
    n = "";
    for (int i=0;i<5;i++)
    {
	n += stream->readWordForward(true);
	n += " ";
    }
    getLineAttrs(); 
    return n;
}

int Control::getPageNumber()
{
    int p;
    int page;
    p = stream->getPosition();
    stream->setPosition(docPosition);
    page = stream->getPageNumber();
    stream->setPosition(p);
    return page;
}

void Control::generateContents(QString filename)
{
    QFile f;
    QString s;
    int progress,prevPr;
    QProgressBar *b = new QProgressBar(parent);
    b->setTotalSteps(100);
    b->resize(parent->width(),20);
    b->move(0,parent->height()-20);
    b->show();
    b->raise();
    f.setName(filename);
    if (!f.open(IO_WriteOnly)) return;
    setLineAttrs();
    QTextStream ts(&f);
    ts.setEncoding(QTextStream::UnicodeUTF8);
    stream->setPosition(0);
    prevPr = -1;
    while (1)
    {
	progress = stream->getPosition()*100/stream->getSize();
	if (prevPr != progress)
	{
	    prevPr = progress;
	    b->setProgress(progress);
	    qApp->processEvents();
	}
	s = stream->findNextContent();
	if (s == "") break;
	ts.operator<<(s);
    }
    b->hide();
    parent->setCentralWidget(this);
    setFocus();
    b->~QProgressBar();
    f.close();
    getLineAttrs();
}
