/* ****************************************************************************
  This file is part of KBabel

  Copyright (C) 1999-2000 by Matthias Kiefer
                            <matthias.kiefer@gmx.de>
		2001-2003 by Stanislav Visnovsky
			    <visnovsky@kde.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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

**************************************************************************** */
#include <qtextstream.h>
#include <qfile.h>
#include <qdir.h>
#include <qfileinfo.h>
#include <qregexp.h>
#include <qstring.h>
#include <qtextcodec.h>
#include <qdatetime.h>

#include <kconfig.h>
#include <kdatatool.h>
#include <kglobal.h>
#include <klibloader.h>
#include <klocale.h>
#include <kmessagebox.h>
#include <kmimetype.h>
#include <kapplication.h>
#include <kio/netaccess.h>
#include <krfcdate.h>
#include <ktrader.h>
#include <kurl.h>

#include "catalog.h"
#include "catalog_private.h"
#include "catalogitem.h"
#include "diff.h"
#include "findoptions.h"
#include "catalogview.h"
#include "editcmd.h"

#include "resources.h"
#include "version.h"

// from libgettext
extern "C"
{
#include "libgettext/fstrcmp.h"
}

using namespace KBabel;

Catalog::Catalog(QObject* parent, const char* name, QString configFile)
        : QObject(parent,name)
{
   d = new CatalogPrivate();

   d->_configFile=configFile;
   KConfig *config;
   bool bConfigMustBeDeleted = false;
   if(d->_configFile.isEmpty() )
   {
       config = KGlobal::config();
   }
   else
   {
       config=new KConfig(d->_configFile);
       bConfigMustBeDeleted = true;
   }
   readPreferences(config);
   if (  bConfigMustBeDeleted )
       delete config;
}

Catalog::Catalog(const Catalog& c): QObject(c.parent(),c.name()
)
{
   kdFatal() << "Copy constructor of Catalog, please report how to reproduce to the authors" << endl;
}

Catalog::~Catalog()
{
    delete d;
}

QStringList Catalog::msgid(uint index, const bool noNewlines) const
{
   if (  d->_entries.isEmpty() )
        return QString::null;
    uint max=d->_entries.count()-1;
   if(index > max)
      index=max;

   return d->_entries[index].msgid(noNewlines);
}

QStringList Catalog::msgstr(uint index, const bool noNewlines) const
{
    if (  d->_entries.isEmpty() )
        return QString::null;

   uint max=d->_entries.count()-1;
   if(index > max)
      index=max;

   return d->_entries[index].msgstr(noNewlines);
}

QString Catalog::comment(uint index) const
{
    if (  d->_entries.isEmpty() )
        return QString::null;
   uint max=d->_entries.count()-1;
   if(index > max)
      index=max;
   return d->_entries[index].comment();
}

QString Catalog::context(uint index) const
{
    QString c = comment(index);

    QStringList lines = QStringList::split("\n",c);

    QString result;
    for( QStringList::Iterator it=lines.begin(); it!=lines.end(); it++)
    {
	if( (*it).startsWith( "#:") )
	{
	    result+=(*it)+"\n";
	}
    }
    return result.stripWhiteSpace();
}

CatalogItem Catalog::header() const
{
   return d->_header;
}

QString Catalog::lastTranslator() const
{
    return headerInfo( d->_header ).lastTranslator;
}

int Catalog::indexForMsgid(const QString& id) const
{
    int i=0;
	QValueVector<CatalogItem>::ConstIterator it = d->_entries.begin();

    while(it != d->_entries.end() && !((*it).msgid().contains(id)))
    {
        ++it;
        i++;
    }

    if(it == d->_entries.end())
        i=-1;

    return i;
}

QStringList Catalog::tagList(uint index)
{
    if (  d->_entries.isEmpty() )
        return QStringList();

   uint max=d->_entries.count()-1;
   if(index > max)
      index=max;

   return d->_entries[index].tagList(*(d->_tagExtractor));
}


QStringList Catalog::argList(uint index)
{
    if (  d->_entries.isEmpty() )
        return QStringList();

   uint max=d->_entries.count()-1;
   if(index > max)
      index=max;

   return d->_entries[index].argList(*(d->_argExtractor));
}


/*
bool Catalog::setMsgstr(uint index,QString msgstr)
{
    kdWarning() << "Catalog::setMsgstr()" << endl;

   bool untranslatedChanged=false;

   if(_entries[index].isUntranslated() && !msgstr.isEmpty())
   {
      _untransIndex.remove(index);
      untranslatedChanged=true;
   }
   else if(msgstr.isEmpty())
   {
      QValueList<uint>::Iterator it;

      // insert index in the right place in the list
      it = _untransIndex.begin();
      while(it != _untransIndex.end() && index > (*it))
      {
         ++it;
      }
      _untransIndex.insert(it,index);

      untranslatedChanged=true;
   }

   _entries[index].setMsgstr(msgstr);

   setModified(true);

   if(untranslatedChanged)
      emit signalNumberOfUntranslatedChanged(numberOfUntranslated());

   return untranslatedChanged;
}
*/

/*
bool Catalog::setComment(uint index,QString comment)
{
    kdWarning() << "Catalog::setComment()" << endl;
   bool fuzziesChanged=false;


   bool wasFuzzy=_entries[index].isFuzzy();

   _entries[index].setComment(comment);

   bool isFuzzy=_entries[index].isFuzzy();

   if(wasFuzzy && !isFuzzy)
   {
      _fuzzyIndex.remove(index);
      fuzziesChanged=true;
   }
   else if(isFuzzy)
   {
      QValueList<uint>::Iterator it;

      // insert index in the right place in the list
      it = _fuzzyIndex.begin();
      while(it != _fuzzyIndex.end() && index > (*it))
      {
         ++it;
      }
      _fuzzyIndex.insert(it,index);

      fuzziesChanged=true;
   }

   setModified(true);

   if(fuzziesChanged)
      emit signalNumberOfFuzziesChanged(numberOfFuzzies());


   return fuzziesChanged;
}
*/

bool Catalog::setHeader(CatalogItem newHeader)
{
   if(newHeader.isValid())
   {
      d->_header=newHeader;
      setModified(true);

      emit signalHeaderChanged();

      return true;
   }

   return false;
}

KURL Catalog::currentURL() const
{
   return d->_url;
}

void Catalog::setCurrentURL(const KURL& url)
{
   d->_url=url;
}


CatalogItem Catalog::updatedHeader(CatalogItem oldHeader, bool usePrefs) const
{
   QStringList headerList=oldHeader.msgstrAsList();
   QStringList commentList=QStringList::split('\n',oldHeader.comment());

   QStringList::Iterator it,ait;
   QString temp;
   bool found;
   if(!usePrefs || d->_saveSettings.updateLastTranslator)
   {
      found=false;

      temp="Last-Translator: "+d->_identitySettings.authorName;
      if(!d->_identitySettings.authorEmail.isEmpty())
      {
         temp+=(" <"+d->_identitySettings.authorEmail+">");
      }
      temp+="\\n";
      for( it = headerList.begin(); it != headerList.end(); ++it )
      {
         if((*it).contains(QRegExp("^ *Last-Translator:.*")))
         {
            (*it).replace(QRegExp("^ *Last-Translator:.*"),temp);
            found=true;
         }
       }
       if(!found)
       {
          headerList.append(temp);
       }
   }
   if(!usePrefs || d->_saveSettings.updateRevisionDate)
   {
      found=false;

      temp="PO-Revision-Date: "+dateTime()+"\\n";

      for( it = headerList.begin(); it != headerList.end(); ++it )
      {
         if((*it).contains(QRegExp("^ *PO-Revision-Date:.*")))
         {
            (*it).replace(QRegExp("^ *PO-Revision-Date:.*"),temp);
            found=true;
         }
       }
       if(!found)
       {
          headerList.append(temp);
       }
   }
   if(!usePrefs || d->_saveSettings.updateProject)
   {
      found=false;

      temp="Project-Id-Version: "+d->_saveSettings.projectString+"\\n";
      temp.replace( "@PACKAGE@", packageName());

      for( it = headerList.begin(); it != headerList.end(); ++it )
      {
         if((*it).contains(QRegExp("^ *Project-Id-Version:.*")))
         {
            (*it).replace(QRegExp("^ *Project-Id-Version:.*"),temp);
            found=true;
         }
       }
       if(!found)
       {
          headerList.append(temp);
       }
   }
   if(!usePrefs || d->_saveSettings.updateLanguageTeam)
   {
      found=false;

      temp="Language-Team: "+d->_identitySettings.languageName;
      if(!d->_identitySettings.mailingList.isEmpty())
      {
         temp+=(" <"+d->_identitySettings.mailingList+">");
      }
      temp+="\\n";
      for( it = headerList.begin(); it != headerList.end(); ++it )
      {
         if((*it).contains(QRegExp("^ *Language-Team:.*")))
         {
            (*it).replace(QRegExp("^ *Language-Team:.*"),temp);
            found=true;
         }
       }
       if(!found)
       {
          headerList.append(temp);
       }
   }
   if(!usePrefs || d->_saveSettings.updateCharset)
   {
      found=false;

	  QString encodingStr;
      if(d->_saveSettings.useOldEncoding && d->fileCodec)
      {
		  encodingStr = charsetString(d->fileCodec);
      }
      else
      {
          encodingStr=charsetString(d->_saveSettings.encoding);
      }
      temp="Content-Type: text/plain; charset="+encodingStr+"\\n";
      QString charsettemp="; charset="+encodingStr+"\\n";

      for( it = headerList.begin(); it != headerList.end(); ++it )
      {
         if((*it).contains(QRegExp("^ *Content-Type:.*;\\s*charset=")))
         {
            (*it).replace(QRegExp(";\\s*charset\\s*=\\s*[^\\\"\\n]+"),charsettemp);
            found=true;
         }
       }
       if(!found)
       {
          headerList.append(temp);
       }
   }
   if(!usePrefs || d->_saveSettings.updateEncoding)
   {
      found=false;

      temp="Content-Transfer-Encoding: 8bit\\n";

      for( it = headerList.begin(); it != headerList.end(); ++it )
      {
         if((*it).contains(QRegExp("^ *Content-Transfer-Encoding:.*")))
         {
            (*it).replace(QRegExp("^ *Content-Transfer-Encoding:.*"),temp);
            found=true;
         }
       }
       if(!found)
       {
          headerList.append(temp);
       }
   }

   temp="X-Generator: KBabel %1\\n";
   temp=temp.arg(VERSION);
   found=false;

   for( it = headerList.begin(); it != headerList.end(); ++it )
   {
      if((*it).contains(QRegExp("^ *X-Generator:.*")))
      {
         (*it).replace(QRegExp("^ *X-Generator:.*"),temp);
         found=true;
      }
    }
    if(!found)
    {
       headerList.append(temp);
    }
    
   // ensure MIME-Version header
   temp="MIME-Version: 1.0\\n";
   found=false;
   for( it = headerList.begin(); it != headerList.end(); ++it )
   {
	if((*it).contains(QRegExp("^ *MIME-Version:")))
    	{
    	    found=true;
	    break;
    	}
   }
   if( !found )
   {
	headerList.append(temp);
   }


   temp="Plural-Forms: %1\\n";
   temp=temp.arg(d->_identitySettings.gnuPluralFormHeader);
   found=false;

   // update plural form header
   if( !d->_identitySettings.gnuPluralFormHeader.isEmpty() )
   {
	for( it = headerList.begin(); it != headerList.end(); ++it )
	{
    	    if((*it).contains(QRegExp("^ *Plural-Forms:")))
    	    {
        	(*it).replace(QRegExp("^ *Plural-Forms:.*"),temp);
        	found=true;
    	    }
	}
    }
    if(!found && !d->_identitySettings.gnuPluralFormHeader.isEmpty() )
    {
       headerList.append(temp);
    }

   QString msgstr;
   for( it = headerList.begin(); it != headerList.end(); ++it )
   {
      msgstr+=("\n"+(*it));
   }

   msgstr.remove(0,1);// remove first newline

   oldHeader.setMsgstr(msgstr);

   //comment = description, copyrights
   if(!usePrefs || (d->_saveSettings.FSFCopyright != NoChange))
   {
      found=false;

      for( it = commentList.begin(); it != commentList.end(); ++it )
      {
         if((*it).contains(QRegExp("^# *Copyright \\(C\\).*Free Software Foundation, Inc")))
         {
            found=true;
	    break;
         }
       }
       if(found)
       {
	    if((*it).contains(QRegExp("^# *Copyright \\(C\\) YEAR Free Software Foundation, Inc\\.")))
	    {
		//template string
    		if(d->_saveSettings.FSFCopyright == Remove) (*it).replace(" YEAR Free Software Foundation, Inc","");
		else (*it).replace("YEAR", QDate::currentDate().toString("yyyy"));
	    } else
	    if( d->_saveSettings.FSFCopyright == Update )
	    {
		    //update years
		    QString cy = QDate::currentDate().toString("yyyy");
		    if( !(*it).contains( QRegExp(cy)) ) // is the year already included?
		    {
			int index = (*it).findRev( QRegExp("[\\d]+[\\d\\-, ]*") );
			if( index == -1 )
			{
			    KMessageBox::information(0,i18n("Free Software Foundation Copyright does not contain any year. "
			    "It will not be updated."));
			} else {
			    (*it).insert(index+1, QString(", ")+cy);
			}
		    }
	    }
	}
   }

   if(!usePrefs || d->_saveSettings.updateDescription)
   {
      temp="# "+d->_saveSettings.descriptionString;
      temp.replace( "@PACKAGE@", packageName());
      temp.replace( "@LANGUAGE@", d->_identitySettings.languageName);

      found=false;
      bool foundTemplate=false;

      for( it = commentList.begin(); it != commentList.end(); ++it )
      {
         if((*it).contains(QRegExp("^"+temp+"$")))
         {
            found=true;
         }
	 if((*it).contains(QRegExp("^# SOME DESCRIPTIVE TITLE.$")))
	 {
	    ait = it;
	    foundTemplate = true;
	 }
       }
       if(foundTemplate) commentList.remove(ait);
       if(!found) commentList.prepend(temp);
   }

   if(!usePrefs || d->_saveSettings.updateTranslatorCopyright)
   {
      QStringList foundAuthors;

      temp="# "+d->_identitySettings.authorName;
      if(!d->_identitySettings.authorEmail.isEmpty())
      {
         temp+=(" <"+d->_identitySettings.authorEmail+">");
      }
      temp+=", "+QDate::currentDate().toString("yyyy")+".";

      for( it = commentList.begin(); it != commentList.end(); ++it )
      {
         if((*it).contains(QRegExp("^#.*<.+@.+>,\\s*([\\d]+[\\d\\-, ]*)|(YEAR)"))) // email address followed by year
         {
	    foundAuthors.append( (*it) );
         }
       }

       for( it = foundAuthors.begin() ; it != foundAuthors.end() ; ++it )
    	    commentList.remove( (*it) );

	it=qFind(foundAuthors.begin(), foundAuthors.end(), QString("# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR."));
	if( it != foundAuthors.end() ) foundAuthors.remove( it );

       if(foundAuthors.size()>0)
       {
          found = false;
	  bool foundAuthor = false;

	  QString cy = QDate::currentDate().toString("yyyy");

	  for( it = foundAuthors.begin() ; it!=foundAuthors.end(); ++it )
	  {
		if( (*it).contains(QRegExp(d->_identitySettings.authorName+".*"
		    +d->_identitySettings.authorEmail)))
		{
		    foundAuthor = true;
		    if( (*it).contains(QRegExp(cy))) found = true;
		    else ait = it;
		}
	   }
	   if( !found )
		if( !foundAuthor ) foundAuthors.append(temp);
		else
		{
		    //update years
		    int index = (*ait).findRev( QRegExp("[\\d]+[\\d\\-, ]*") );
		    if( index == -1 ) (*ait)+=", "+cy;
		    else (*ait).insert(index+1, QString(", ")+cy);
		}

	}
	else foundAuthors.append(temp);
	it=commentList.end();
	do
	    --it;
	while( (*it).contains( QRegExp( "^#\\s*$")) || (*it).contains( QRegExp( "^#[:,\\.]")));
	it++;
	for( ait = foundAuthors.begin() ; ait != foundAuthors.end() ; ++ait )
	{
	    QString s = (*ait);

	    // ensure dot at the end of copyright
	    if( !s.endsWith(".") ) s += ".";
	    commentList.insert(it, s);
	}
   }

   QString comment;
   for( it = commentList.begin(); it != commentList.end(); ++it )
   {
      comment+=("\n"+(*it));
   }

   comment.remove(0,1);// remove first newline

   oldHeader.setComment(comment);

   return oldHeader;
}

void Catalog::setFuzzy(uint index, bool on)
{
    if (  d->_entries.isEmpty() )
        return;

   uint max=d->_entries.count()-1;
   if(index > max)
       return;

   if(d->_entries[index].isFuzzy() != on)
   {
      applyBeginCommand( index, Comment, 0 );

      QPtrList<EditCommand> editList;
      if(on)
      {
          editList=d->_entries[index].addFuzzy(false);
      }
      else
      {
          editList=d->_entries[index].removeFuzzy(false);
          d->_fuzzyIndex.remove(index);
      }

      for ( EditCommand* cmd=editList.first(); cmd != 0; cmd=editList.next() )
      {
         cmd->setIndex(index);
         applyEditCommand(cmd,0);
      }

      setModified(true);

      applyEndCommand( index, Comment, 0 );

      emit signalNumberOfFuzziesChanged(numberOfFuzzies());
   }

}

void Catalog::removeFuzzyStatus(uint index)
{
    setFuzzy(index,false);
}


void Catalog::setModified(bool flag)
{
    bool old=d->_modified;
    d->_modified=flag;

    if(old!=d->_modified)
       emit signalModified(flag);
}


QString Catalog::packageName() const
{
    if( !d->_packageName.isNull() ) return d->_packageName;

    QString package=d->_url.fileName();

    int index=package.find(QRegExp("(\\."+d->_identitySettings.languageCode+")?\\.pot?$"));

    if(index>0)
      package=package.left(index);

    return package;
}

void Catalog::setPackage(const QString& package )
{
    int pos=package.findRev("/");
    if( pos<0 )
    {
	d->_packageDir = "";
	d->_packageName = package;
	d->_packageName.replace( QRegExp("^/+"),"");
    }
    else
    {
	d->_packageDir = package.left(pos);
	if( !d->_packageDir.endsWith("/") ) d->_packageDir+="/";
	d->_packageName = package.right(package.length()-pos);
	d->_packageName.replace( QRegExp("^/+"),"");
    }
}

QString Catalog::packageDir() const
{
    QString result;
    if( !d->_packageDir.isNull() ) result=d->_packageDir;
    else result=d->_url.directory(false);

    return result;
}

QString Catalog::encoding() const
{
    QString encodingStr;
    if(d->_saveSettings.useOldEncoding && d->fileCodec)
    {
	    encodingStr = charsetString(d->fileCodec);
    }
    else
    {
        encodingStr= charsetString(d->_saveSettings.encoding);
    }

    return encodingStr;
}

ConversionStatus Catalog::openURL(const KURL& url, const QString& package)
{
   QString target;
   ConversionStatus error = OK;

   if(KIO::NetAccess::download(url, target))
   {
       CatalogImportPlugin* filter=0;

       // gimme plugin for this MIME type
       KMimeType::Ptr mime = KMimeType::findByURL( target );
       KTrader::OfferList offers = KTrader::self()->query("KBabelFilter", "('"+mime->name()+"' in [X-KDE-Import])");
       KService::Ptr ptr = offers.first();

       // we have no offer for this MIME type
       if( !ptr )
       {
    	    kdDebug(KBABEL) << "No plugin for this type" << endl;
    	    KIO::NetAccess::removeTempFile(target);
    	    return NO_PLUGIN;
       }

       // try to load the library, if unsuccesfull, we have an installation problem
       KLibFactory *factory = KLibLoader::self()->factory( ptr->library().local8Bit() );
       if (!factory)
       {
    	    kdDebug(KBABEL) << "No factory" << endl;
    	    KIO::NetAccess::removeTempFile(target);
    	    return OS_ERROR;
       }

       // create the filter
       filter = static_cast<CatalogImportPlugin*>(factory->create(0, 0));

       // provide progress bar indication
	connect( filter, SIGNAL( signalResetProgressBar(QString,int) ),
	    this, SIGNAL( signalResetProgressBar(QString,int) ));
	connect( filter, SIGNAL( signalProgress(int) ),
	    this, SIGNAL( signalProgress(int) ));
	connect( filter, SIGNAL( signalClearProgressBar() ),
	    this, SIGNAL( signalClearProgressBar() ));

	connect( this, SIGNAL( signalStopActivity() ),
	    filter, SLOT( stop() ));

	// load in the file (target is always local)
	d->_active = true;
	kdDebug(KBABEL) << "openURL active" << endl;
        error = filter->open(target,mime->name(),this);
	// we should be not freed yet
	d->_active = false;
	kdDebug(KBABEL) << "openURL not active" << endl;
	if( error == STOPPED )
	{
	    delete filter;
	    return STOPPED;
	}

	if( error == OK || error == RECOVERED_PARSE_ERROR || error == HEADER_ERROR )
	{
	    setModified(false);
	    d->_url=url;

	    if( package.isNull() )
	    {
		d->_packageName=QString::null;
		d->_packageDir=QString::null;
	    }
	    else setPackage(package);

	    emit signalFileOpened(d->_readOnly);
	    emit signalNumberOfFuzziesChanged(numberOfFuzzies());
	    emit signalNumberOfUntranslatedChanged(numberOfUntranslated());
	    emit signalTotalNumberChanged(numberOfEntries());
	}

	delete filter;

        return error;
   }
   else
   {
      return OS_ERROR;
   }
}

ConversionStatus Catalog::openURL(const KURL& openUrl, const KURL& saveURL, const QString& package)
{
   QString target;
   ConversionStatus error = OK;

   if(KIO::NetAccess::download(openUrl, target))
   {
       CatalogImportPlugin* filter=0;

       // gimme plugin for this MIME type
       KMimeType::Ptr mime = KMimeType::findByURL( target );
       KTrader::OfferList offers = KTrader::self()->query("KBabelFilter", "('"+mime->name()+"' in [X-KDE-Import])");
       KService::Ptr ptr = offers.first();

       // we have no offer for this MIME type
       if( !ptr )
       {
    	    kdDebug(KBABEL) << "No plugin for this type" << endl;
    	    KIO::NetAccess::removeTempFile(target);
    	    return NO_PLUGIN;
       }

       // try to load the library, if unsuccesfull, we have an installation problem
       KLibFactory *factory = KLibLoader::self()->factory( ptr->library().local8Bit() );
       if (!factory)
       {
    	    kdDebug(KBABEL) << "No factory" << endl;
    	    KIO::NetAccess::removeTempFile(target);
    	    return OS_ERROR;
       }

       // create the filter
       filter = static_cast<CatalogImportPlugin*>(factory->create(0, 0));

       // provide progress bar indication
	connect( filter, SIGNAL( signalResetProgressBar(QString,int) ),
	    this, SIGNAL( signalResetProgressBar(QString,int) ));
	connect( filter, SIGNAL( signalProgress(int) ),
	    this, SIGNAL( signalProgress(int) ));
	connect( filter, SIGNAL( signalClearProgressBar() ),
	    this, SIGNAL( signalClearProgressBar() ));

	connect( this, SIGNAL( signalStopActivity() ),
	    filter, SLOT( stop() ));

	// load in the file (target is always local)
	d->_active = true;
	kdDebug(KBABEL) << "openURL - template active" << endl;
        error = filter->open(target,mime->name(),this);
	// we should be not freed yet
	kdDebug(KBABEL) << "openURL - template not active" << endl;
	d->_active = false;
	if( error == STOPPED )
	{
	    delete filter;
	    return STOPPED;
	}

	if( error == OK )
	{
           setModified(false);
           d->_url = saveURL;
	   if( package.isNull() )
	   {
		d->_packageName=QString::null;
		d->_packageDir=QString::null;
	   }
	   else setPackage(package);

           emit signalFileOpened(d->_readOnly);
           emit signalNumberOfFuzziesChanged(numberOfFuzzies());
           emit signalNumberOfUntranslatedChanged(numberOfUntranslated());
           emit signalTotalNumberChanged(numberOfEntries());
        }

	delete filter;

        // and remove the temp file
        KIO::NetAccess::removeTempFile(target);

        return error;
   }
   else
   {
      return OS_ERROR;
   }
}

Msgfmt::Status Catalog::checkSyntax(QString& output, bool clearErrors)
{
   if( !d->_mimeTypes.contains( "application/x-gettext" ) )
	return Msgfmt::Unsupported;

   QString filename;
   bool tempFileUsed=false;

   if(d->_url.isLocalFile() && !isModified())
   {
      filename=d->_url.path(0);
   }
   else
   {
      tempFileUsed=true;
      filename=saveTempFile();
   }

   Msgfmt msgfmt;
   Msgfmt::Status result = msgfmt.checkSyntax( filename , output, pluralFormType() != KDESpecific );

   if( clearErrors) clearErrorList();

   if( result==Msgfmt::SyntaxError )
   {
      int currentIndex=-1;
      int currentLine=0;

      if( !d->_header.msgstr().isEmpty() )
         currentLine=d->_header.totalLines()+1;

      QStringList lines = QStringList::split("\n",output);
      for ( QStringList::Iterator it = lines.begin(); it != lines.end(); ++it )
      {
         if( (*it).contains(QRegExp("^.+:\\d+:")) )
         {
            int begin=(*it).find(":",0)+1;
            int end=(*it).find(":",begin);

            QString line=(*it).mid(begin,end-begin);

            while( line.toInt() > currentLine )
            {
               currentIndex++;
               currentLine += ( d->_entries[currentIndex].totalLines() + 1 );
            }

	    if( currentIndex == -1 )
	    {
		// header error
		result = Msgfmt::HeaderError;
		continue;
	    }

            if( !d->_errorIndex.contains(currentIndex) )
            {
               d->_errorIndex.append(currentIndex);
			   d->_entries[currentIndex].setSyntaxError(true);
            }
         }
      }
   }

   if(tempFileUsed)
      QFile::remove(filename);

   return result;
}

void Catalog::clearErrorList()
{
	QValueList<uint>::Iterator it;
	for(it = d->_errorIndex.begin(); it != d->_errorIndex.end(); ++it)
	{
		d->_entries[(*it)].setSyntaxError(false);
		d->_entries[(*it)].clearErrors();
	}

	d->_errorIndex.clear();
}

void Catalog::removeFromErrorList(uint index)
{
	if(d->_errorIndex.contains(index))
	{
		d->_errorIndex.remove(index);
		d->_entries[index].setSyntaxError(false);
		d->_entries[index].clearErrors();
	}
}

QStringList Catalog::itemStatus(uint index, bool recheck, QPtrList<KDataTool> whatToCheck)
{
    if (  d->_entries.isEmpty() )
        return QStringList();

	uint max=d->_entries.count()-1;
	if(index > max)
		index=max;

	CatalogItem& item = d->_entries[index];

	if(recheck)
	{
	    for( KDataTool* t = whatToCheck.first(); t ; t=whatToCheck.next() )
	    {
		t->run("validate", (void*)(&item), "CatalogItem", "application/x-kbabel-catalogitem" );
	    }
	}

	return item.errors();
}

QStringList Catalog::itemStatus(uint index)
{
       if (  d->_entries.isEmpty() )
        return QStringList();

	uint max=d->_entries.count()-1;
	if(index > max)
		index=max;

	CatalogItem& item = d->_entries[index];

	return item.errors();
}

bool Catalog::checkUsingTool(KDataTool* tool, bool clearErrors)
{
	if(clearErrors)
		clearErrorList();

	kdDebug(KBABEL) << "checkUsingTool active" << endl;
	d->_active=true;
	d->_stop=false;
	connect( this, SIGNAL( signalStopActivity() ), this, SLOT( stopInternal() ));

	int index = 0;
	bool hasErrors=false;

	emit signalResetProgressBar(i18n("validating file"),100);

	for ( QValueVector<CatalogItem>::Iterator it = d->_entries.begin();
					it != d->_entries.end(); ++it, index++ )
	{
	    if( !tool->run( "validate", (void*)(&(*it)), "CatalogItem", "application/x-kbabel-catalogitem" ))
	    {
		if( !d->_errorIndex.contains(index) )
		{
			d->_errorIndex.append(index);
			hasErrors=true;
		}
	    }
	    if( d->_stop ) break;
	    emit signalProgress((index*100)/d->_entries.count());
	}

	if( hasErrors && !clearErrors ) qHeapSort(d->_errorIndex);

	kdDebug(KBABEL) << "checkUsingTool not active" << endl;
	d->_active=false;
	d->_stop=false;
	disconnect( this, SIGNAL( signalStopActivity() ), this, SLOT( stopInternal() ));

	emit signalClearProgressBar();

	return !hasErrors;
}

void Catalog::modifyUsingTool(KDataTool* tool, const QString& command)
{
    kdDebug(KBABEL) << "modifyUsingTool active" << endl;
    d->_active=true;
    d->_stop=false;
    connect( this, SIGNAL( signalStopActivity() ), this, SLOT( stopInternal() ));

    int index = 0;
    bool modified = false;

    emit signalResetProgressBar(i18n("applying tool"),100);

    for ( QValueVector<CatalogItem>::Iterator it = d->_entries.begin();
    				it != d->_entries.end(); ++it, index++ )
    {
	CatalogItem dummyItem( *it );

	tool->run( command, (void*)(&dummyItem), "CatalogItem", "application/x-kbabel-catalogitem" );

	if( (*it).msgstr() != dummyItem.msgstr() || (*it).comment() != dummyItem.comment() )
	{
	    if( !modified )
	    {
		applyBeginCommand(0,Msgstr,0);
		modified = true;
	    }

	    if( (*it).msgstr() != dummyItem.msgstr() )
	    {
		uint in = 0; // number of current lural form
		// go over all plural forms and test, which changed
		for ( QStringList::Iterator itorig = (*it).msgstr().begin()
			, itchanged = dummyItem.msgstr().begin()
			; itorig != (*it).msgstr().end()
			; ++itorig, ++itchanged) {
		    if( (*itorig) != (*itchanged) )
		    {
			EditCommand* cmd = new DelTextCmd(0,(*itorig),index);
			cmd->setPart(Msgstr);
			applyEditCommand(cmd,0);
			cmd = new InsTextCmd(0,(*itchanged),index);
			cmd->setPart(Msgstr);
			applyEditCommand(cmd,0);
		    }
		    in++;
		}
	    }

	    if( (*it).comment() != dummyItem.comment() )
	    {
		EditCommand* cmd = new DelTextCmd(0,(*it).comment(),0);
		cmd->setPart(Comment);
		cmd->setIndex(index);
		applyEditCommand(cmd,0);
		cmd = new InsTextCmd(0,dummyItem.comment(),0);
		cmd->setPart(Comment);
		cmd->setIndex(index);
		applyEditCommand(cmd,0);
		kdDebug(KBABEL) << "DummyItem comment is " << dummyItem.comment() << endl;
	    }
	}

	if( d->_stop ) break;
	emit signalProgress((index*100)/d->_entries.count());
    }

    if( modified ) applyEndCommand(0, Msgstr, 0);

    kdDebug(KBABEL) << "modifyUsingTool not active" << endl;
    d->_active=false;
    d->_stop=false;
    disconnect( this, SIGNAL( signalStopActivity() ), this, SLOT( stopInternal() ));

    emit signalClearProgressBar();
}

void Catalog::clear()
{
    d->_entries.clear();
    d->_url=KURL();
    d->_obsoleteEntries.clear();

    if(d->_undoList.count() > 0)
       emit signalUndoAvailable(false);
    if(d->_redoList.count() > 0)
       emit signalRedoAvailable(false);

    d->_undoList.clear();
    d->_redoList.clear();

    d->msgidDiffList.clear();
    d->msgstr2MsgidDiffList.clear();
    d->diffCache.clear();
}


uint Catalog::numberOfEntries() const
{
   return d->_entries.count();
}

uint Catalog::numberOfFuzzies() const
{
   return d->_fuzzyIndex.count();
}

uint Catalog::numberOfUntranslated() const
{
   return d->_untransIndex.count();
}


bool Catalog::hasFuzzyInFront(uint index)  const
{
   if(findPrevInList(d->_fuzzyIndex,index)>=0)
   {
      return true;
   }

   return false;
}

bool Catalog::hasFuzzyAfterwards(uint index) const
{
   if(findNextInList(d->_fuzzyIndex,index)>=0)
   {
      return true;
   }

   return false;
}

bool Catalog::hasUntranslatedInFront(uint index) const
{
   if(findPrevInList(d->_untransIndex,index)>=0)
   {
      return true;
   }

   return false;
}

bool Catalog::hasUntranslatedAfterwards(uint index) const
{
   if(findNextInList(d->_untransIndex,index)>=0)
   {
      return true;
   }

   return false;
}

bool Catalog::hasErrorInFront(uint index)  const
{
   if(findPrevInList(d->_errorIndex,index)>=0)
   {
      return true;
   }

   return false;
}

bool Catalog::hasErrorAfterwards(uint index) const
{
   if(findNextInList(d->_errorIndex,index)>=0)
   {
      return true;
   }

   return false;
}

bool Catalog::isFuzzy(uint index) const
{
    if (  d->_entries.isEmpty() )
        return false;

   if(index > numberOfEntries())
      return false;

   return d->_entries[index].isFuzzy();
}


bool Catalog::isUntranslated(uint index) const
{
    if (  d->_entries.isEmpty() )
        return false;

   if(index > numberOfEntries())
      return false;

   return d->_entries[index].isUntranslated();
}

bool Catalog::hasError(uint index, DocPosition& pos) const
{
    if( d->_errorIndex.contains(index) )
    {
	pos.item=index;
	pos.form=0;
	return true;
    }
   return false;
}

PluralFormType Catalog::pluralForm(uint index) const
{
    if (  d->_entries.isEmpty() )
        return NoPluralForm;

    if(index > numberOfEntries())
        return NoPluralForm;

    return static_cast<PluralFormType>(d->_entries[index].pluralForm());
}

PluralFormType Catalog::pluralFormType() const
{
    if (  d->_entries.isEmpty() )
        return NoPluralForm;

    for( uint i = 0 ; i < numberOfEntries(); i++)
    {
	if( d->_entries[i].pluralForm() != NoPluralForm )
	    return d->_entries[i].pluralForm();
    }
    
    return NoPluralForm;
}

int Catalog::nextFuzzy(uint startIndex, DocPosition& pos) const
{
   pos.item=findNextInList(d->_fuzzyIndex,startIndex);
   pos.form=0;
   return pos.item;
}

int Catalog::prevFuzzy(uint startIndex, DocPosition& pos) const
{
   pos.item=findPrevInList(d->_fuzzyIndex,startIndex);
   pos.form=0;
   return pos.item;
}

int Catalog::nextUntranslated(uint startIndex, DocPosition& pos) const
{
   pos.item=findNextInList(d->_untransIndex,startIndex);
   pos.form=0;
   return pos.item;
}

int Catalog::prevUntranslated(uint startIndex, DocPosition& pos) const
{
   pos.item=findPrevInList(d->_untransIndex,startIndex);
   pos.form=0;
   return pos.item;
}


int Catalog::nextError(uint startIndex, DocPosition& pos) const
{
   pos.item=findNextInList(d->_errorIndex,startIndex);
   pos.form=0;
   return pos.item;
}

int Catalog::prevError(uint startIndex, DocPosition& pos) const
{
   pos.item=findPrevInList(d->_errorIndex,startIndex);
   pos.form=0;
   return pos.item;
}


void Catalog::registerView(CatalogView* view)
{
   if(d->_views.containsRef(view)==0)
   {
      d->_views.append(view);
   }
}


void Catalog::removeView(CatalogView* view)
{
   d->_views.removeRef(view);
}


void Catalog::updateViews(EditCommand* cmd,CatalogView* view2exclude)
{
    CatalogView* view;
    for ( view=d->_views.first(); view != 0; view=d->_views.next())
    {
       if(view!=view2exclude)
       {
          view->update(cmd);
       }
    }
}



bool Catalog::hasView() const
{
    if(d->_views.count()==0)
           return false;

    return true;
}

bool Catalog::isLastView() const
{
    if(d->_views.count()<=1)
           return true;

    return false;
}


void Catalog::readPreferences(KConfig *config)
{
   KConfigGroupSaver groupSaver(config,"Header");

   d->_saveSettings.autoUpdate=config->readBoolEntry("AutoUpdate"
				   ,Defaults::Save::autoUpdate);
   d->_saveSettings.updateLastTranslator=config->readBoolEntry("Update-Last-Translator"
                       ,Defaults::Save::updateLastTranslator);
   d->_saveSettings.updateRevisionDate=config->readBoolEntry("Update-Revision-Date"
                       ,Defaults::Save::updateRevisionDate);
   d->_saveSettings.updateLanguageTeam=config->readBoolEntry("Update-Language-Team"
                       ,Defaults::Save::updateLanguageTeam);
   d->_saveSettings.updateCharset=config->readBoolEntry("Update-Charset"
                       ,Defaults::Save::updateCharset);
   d->_saveSettings.updateEncoding=config->readBoolEntry("Update-Encoding"
                       ,Defaults::Save::updateEncoding);
   d->_saveSettings.encoding=(FileEncoding)(config->readNumEntry("Encoding"
						   ,(int)Defaults::Save::encoding));
   d->_saveSettings.useOldEncoding=config->readBoolEntry("UseOldEncoding"
                           ,Defaults::Save::useOldEncoding);

   d->_saveSettings.updateProject=config->readBoolEntry("Update-Project"
                       ,Defaults::Save::updateProject);
   d->_saveSettings.projectString=config->readEntry("ProjectString"
                       ,Defaults::Save::projectString());

   d->_saveSettings.autoSyntaxCheck = config->readBoolEntry("AutoSyntaxCheck"
                 ,Defaults::Save::autoSyntaxCheck);
   d->_saveSettings.saveObsolete = config->readBoolEntry("SaveObsolete"
                 ,Defaults::Save::saveObsolete);
   d->_saveSettings.customDateFormat = config->readEntry("CustomDateFormat"
                 ,Defaults::Save::customDateFormat());
   d->_saveSettings.dateFormat = (Qt::DateFormat)( config->readNumEntry("DateFormat"
                 ,(int)Defaults::Save::dateFormat) );
   d->_saveSettings.updateDescription = config->readBoolEntry("UpdateDescription"
		 ,Defaults::Save::updateDescription);
   d->_saveSettings.descriptionString = config->readEntry("DescriptionString"
		 ,Defaults::Save::descriptionString());
   d->_saveSettings.updateTranslatorCopyright = config->readBoolEntry("UpdateTranslatorCopyright"
		 ,Defaults::Save::updateTranslatorCopyright);
   d->_saveSettings.FSFCopyright=(CopyrightUpdate)(config->readNumEntry("FSFCopyright"
						   ,(int)Defaults::Save::FSFCopyright));

   d->_saveSettings.autoSaveDelay=config->readNumEntry("AutoSaveDelay", Defaults::Save::autoSaveDelay);

    Defaults::Identity defaultIdentity;
   d->_identitySettings.authorName=config->readEntry("Author-Name"
				   ,defaultIdentity.authorName());
   d->_identitySettings.authorLocalizedName=config->readEntry("Local-Author-Name"
				   ,defaultIdentity.authorName());
   d->_identitySettings.authorEmail=config->readEntry("Author-Email"
				   ,defaultIdentity.authorEmail());
   d->_identitySettings.languageName=config->readEntry("Language"
				   ,defaultIdentity.languageName());
   d->_identitySettings.languageCode=config->readEntry("LanguageCode"
				   ,defaultIdentity.languageCode());
   d->_identitySettings.mailingList=config->readEntry("Mailinglist"
				   ,defaultIdentity.mailingList());
   d->_identitySettings.timeZone=config->readEntry("Timezone"
				   ,defaultIdentity.timezone());

   config->setGroup("Misc");

   Defaults::Misc defaultMisc;
   QString temp=config->readEntry("AccelMarker"
				   ,defaultMisc.accelMarker());
   if(temp.length() > 0)
		   d->_miscSettings.accelMarker=temp[0];

   temp = config->readEntry("ContextInfo"
				   ,defaultMisc.contextInfo().pattern());
   d->_miscSettings.contextInfo.setPattern(temp);

   temp = config->readEntry("SingularPlural"
				   ,defaultMisc.singularPlural().pattern());
   d->_miscSettings.singularPlural.setPattern(temp);

   d->_identitySettings.numberOfPluralForms=config->readNumEntry("PluralForms"
           , defaultIdentity.numberOfPluralForms);
   if (d->_identitySettings.numberOfPluralForms < 1)
   {
	kdWarning() << "Invalid number of plural forms, replacing by 2: " << d->_identitySettings.numberOfPluralForms << endl;
	d->_identitySettings.numberOfPluralForms = 2;
   }
   d->_identitySettings.checkPluralArgument=config->readBoolEntry("CheckPluralArgument",true);
   d->_identitySettings.gnuPluralFormHeader=config->readEntry("PluralFormsHeader"
           , defaultIdentity.gnuPluralFormHeader);

   getNumberOfPluralForms();

   d->_miscSettings.useBzip = config->readBoolEntry("BZipCompression", defaultMisc.useBzip);
   d->_miscSettings.compressSingleFile = config->readBoolEntry("CompressSingleFile", defaultMisc.compressSingleFile);

   config->setGroup("Tags");

   d->_tagSettings.tagExpressions=config->readListEntry("TagExpressions");
   if( d->_tagSettings.tagExpressions.empty() ) d->_tagSettings.tagExpressions = Defaults::Tag::tagExpressions();
   d->_tagExtractor->setRegExpList(d->_tagSettings.tagExpressions) ;

   d->_tagSettings.argExpressions=config->readListEntry("ArgExpressions");
   if( d->_tagSettings.argExpressions.empty() ) d->_tagSettings.argExpressions = Defaults::Tag::argExpressions();
   d->_argExtractor->setRegExpList(d->_tagSettings.argExpressions) ;

}

void Catalog::savePreferences(KConfig *config)
{
   KConfigGroupSaver groupSaver(config,"Header");

   config->writeEntry("AutoUpdate",d->_saveSettings.autoUpdate);
   config->writeEntry("Update-Last-Translator",d->_saveSettings.updateLastTranslator);
   config->writeEntry("Update-Revision-Date",d->_saveSettings.updateRevisionDate);
   config->writeEntry("Update-Language-Team",d->_saveSettings.updateLanguageTeam);
   config->writeEntry("Update-Charset",d->_saveSettings.updateCharset);
   config->writeEntry("Update-Encoding",d->_saveSettings.updateEncoding);
   config->writeEntry("Encoding",(int)d->_saveSettings.encoding);
   config->writeEntry("UseOldEncoding",d->_saveSettings.useOldEncoding);

   config->writeEntry("Update-Project", d->_saveSettings.updateProject);
   config->writeEntry("ProjectString", d->_saveSettings.projectString);

   config->writeEntry("AutoSyntaxCheck",d->_saveSettings.autoSyntaxCheck);
   config->writeEntry("SaveObsolete",d->_saveSettings.saveObsolete);
   config->writeEntry("CustomDateFormat",d->_saveSettings.customDateFormat);
   config->writeEntry("DateFormat",(int)d->_saveSettings.dateFormat);

   config->writeEntry("UpdateDescription", d->_saveSettings.updateDescription);
   config->writeEntry("DescriptionString", d->_saveSettings.descriptionString);
   config->writeEntry("UpdateTranslatorCopyright",d->_saveSettings.updateTranslatorCopyright);
   config->writeEntry("FSFCopyright", (int)d->_saveSettings.FSFCopyright);

   config->writeEntry("AutoSaveDelay", d->_saveSettings.autoSaveDelay);

   config->writeEntry("Author-Name",d->_identitySettings.authorName);
   config->writeEntry("Local-Author-Name",d->_identitySettings.authorLocalizedName);
   config->writeEntry("Author-Email",d->_identitySettings.authorEmail);
   config->writeEntry("Language",d->_identitySettings.languageName);
   config->writeEntry("LanguageCode",d->_identitySettings.languageCode);
   config->writeEntry("Mailinglist",d->_identitySettings.mailingList);
   config->writeEntry("Timezone",d->_identitySettings.timeZone);


   config->setGroup("Misc");
   QString temp(d->_miscSettings.accelMarker);
   config->writeEntry("AccelMarker",temp);
   config->writeEntry("ContextInfo",d->_miscSettings.contextInfo.pattern());
   config->writeEntry("SingularPlural",d->_miscSettings.singularPlural.pattern());
   config->writeEntry("PluralForms",d->_identitySettings.numberOfPluralForms);
   config->writeEntry("PluralFormsHeader",d->_identitySettings.gnuPluralFormHeader);
   config->writeEntry("BZipCompression", d->_miscSettings.useBzip);
   config->writeEntry("CompressSingleFile", d->_miscSettings.compressSingleFile);
   config->writeEntry("CheckPluralArgument", d->_identitySettings.checkPluralArgument);

   config->setGroup("Tags");

   config->writeEntry( "TagExpressions", d->_tagSettings.tagExpressions );
   config->writeEntry( "ArgExpressions", d->_tagSettings.argExpressions );

   config->sync();
}

IdentitySettings Catalog::identitySettings() const
{
    return d->_identitySettings;

}

SaveSettings Catalog::saveSettings() const
{
    return d->_saveSettings;

}

MiscSettings Catalog::miscSettings() const
{
    return d->_miscSettings;

}

TagSettings Catalog::tagSettings() const
{
    return d->_tagSettings;

}

bool Catalog::isGeneratedFromDocbook() const
{
    return d->_generatedFromDocbook;
}

QString Catalog::package() const
{
    return packageDir()+packageName();
}

bool Catalog::isReadOnly() const
{
    return d->_readOnly;
}

void Catalog::setSettings(SaveSettings settings)
{
   d->_saveSettings=settings;

   emit signalSettingsChanged(settings);
}

void Catalog::setSettings(IdentitySettings settings)
{
   QString oldLanguageCode = d->_identitySettings.languageCode;
   int oldForms =d->_identitySettings.numberOfPluralForms;


   d->_identitySettings=settings;

   if(oldLanguageCode != d->_identitySettings.languageCode)
   {
       getNumberOfPluralForms();
   }

    if(oldForms != d->_identitySettings.numberOfPluralForms)
    {
        getNumberOfPluralForms();
    }

   emit signalSettingsChanged(settings);
}

void Catalog::setSettings(MiscSettings settings)
{
	d->_miscSettings=settings;

	emit signalSettingsChanged(settings);
}

void Catalog::setSettings(TagSettings settings)
{
	d->_tagSettings=settings;

	emit signalSettingsChanged(settings);
}

void Catalog::generateIndexLists()
{
   d->_fuzzyIndex.clear();
   d->_untransIndex.clear();
   clearErrorList();

   uint counter=0;
   for ( QValueVector<CatalogItem>::Iterator it = d->_entries.begin(); it != d->_entries.end(); ++it )
   {
       if((*it).isUntranslated())
       {
          d->_untransIndex.append(counter);
       }
       else if((*it).isFuzzy())
       {
          d->_fuzzyIndex.append(counter);
       }

       counter++;
   }

}

int Catalog::findNextInList(const QValueList<uint>& list,uint index) const
{
    QValueList<uint>::ConstIterator it;

    int nextIndex=-1;

    // find index in List
    it=list.find(index);

    // if the given index is found in the list and not the last entry
    // in the list, return the next listentry
    if(it!=list.end() && it!=list.fromLast())
    {
       ++it;
       return (*it);
    }

    // if the index is not in the list, search the index in the list, that
    // is the nearest to the given index
    for( it = list.begin(); it != list.end(); ++it )
    {
       if((*it) > index)
       {
          nextIndex=(*it);
          break;
       }
    }


    return nextIndex;
}

int Catalog::findPrevInList(const QValueList<uint>& list,uint index) const
{
    QValueList<uint>::ConstIterator it;

    int prevIndex=-1;

    it=list.find(index);

    // if the given index is found in the list and not the last entry
    // in the list, return the next listentry
    if(it!=list.end() && it!=list.begin())
    {
       --it;
       return (*it);
    }


    // if the index is not in the list, search the index in the list, that
    // is the nearest to the given index
    for( it = list.fromLast(); it != list.end(); --it )
    {
       if((*it) < index)
       {
          prevIndex=(*it);
          break;
       }
    }


    return prevIndex;
}


QString Catalog::dateTime() const
{
    QString dateTimeString;
    QDate date=QDate::currentDate();
    QTime time=QTime::currentTime();

    switch(d->_saveSettings.dateFormat)
    {
       case Qt::LocalDate:
       {
          return KGlobal::locale()->formatDateTime(QDateTime::currentDateTime());
       }
       case Qt::ISODate:
          dateTimeString = Defaults::Save::customDateFormat();
          break;
       case Qt::TextDate:
          dateTimeString = d->_saveSettings.customDateFormat;
          break;
    }

    // the year
    dateTimeString.replace( "%Y", QString::number( date.year() ) );
    dateTimeString.replace( "%y", QString::number( date.year() ).right(2) );

    // the month
    if(date.month()<10)
    {
       dateTimeString.replace( "%m", "0"+QString::number( date.month() ) );
    }
    else
    {
       dateTimeString.replace( "%m", QString::number( date.month() ) );
    }

    dateTimeString.replace( "%f", QString::number( date.month() ) );

    dateTimeString.replace( "%b", date.longMonthName(date.month()) );
    dateTimeString.replace( "%h", date.longMonthName(date.month()) );

    // the day
    dateTimeString.replace( "%j", QString::number( date.dayOfYear() ) );
    dateTimeString.replace( "%e", QString::number( date.day() ) );
    if(date.day() < 10)
    {
       dateTimeString.replace( "%d", "0"+QString::number( date.day() ) );
    }
    else
    {
       dateTimeString.replace( "%d", QString::number( date.day() ) );
    }

    dateTimeString.replace( "%a", date.longDayName( date.dayOfWeek() ) );


    // hour
    dateTimeString.replace( "%k", QString::number( time.hour() ) );

    if(time.hour() < 10)
    {
       dateTimeString.replace( "%H", "0"+QString::number( time.hour() ) );
    }
    else
    {
       dateTimeString.replace( "%H", QString::number( time.hour() ) );
    }

    QString zone;
    int hour;
    if( time.hour() > 12 )
    {
       zone="PM";
       hour=time.hour()-12;
    }
    else
    {
       zone="AM";
       hour=time.hour();
    }

    dateTimeString.replace( "%I", QString::number( hour ) );

    if(hour < 10)
    {
       dateTimeString.replace( "%i", "0"+QString::number( hour ) );
    }
    else
    {
       dateTimeString.replace( "%i", QString::number( hour ) );
    }

    dateTimeString.replace( "%p", zone );

    // minutes
    if(time.minute() < 10)
    {
       dateTimeString.replace( "%M", "0"+QString::number( time.minute() ) );
    }
    else
    {
       dateTimeString.replace( "%M", QString::number( time.minute() ) );
    }

    // seconds
    if(time.second() < 10)
    {
       dateTimeString.replace( "%S", "0"+QString::number( time.second() ) );
    }
    else
    {
       dateTimeString.replace( "%S", QString::number( time.second() ) );
    }

    // timezone
    dateTimeString.replace( "%Z", d->_identitySettings.timeZone );
    QTime t;
    int sgn = KRFCDate::localUTCOffset() < 0 ? -1 : 1 ;
    t = t.addSecs( sgn*KRFCDate::localUTCOffset()*60 );
    dateTimeString.replace( "%z", (sgn<0 ? "-" : "+") +t.toString("hhmm"));

    return dateTimeString;
}


ConversionStatus Catalog::saveFile()
{
   if(d->_url.isEmpty())
   {
      kdFatal(KBABEL) << "fatal error: empty filename" << endl;
      return NO_FILE;
   }

   return saveFileAs(d->_url,true);
}

ConversionStatus Catalog::saveFileAs(const KURL &url, bool overwrite)
{
   if( d->_active ) return BUSY;

   ConversionStatus status=OK;

   bool newName=false;
   KURL targetURL=d->_url;

   if(url != d->_url)
   {
      newName = true;
      targetURL=url;
   }


   if(d->_saveSettings.autoUpdate)
   {
      d->_header=updatedHeader(d->_header);
      emit signalHeaderChanged();
   }


   if(targetURL.isLocalFile())
   {
      // test if the directory exists. If not, create it.
      QDir dir( targetURL.directory());

      QStringList dirList;
      while(!dir.exists() && !dir.dirName().isEmpty())
      {
         dirList.prepend(dir.dirName());
         dir.setPath(dir.path()+"/..");
      }
      for ( QStringList::Iterator it = dirList.begin(); it != dirList.end(); ++it )
      {
         if(!dir.mkdir(*it))
         {
            status=OS_ERROR;
            break;
         }
         dir.cd(*it);
      }

      if(status==OK)
      {
         status=writeFile(targetURL.path(0),overwrite);
      }
   }
   else
   {
      QString tempFile=kapp->tempSaveName(targetURL.path(0));

      status = writeFile(tempFile,overwrite);

      if(status == OK)
      {
         KURL temp(tempFile);
         if( !KIO::NetAccess::upload( temp.url(), targetURL.url() ) )
         {
            status = OS_ERROR;
         }
      }

      QFile::remove(tempFile);
   }

   if(status == OK)
   {
      setModified(false);

      if(newName)
      {
         // if we saved a file, the catalog can not be any longer readOnly;
         d->_readOnly=false;

         d->_url=targetURL;

         emit signalFileOpened(d->_readOnly);
      }
   }

   return status;
}

QString Catalog::saveTempFile()
{
   QString filename = kapp->tempSaveName("/temp/kbabel_temp.po");
   if( writeFile(filename) != OK )
   {
      filename = QString::null;
   }

   return filename;
}


ConversionStatus Catalog::writeFile(QString localFile , bool overwrite)
{
   QFileInfo info(localFile);

   if(info.isDir())
      return NO_FILE;

   if(info.exists())
   {
      if(!overwrite || !info.isWritable())
      {
         return NO_PERMISSIONS;
      }
   }
   else // check if the directory is writable
   {
      QFileInfo dir(info.dirPath());
      if(!dir.isWritable())
      {
         return NO_PERMISSIONS;
      }
   }

    ConversionStatus error = OK;
    CatalogExportPlugin* filter=0;

    // gimme plugin for this MIME type
    KMimeType::Ptr mime = KMimeType::findByURL( localFile );
    KTrader::OfferList offers = KTrader::self()->query("KBabelFilter", "('"+mime->name()+"' in [X-KDE-Export])");
    KService::Ptr ptr = offers.first();

       // we have no offer for this MIME type
    if( !ptr )
    {
    	kdDebug(KBABEL) << "No plugin for this type" << endl;
    	return NO_PLUGIN;
    }

    // try to load the library, if unsuccesfull, we have an installation problem
    KLibFactory *factory = KLibLoader::self()->factory( ptr->library().local8Bit() );
    if (!factory)
    {
    	kdDebug(KBABEL) << "No factory" << endl;
    	return OS_ERROR;
    }

    // create the filter
    filter = static_cast<CatalogExportPlugin*>(factory->create(0, 0));

    // provide progress bar indication
    connect( filter, SIGNAL( signalResetProgressBar(QString,int) ),
	    this, SIGNAL( signalResetProgressBar(QString,int) ));
    connect( filter, SIGNAL( signalProgress(int) ),
	    this, SIGNAL( signalProgress(int) ));
    connect( filter, SIGNAL( signalClearProgressBar() ),
	    this, SIGNAL( signalClearProgressBar() ));

    connect( this, SIGNAL( signalStopActivity() ),
	    filter, SLOT( stop() ));

    // load in the file (target is always local)
    kdDebug(KBABEL) << "writeFile active" << endl;
    d->_active = true;
    error = filter->save(localFile,mime->name(),this);
    // we should be not freed yet
    kdDebug(KBABEL) << "writeFile not active" << endl;
    d->_active = false;
    if( error == STOPPED ) return STOPPED;

    delete filter;

    return error;
}

QTextCodec* Catalog::codecForFile(QString gettextHeader)
{
   QString charset;

   QString head = gettextHeader;

   QRegExp r("Content-Type:\\s*\\w+/[-\\w]+;\\s*charset\\s*=\\s*[^\\\"\\n]+");
   int begin=r.search(head);
   int len=r.matchedLength();
   if(begin<0) {
   	kdDebug(KBABEL) << "no charset entry found" << endl;
   	return 0;
   }

   head = head.mid(begin,len);

   QRegExp regexp("charset *= *([^\\\\\\\"]+)");
   if( regexp.search( head ) > -1 )
   {
       charset = regexp.cap(1);
   }

   QTextCodec* codec=0;

   if(!charset.isEmpty())
   {
      // "CHARSET" is the default charset entry in a template (pot).
      // characters in a template should be either pure ascii or
      // at least utf8, so utf8-codec can be used for both.
      if( charset == "CHARSET")
      {
          codec=QTextCodec::codecForName("utf8");
          kdDebug(KBABEL)
              << QString("file seems to be a template: using utf8 encoding.")
              << endl;
      }
      else
      {
         codec=QTextCodec::codecForName(charset.latin1());
      }

      if(!codec)
      {
         kdWarning() << "charset found, but no codec available, using UTF8 instead" << endl;
	 codec=QTextCodec::codecForName("utf8");
      }
   }

   return codec;
}

PoInfo Catalog::headerInfo(const CatalogItem headerItem)
{
   QStringList header=headerItem.msgstrAsList();

   QStringList::Iterator it;

   PoInfo info;

   // extract information from the header
   for(it=header.begin();it!=header.end();++it)
   {
      if((*it).contains(QRegExp("^\\s*Project-Id-Version\\s*:\\s*.+\\s*$")))
      {
         info.project=(*it).replace(QRegExp("^\\s*Project-Id-Version\\s*:\\s*"),"");

         if(info.project.right(2)=="\\n")
            info.project.remove(info.project.length()-2,2);

         info.project=info.project.simplifyWhiteSpace();
      }
      else if((*it).contains(QRegExp("^\\s*POT-Creation-Date\\s*:\\s*.+\\s*$")))
      {
         info.creation=(*it).replace(QRegExp("^\\s*POT-Creation-Date\\s*:\\s*"),"");

         if(info.creation.right(2)=="\\n")
            info.creation.remove(info.creation.length()-2,2);

         info.creation=info.creation.simplifyWhiteSpace();
      }
      else if((*it).contains(QRegExp("^\\s*PO-Revision-Date\\s*:\\s*.+\\s*$")))
      {
         info.revision=(*it).replace(QRegExp("^\\s*PO-Revision-Date\\s*:\\s*"),"");

         if(info.revision.right(2)=="\\n")
            info.revision.remove(info.revision.length()-2,2);

         info.revision=info.revision.simplifyWhiteSpace();
      }
      else if((*it).contains(QRegExp("^\\s*Last-Translator\\s*:\\s*.+\\s*$")))
      {
         info.lastTranslator=(*it).replace(QRegExp("^\\s*Last-Translator\\s*:\\s*"),"");

         if(info.lastTranslator.right(2)=="\\n")
            info.lastTranslator.remove(info.lastTranslator.length()-2,2);

         info.lastTranslator=info.lastTranslator.simplifyWhiteSpace();
      }
      else if((*it).contains(QRegExp("^\\s*Language-Team\\s*:\\s*.+\\s*")))
      {
         info.languageTeam=(*it).replace(QRegExp("^\\s*Language-Team\\s*:\\s*"),"");

         if(info.languageTeam.right(2)=="\\n")
            info.languageTeam.remove(info.languageTeam.length()-2,2);

         info.languageTeam=info.languageTeam.simplifyWhiteSpace();
      }
      else if((*it).contains(QRegExp("^\\s*MIME-Version\\s*:\\s*.+\\s*")))
      {
         info.mimeVersion=(*it).replace(QRegExp("^\\s*MIME-Version\\s*:\\s*"),"");

         if(info.mimeVersion.right(2)=="\\n")
            info.mimeVersion.remove(info.mimeVersion.length()-2,2);

         info.mimeVersion=info.mimeVersion.simplifyWhiteSpace();
      }
      else if((*it).contains(QRegExp("^\\s*Content-Type\\s*:\\s*.+\\s*")))
      {
         info.contentType=(*it).replace(QRegExp("^\\s*Content-Type\\s*:\\s*"),"");

         if(info.contentType.right(2)=="\\n")
            info.contentType.remove(info.contentType.length()-2,2);

         info.contentType=info.contentType.simplifyWhiteSpace();
      }
      else if((*it).contains(QRegExp("^\\s*Content-Transfer-Encoding\\s*:\\s*.+\\s*")))
      {
         info.encoding=(*it).replace(QRegExp("^\\s*Content-Transfer-Encoding\\s*:\\s*"),"");

         if(info.encoding.right(2)=="\\n")
            info.encoding.remove(info.encoding.length()-2,2);

         info.encoding=info.encoding.simplifyWhiteSpace();
      }
      else
      {
          QString line=(*it);

         if(line.right(2)=="\\n")
            line.remove(line.length()-2,2);

         line=line.simplifyWhiteSpace();
         if(!info.others.isEmpty())
             info.others+='\n';

         info.others+=line;
      }


   }

   info.headerComment=headerItem.comment();

   return info;
}

bool Catalog::isUndoAvailable()
{
   return !d->_undoList.isEmpty();
}

bool Catalog::isRedoAvailable()
{
   return !d->_redoList.isEmpty();
}

int Catalog::undo()
{
   if(!isUndoAvailable())
      return -1;

   int macroLevel = 0;

   EditCommand *command=0;
   do
   {
      command = d->_undoList.take();
      if ( !command )
      {
         kdError() << "undo command is NULL?" << endl;
         return -1;
      }

      processCommand( command, 0, true );

      macroLevel += command->terminator();

      if ( d->_undoList.isEmpty() )
      {
         emit signalUndoAvailable( false );
      }
      if(d->_redoList.isEmpty())
      {
         emit signalRedoAvailable(true);
      }
      d->_redoList.append(command);

    }
    while(macroLevel != 0);

    return command->index();
}

int Catalog::redo()
{
   if(!isRedoAvailable())
      return -1;

   int macroLevel = 0;
   EditCommand *command=0;

   do
   {
      command = d->_redoList.take();
      if ( !command )
      {
         kdError() << "undo command is NULL?" << endl;
         return -1;
      }

      processCommand( command, 0,false );

      macroLevel += command->terminator();
      if ( d->_redoList.isEmpty() )
      {
         emit signalRedoAvailable( false );
      }
      if ( d->_undoList.isEmpty() )
      {
         emit signalUndoAvailable( true );
      }

      d->_undoList.append( command );
    }
    while (macroLevel != 0);

    return command->index();
}

void Catalog::applyEditCommand(EditCommand* cmd, CatalogView* view)
{

    processCommand(cmd,view);
    setModified(true);

    if ( d->_undoList.isEmpty() )
    {
       emit signalUndoAvailable(true);
    }
    else if ( cmd->merge( d->_undoList.last() ) )
    {
       delete cmd;
       return;
    }


    d->_undoList.append(cmd);


    if ( !d->_redoList.isEmpty() )
    {
       d->_redoList.clear();
       emit signalRedoAvailable( false );
    }

}

void Catalog::applyBeginCommand(uint index, Part part, CatalogView* view)
{
    applyEditCommand( new BeginCommand(index,part), view );
}

void Catalog::applyEndCommand(uint index, Part part, CatalogView* view)
{
    applyEditCommand( new EndCommand(index,part), view );
}

void Catalog::processCommand(EditCommand* cmd,CatalogView* view, bool undo)
{
    if(cmd->terminator()==0)
    {
       bool checkUntranslated=false;
       bool checkFuzzy=false;
       bool wasFuzzy=false;

       CatalogItem &item=d->_entries[cmd->index()];

       if(cmd->part() == Msgstr)
       {
          if( item.isUntranslated() )
          {
             d->_untransIndex.remove(cmd->index());

             emit signalNumberOfUntranslatedChanged(numberOfUntranslated());
          }
          else
          {
             checkUntranslated=true;
          }
       }
       else if(cmd->part() == Comment)
       {
          checkFuzzy=true;
          wasFuzzy=item.isFuzzy();
       }



       item.processCommand(cmd,undo);

       if(undo)
       {
          EditCommand* tmpCmd=0;
          DelTextCmd* delcmd = (DelTextCmd*) cmd;
          if (delcmd->type() == EditCommand::Delete )
          {
             tmpCmd = new InsTextCmd(delcmd->offset,delcmd->str,delcmd->pluralNumber);
          }
          else
          {
             tmpCmd = new DelTextCmd(delcmd->offset,delcmd->str,delcmd->pluralNumber);
          }

          tmpCmd->setIndex(cmd->index());
          tmpCmd->setPart(cmd->part());

          updateViews(tmpCmd,view);

          delete tmpCmd;
       }
       else
       {
          updateViews(cmd,view);
       }

       if(checkUntranslated && item.isUntranslated())
       {
          QValueList<uint>::Iterator it;

          // insert index in the right place in the list
          it = d->_untransIndex.begin();
          while(it != d->_untransIndex.end() && cmd->index() > (int)(*it))
          {
             ++it;
          }
          d->_untransIndex.insert( it,(uint)(cmd->index()) );

          emit signalNumberOfUntranslatedChanged(numberOfUntranslated());
       }
       else if(checkFuzzy)
       {
          if(wasFuzzy != item.isFuzzy())
          {
             if(wasFuzzy)
             {
                d->_fuzzyIndex.remove(cmd->index());
                emit signalNumberOfFuzziesChanged(numberOfFuzzies());
             }
             else
             {
                QValueList<uint>::Iterator it;

                // insert index in the right place in the list
                it = d->_fuzzyIndex.begin();
                while(it != d->_fuzzyIndex.end() && cmd->index() > (int)(*it))
                {
                   ++it;
                }
                d->_fuzzyIndex.insert( it,(uint)(cmd->index()) );

                emit signalNumberOfFuzziesChanged(numberOfFuzzies());
             }
          }
       }

    }
}

bool Catalog::findNext(const FindOptions* findOpts, DocPosition& docPos, int& len)
{
	bool success = false; // true, when string found
	bool endReached=false;

	kdDebug(KBABEL) << "findNext active" << endl;
	d->_active=true;
	d->_stop=false;
	connect( this, SIGNAL( signalStopActivity() ), this, SLOT( stopInternal() ));

	len=0;
	int pos=0;

	QString searchStr = findOpts->findStr;
	QRegExp regexp(searchStr);

	if( findOpts->isRegExp ) {
		regexp.setCaseSensitive(findOpts->caseSensitive);
	}

	if( docPos.item == numberOfEntries()-1) {
		switch(docPos.part) {
			case Msgid:
				// FIXME: we should search in plurals as well
				if(!findOpts->inMsgstr && !findOpts->inComment
					&& docPos.offset >= msgid(docPos.item).first().length() ) {
					endReached=true;
				}
				break;
			case Msgstr:
				if(!findOpts->inComment && (int)(docPos.form+1) >= numberOfPluralForms(docPos.item)
				    && docPos.offset >= msgstr(docPos.item).last().length() ) {
					endReached=true;
				}
				break;
			case Comment:
				if(docPos.offset >= comment(docPos.item).length() ) {
					endReached=true;
				}
				break;
			case UndefPart:
				break;
		}
	}

	while(!success) {
        int accelMarkerPos = -1;
        int contextInfoLength = 0;
        int contextInfoPos = -1;
		QString targetStr;

		kapp->processEvents(10);

		if( d->_stop || endReached)
		{
		    kdDebug(KBABEL) << "FindNext: endReached or stopped" << endl;
		    disconnect( this, SIGNAL( signalStopActivity() ), this, SLOT( stopInternal() ));
		    kdDebug(KBABEL) << "findNext not active" << endl;
		    d->_active=false;
		    d->_stop=false;
		    return false;
		}

		switch(docPos.part) {
			case Msgid:
				// FIXME: should care about plural forms in msgid
				targetStr = msgid(docPos.item).first();
				break;
			case Msgstr:
				targetStr = *(msgstr(docPos.item).at(docPos.form));
				break;
			case Comment:
				targetStr = comment(docPos.item);
				break;
			case UndefPart:
				break;
		}

        if(findOpts->ignoreContextInfo)
        {
            contextInfoPos = d->_miscSettings.contextInfo.search(targetStr);
	    contextInfoLength = d->_miscSettings.contextInfo.matchedLength();
            if(contextInfoPos >= 0)
            {
                targetStr.remove(contextInfoPos,contextInfoLength);

                if(docPos.offset > (uint)contextInfoPos)
                    docPos.offset -= contextInfoLength;
            }
        }

        if(findOpts->ignoreAccelMarker
                && targetStr.contains(d->_miscSettings.accelMarker))
        {
            accelMarkerPos = targetStr.find(d->_miscSettings.accelMarker);
            targetStr.remove(accelMarkerPos,1);

            if(docPos.offset > (uint)accelMarkerPos)
                docPos.offset--;
        }

		if( findOpts->isRegExp ) {
			if ((pos=regexp.search(targetStr,docPos.offset)) >= 0 ) {
			    len = regexp.matchedLength();
				if(findOpts->wholeWords) {
					QString pre=targetStr.mid(pos-1,1);
					QString post=targetStr.mid(pos+len,1);
					if(!pre.contains(QRegExp("[a-zA-Z0-9]")) && !post.contains(QRegExp("[a-zA-Z0-9]")) ){
						success=true;
						docPos.offset=pos;
					}
				}
				else {
					success=true;
					docPos.offset=pos;
				}
			}
		}
		else {
			if( (pos=targetStr.find(searchStr,docPos.offset,findOpts->caseSensitive)) >= 0 ) {
				len=searchStr.length();

				if(findOpts->wholeWords) {
					QString pre=targetStr.mid(pos-1,1);
					QString post=targetStr.mid(pos+len,1);
					if(!pre.contains(QRegExp("[a-zA-Z0-9]")) && !post.contains(QRegExp("[a-zA-Z0-9]")) ){
						success=true;
						docPos.offset=pos;
					}
				}
				else {
					success=true;
					docPos.offset=pos;
				}
			}
		}


		if(!success) {
			docPos.offset=0;
			switch(docPos.part) {
				case Msgid:
				{
					if(findOpts->inMsgstr) {
						docPos.part = Msgstr;
						docPos.form = 0;
					}
					else if(findOpts->inComment) {
						docPos.part = Comment;
					}
					else
					{
						if(docPos.item >= numberOfEntries()-1)
						{
							endReached=true;
						}
						else
						{
							docPos.item++;
						}
					}
					break;
				}
				case Msgstr:
					if( (int)docPos.form < numberOfPluralForms(docPos.item)-1 && pluralForm(docPos.item)==Gettext) {
					    docPos.form++;
					}
					else
					if(findOpts->inComment) {
						docPos.part = Comment;
					}
					else if(findOpts->inMsgid) {
						if(docPos.item >= numberOfEntries()-1)
						{
							endReached=true;
						}
						else
						{
							docPos.part = Msgid;
							docPos.item++;
						}
					}
					else {
						if(docPos.item >= numberOfEntries()-1)
						{
							endReached=true;
						}
						else
						{
							docPos.item++;
						}
					}
					break;
				case Comment:
					if(findOpts->inMsgid) {
						if(docPos.item >= numberOfEntries()-1)
						{
							endReached=true;
						}
						else
						{
							docPos.part = Msgid;
							docPos.item++;
						}
					}
					else if(findOpts->inMsgstr){
						if(docPos.item >= numberOfEntries()-1)
						{
							endReached=true;
						}
						else
						{
							docPos.part = Msgstr;
							docPos.form = 0;
							docPos.item++;
						}
					}
					else {
						if(docPos.item >= numberOfEntries()-1)
						{
							endReached=true;
						}
						else
						{
							docPos.item++;
						}
					}
					break;
				case UndefPart:
					break;
			}
		}
        else
        {
            if(accelMarkerPos >= 0)
            {
                if(docPos.offset >= (uint)accelMarkerPos)
                {
                    docPos.offset++;
                }
                else if(docPos.offset+len > (uint)accelMarkerPos)
                {
                    len++;
                }
            }

            if(contextInfoPos >= 0)
            {
                if(docPos.offset >= (uint)contextInfoPos)
                {
                    docPos.offset+=contextInfoLength;
                }
                else if(docPos.offset+len > (uint)contextInfoPos)
                {
                    len+=contextInfoLength;
                }

            }
        }
	}

	disconnect( this, SIGNAL( signalStopActivity() ), this, SLOT( stopInternal() ));
	kdDebug(KBABEL) << "findNext not active" << endl;
	d->_active=false;
	d->_stop=false;

	return true;
}

bool Catalog::findPrev(const FindOptions* findOpts, DocPosition& docPos, int& len)
{
	bool success = false;  // true, when found
	bool beginReached = false;

	kdDebug(KBABEL) << "findPrev active" << endl;
	d->_active=true;
	d->_stop=false;
	connect( this, SIGNAL( signalStopActivity() ), this, SLOT( stopInternal() ));

	len=0;
	int pos=0;

	QString searchStr = findOpts->findStr;
	QRegExp regexp(searchStr);

	if( findOpts->isRegExp ) {
		regexp.setCaseSensitive(findOpts->caseSensitive);
	}
	while(!success) {
        int accelMarkerPos = -1;
        int contextInfoLength = 0;
        int contextInfoPos = -1;
		QString targetStr;

		kapp->processEvents(10);

		if( d->_stop || beginReached)
		{
		    kdDebug(KBABEL) << "FindNext: endReached or stopped" << endl;
		    disconnect( this, SIGNAL( signalStopActivity() ), this, SLOT( stopInternal() ));
		    kdDebug(KBABEL) << "findPrev active" << endl;
		    d->_active=false;
		    d->_stop=false;
		    return false;
		}

		switch(docPos.part) {
			case Msgid:
				// FIXME: should care about plural forms in msgid
				targetStr = msgid(docPos.item).first();
				break;
			case Msgstr:
				targetStr = *(msgstr(docPos.item).at(docPos.form));
				break;
			case Comment:
				targetStr = comment(docPos.item);
				break;
			case UndefPart:
				break;
		}

        if(findOpts->ignoreContextInfo)
        {
            contextInfoPos = d->_miscSettings.contextInfo.search(targetStr);
	    contextInfoLength = d->_miscSettings.contextInfo.matchedLength();
            if(contextInfoPos >= 0)
            {
                targetStr.remove(contextInfoPos,contextInfoLength);

                if(docPos.offset > (uint)contextInfoPos)
                    docPos.offset -= contextInfoLength;
            }
        }

        if(findOpts->ignoreAccelMarker
                && targetStr.contains(d->_miscSettings.accelMarker))
        {
            accelMarkerPos = targetStr.find(d->_miscSettings.accelMarker);
            targetStr.remove(accelMarkerPos,1);

            if(docPos.offset > (uint)accelMarkerPos)
                docPos.offset--;
        }

		if(docPos.offset <= 0) {
			success=false;
		}
		else if( findOpts->isRegExp ) {
			/*
			don't work!?
			if((pos=targetStr.findRev(regexp,docPos.offset)) >= 0 ) {
				regexp.match(targetStr,pos,&len); // to get the length of the string
			*/
			bool found=false;
			int tmpPos=docPos.offset;
			while(!found && tmpPos>=0)
			{
				if( (pos=regexp.search(targetStr,tmpPos)) >= 0 && (uint)pos < docPos.offset)
					found=true;
				else
					tmpPos--;
				len = regexp.matchedLength();
			}
			if(found) {
				if(findOpts->wholeWords) {
					QString pre=targetStr.mid(pos-1,1);
					QString post=targetStr.mid(pos+len,1);
					if(!pre.contains(QRegExp("[a-zA-Z0-9]")) && !post.contains(QRegExp("[a-zA-Z0-9]")) ){
						success=true;
						docPos.offset=pos;
					}
				}
				else {
					success=true;
					docPos.offset=pos;
				}
			}
		}
		else if( (pos=targetStr.findRev(searchStr,docPos.offset-1,findOpts->caseSensitive)) >= 0
		          && (uint)pos < docPos.offset) {
			len=searchStr.length();
			if(findOpts->wholeWords) {
				QString pre=targetStr.mid(pos-1,1);
				QString post=targetStr.mid(pos+len,1);
				if(!pre.contains(QRegExp("[a-zA-Z0-9]")) && !post.contains(QRegExp("[a-zA-Z0-9]")) ){
					success=true;
					docPos.offset=pos;
				}
			}
			else {
				success=true;
				docPos.offset=pos;
			}
		}

		if(!success) {
			switch(docPos.part) {
				case Comment:
				{
					if(findOpts->inMsgstr) {
						docPos.part = Msgstr;
						docPos.form = msgstr(docPos.item).count()-1;
						docPos.offset = msgstr(docPos.item).last().length();
					}
					else if(findOpts->inMsgid) {
						docPos.part = Msgid;
						// FIXME: should care about plural forms in msgid
						docPos.offset = msgid(docPos.item).first().length();
					}
					else
					{
						if(docPos.item <= 0)
						{
							beginReached=true;
						}
						else
						{
							docPos.item--;
							docPos.offset = comment(docPos.item).length();
						}
					}
					break;
				}
				case Msgstr:
					if(docPos.form != 0 ) {
					    docPos.form--;
					    docPos.offset = (*msgstr(docPos.item).at(docPos.form)).length();
					}
					else if(findOpts->inMsgid) {
						docPos.part = Msgid;
						// FIXME: should care about plural forms in msgid
						docPos.offset = msgid(docPos.item).first().length();
					}
					else if(findOpts->inComment) {
						if(docPos.item <= 0)
						{
							beginReached=true;
						}
						else
						{
							docPos.part = Comment;
							docPos.item--;
							docPos.offset = comment(docPos.item).length();
						}
					}
					else {
						if(docPos.item <= 0)
						{
							beginReached=true;
						}
						else
						{
							docPos.item--;
							docPos.offset = msgstr(docPos.item).last().length();
							docPos.form = msgstr(docPos.item).count()-1;
						}
					}
					break;
				case Msgid:
					if(findOpts->inComment) {
						if(docPos.item <= 0 )
						{
							beginReached=true;
						}
						else
						{
							docPos.part = Comment;
							docPos.item--;
							docPos.offset = comment(docPos.item).length();
						}
					}
					else if(findOpts->inMsgstr){
						if(docPos.item <= 0)
						{
							beginReached=true;
						}
						else
						{
							docPos.part = Msgstr;
							docPos.item--;
							docPos.offset = msgstr(docPos.item).last().length();
							docPos.form = msgstr(docPos.item).count()-1;
						}
					}
					else {
						if(docPos.item <= 0)
						{
							beginReached=true;
						}
						else
						{
							docPos.item--;
							// FIXME: should care about plural forms in msgid
							docPos.offset = msgid(docPos.item).first().length();
						}
					}
					break;
				case UndefPart:
					break;
			}
		}
        else
        {
            if(accelMarkerPos >= 0)
            {
                if(docPos.offset >= (uint)accelMarkerPos)
                {
                    docPos.offset++;
                }
                else if(docPos.offset+len > (uint)accelMarkerPos)
                {
                    len++;
                }
            }

            if(contextInfoPos >= 0)
            {
                if(docPos.offset >= (uint)contextInfoPos)
                {
                    docPos.offset+=contextInfoLength;
                }
                else if(docPos.offset+len > (uint)contextInfoPos)
                {
                    len+=contextInfoLength;
                }

            }
        }
	}

	disconnect( this, SIGNAL( signalStopActivity() ), this, SLOT( stopInternal() ));
	kdDebug(KBABEL) << "findPrev active" << endl;
	d->_active=false;
	d->_stop=false;

	return true;
}


Catalog::DiffResult Catalog::diff(uint entry, QString *result)
{
    if(!result)
    {
        kdWarning() << "0 pointer for result" << endl;
        return DiffNotFound;
    }

    if( d->msgidDiffList.isEmpty() )
    {
        return DiffNeedList;
    }

    // first look if the diff for this entry is in the cache
    QString *s = d->diffCache[entry];
    if(s)
    {
        if(s->isEmpty())
            return DiffNotFound;


        *result = *s;
        return DiffOk;
    }

    // then look if the same msgid is contained in the diff file
    // FIXME: should care about plural forms in msgid
    QString id = msgid(entry).first();
    id.replace( "\n","");
    if(d->msgidDiffList.contains(id))
    {
	// FIXME:: should care about plural forms in msgid
        *result = msgid(entry).first();

        return DiffOk;
    }

    connect( this, SIGNAL( signalStopActivity() ), this, SLOT( stopInternal() ));
    kdDebug(KBABEL) << "diffv active" << endl;
    d->_active=true;
    d->_stop=false;

    QString idForDiff;

    // then look if there are entries with the same translation
    kdWarning() << "Diff feature (2) does not work with plural forms" << endl;
    QString str = msgstr(entry).first();
    str.replace("\n","");
    if(d->msgstr2MsgidDiffList.contains(str))
    {
        QStringList list = d->msgstr2MsgidDiffList[str];

        if(list.count() == 1)
        {
            idForDiff = list.first();
        }
        else
        {
            // find the best matching id
            double bestWeight = 0.6;
            QString bestId;

            QStringList::ConstIterator it;
            for(it = list.begin(); it != list.end(); ++it)
            {
                double weight = fstrcmp( id.utf8(), (*it).utf8() );
                if(weight > bestWeight)
                {
                    bestWeight = weight;
                    bestId = (*it);
                }
            }

            if( !bestId.isEmpty() )
            {
                idForDiff = bestId;
            }
        }
    }
    else
    {
        emit signalResetProgressBar(i18n("searching matching message")
                ,100);

        // find the best matching id
        double bestWeight = 0.6;
        QString bestId;

        int counter=0;
        int oldPercent=0;
        int max = QMAX( d->msgidDiffList.count()-1, 1);

        QStringList::ConstIterator it;
        for(it = d->msgidDiffList.begin();
                it != d->msgidDiffList.end(); ++it)
        {
            counter++;
            int  percent = 100*counter/max;
            if(percent > oldPercent)
            {
                oldPercent = percent;
                emit signalProgress(percent);
            }

            double weight = fstrcmp( id.utf8(), (*it).utf8() );
            if(weight > bestWeight)
            {
                bestWeight = weight;
                bestId = (*it);
            }

            kapp->processEvents(10);

	    if( d->_stop )
	    {
		disconnect( this, SIGNAL( signalStopActivity() ), this, SLOT( stopInternal() ));
		kdDebug

		(KBABEL) << "diffv not active" << endl;
		d->_active=false;
		d->_stop=false;
		return DiffNotFound;
	    }
        }

        if( !bestId.isEmpty() )
        {
            idForDiff = bestId;
        }

        emit signalClearProgressBar();

    }

    if( idForDiff.isEmpty() )
    {
        s = new QString(*result);
        if( !d->diffCache.insert(entry,s) )
            delete s;

	kdDebug	(KBABEL) << "diffv not active" << endl;
	d->_active=false;
	d->_stop=false;
        return DiffNotFound;
    }

    QString r = Diff::charDiff(idForDiff,id);


    // restore the newlines
    // FIXME: should care about plural forms in msgid
    QString orig = msgid(entry).first();
    if(orig.contains('\n'))
    {
        int pos=orig.find('\n');
        int oldPos=0;
        int rPos=0;
        int max = r.length();
        while(pos > 0)
        {
            int len = pos - oldPos;
            oldPos = pos+1;

            int p = 0;
            while(p < len && rPos < max)
            {
                QString m = r.mid(rPos,11);
                if(r[rPos]=='<' && m == "<KBABELDEL>")
                {
                    int endPos = r.find("</KBABELDEL>",rPos+11);
                    if(endPos > rPos)
                    {
                        rPos = endPos+12;
                    }
                    else
                    {
                        kdWarning(KBABEL) << "no closing diff remove tag found"
                                          << endl;
                        rPos+=11;
                    }
                }
                else if(r[rPos] == '<' && m == "<KBABELADD>")
                {
                    rPos+=11;
                }
                else if(r[rPos] == '<' && m == "</KBABELADD")
                {
                    rPos+=12;
                }
                else
                {
                    rPos++;
                    p++;
                }
            }

            if(rPos < max)
            {
                r.insert(rPos,'\n');
                max++;
                rPos++;
            }


            pos = orig.find('\n',pos+1);
        }
    }


    int pos = r.find("\\n");
    while(pos >= 0 )
    {
        int slash=1;
        while(pos-slash >= 0 && r[pos-slash]=='\\')
        {
            slash++;
        }
        if(slash%2 == 1 && r[pos+2]!='\n')
        {
            r.insert(pos+2,'\n');
            pos+=3;
        }
        else
        {
            pos+=2;
        }

        pos = r.find("\\n",pos);
    }

    // add newlines if line has gotten to long;
    pos = r.find('\n');
    int oldPos=0;
    const int maxLength = 60;
    while(pos >= 0)
    {
        int length=pos-oldPos;
        QString tmp = r.mid(oldPos, pos-oldPos);
        if(!tmp.contains("KBABELDEL>") )
        {
            oldPos = pos;
            pos = r.find('\n',pos+1);
            continue;
        }

        int n = tmp.contains("<KBABELADD>");
        length -= n*11;
        n = tmp.contains("<KBABELDEL>");
        length -= n*11;
        n = tmp.contains("</KBABELADD>");
        length -= n*12;
        n = tmp.contains("</KBABELDEL>");
        length -= n*12;
        while(length > maxLength)
        {
            int counter=0;
            while(counter < maxLength-10 && oldPos < pos)
            {
                if(r[oldPos]=='<' && r.mid(oldPos,11)=="<KBABELADD>")
                {
                    oldPos+=11;
                }
                else if(r[oldPos]=='<' && r.mid(oldPos,11)=="<KBABELDEL>")
                {
                    oldPos+=11;
                }
                else if(r[oldPos]=='<' && r.mid(oldPos,12)=="</KBABELADD>")
                {
                    oldPos+=12;
                }
                else if(r[oldPos]=='<' && r.mid(oldPos,12)=="</KBABELDEL>")
                {
                    oldPos+=12;
                }
                else
                {
                    counter++;
                    oldPos++;
                }
            }

            while(oldPos < pos-5 && !r[oldPos].isSpace() )
            {
                oldPos++;
            }
            oldPos++;
            if(oldPos < pos-5)
            {
                r.insert(oldPos,'\n');
                pos++;
            }
            else
            {
                break;
            }

            length=pos-oldPos;
            QString tmp = r.mid(oldPos, pos-oldPos);
            n = tmp.contains("<KBABELADD>");
            length -= n*11;
            n = tmp.contains("<KBABELDEL>");
            length -= n*11;
            n = tmp.contains("</KBABELADD>");
            length -= n*12;
            n = tmp.contains("</KBABELDEL>");
            length -= n*12;
        }

        oldPos=pos;
        pos = r.find('\n',pos+1);
    }

    // now the last line
    pos = r.length();

    int length=pos-oldPos;
    QString tmp = r.mid(oldPos, pos-oldPos);
    int n = tmp.contains("<KBABELADD>");
    length -= n*11;
    n = tmp.contains("<KBABELDEL>");
    length -= n*11;
    n = tmp.contains("</KBABELADD>");
    length -= n*12;
    n = tmp.contains("</KBABELDEL>");
    length -= n*12;
    while(length > maxLength)
    {
        int counter=0;
        while(counter < maxLength-10 && oldPos < pos)
        {
            if(r[oldPos]=='<' && r.mid(oldPos,11)=="<KBABELADD>")
            {
                oldPos+=11;
            }
            else if(r[oldPos]=='<' && r.mid(oldPos,11)=="<KBABELDEL>")
            {
                oldPos+=11;
            }
            else if(r[oldPos]=='<' && r.mid(oldPos,12)=="</KBABELADD>")
            {
                oldPos+=12;
            }
            else if(r[oldPos]=='<' && r.mid(oldPos,12)=="</KBABELDEL>")
            {
                oldPos+=12;
            }
            else
            {
                counter++;
                oldPos++;
            }
        }

        while(oldPos < pos-5 && !r[oldPos].isSpace() )
        {
            oldPos++;
        }
        oldPos++;
        if(oldPos < pos-5)
        {
            r.insert(oldPos,'\n');
            pos++;
        }
        else
        {
            break;
        }

        length=pos-oldPos;
        QString tmp = r.mid(oldPos, pos-oldPos);
        n = tmp.contains("<KBABELADD>");
        length -= n*11;
        n = tmp.contains("<KBABELDEL>");
        length -= n*11;
        n = tmp.contains("</KBABELADD>");
        length -= n*12;
        n = tmp.contains("</KBABELDEL>");
        length -= n*12;
    }

    *result = r;

    s = new QString(*result);
    if( !d->diffCache.insert(entry,s) )
        delete s;

    disconnect( this, SIGNAL( signalStopActivity() ), this, SLOT( stopInternal() ));
    kdDebug(KBABEL) << "diffv not active" << endl;
    d->_active=false;
    d->_stop=false;

    return DiffOk;
}

void Catalog::setDiffList( const QValueList<DiffEntry>& list)
{
    connect( this, SIGNAL( signalStopActivity() ), this, SLOT( stopInternal() ));
    kdDebug(KBABEL) << "setDiffList active" << endl;
    d->_active=true;
    d->_stop=false;

    emit signalResetProgressBar(i18n("preparing messages for diff"),100);

    d->msgidDiffList.clear();
    d->msgstr2MsgidDiffList.clear();
    d->diffCache.clear();

    uint max = QMAX(list.count()-1,1);
    int oldPercent=0;
    uint counter=0;
    QValueList<DiffEntry>::ConstIterator it;
    for(it = list.begin(); it != list.end(); ++it)
    {
        int percent = (100*counter)/max;
        counter++;
        if(percent > oldPercent)
        {
            oldPercent = percent;
            emit signalProgress(percent);
            kapp->processEvents(10);
        }

        QString id = (*it).msgid;
        id.replace("\n","");
        QString str = (*it).msgstr;
        str.replace("\n","");
        d->msgidDiffList.append(id);

        if(!str.isEmpty())
        {
            if(d->msgstr2MsgidDiffList.contains(str))
            {
                QStringList sl = d->msgstr2MsgidDiffList[str];
                sl.append(id);
            }
            else
            {
                QStringList sl;
                sl.append(id);
                d->msgstr2MsgidDiffList.insert(str,sl);
            }
        }
    }

    emit signalClearProgressBar();

    disconnect( this, SIGNAL( signalStopActivity() ), this, SLOT( stopInternal() ));
    kdDebug(KBABEL) << "setDiffList not active" << endl;
    d->_active=false;
    d->_stop=false;
}

QValueList<DiffEntry> Catalog::asDiffList()
{
    QValueList<DiffEntry> list;

    for ( QValueVector<CatalogItem>::Iterator it = d->_entries.begin();
					it != d->_entries.end(); ++it)
	{
        DiffEntry e;
        e.msgid = (*it).msgid().first();
	kdWarning() << "Diff feature does not support plural forms" << endl;
        e.msgstr = (*it).msgstr().first();

        list.append(e);
    }

    return list;

}

int Catalog::defaultNumberOfPluralForms() const
{
    return d->numberOfPluralForms;
}

void Catalog::getNumberOfPluralForms()
{
    if(d->_identitySettings.numberOfPluralForms > 0)
    {
        d->numberOfPluralForms = d->_identitySettings.numberOfPluralForms;
        return;
    }

    QString lang=d->_identitySettings.languageCode;
    if(lang.isEmpty())
    {
        d->numberOfPluralForms=-1;
        return;
    }

    d->numberOfPluralForms = getNumberOfPluralForms(lang);
}

int Catalog::getNumberOfPluralForms(const QString& lang)
{
    int nr=-1;

    KLocale locale("kdelibs");
    locale.setLanguage(lang);

    const char* formsString =
    "_: Dear translator, please do not translate this string in any form, but "
    "pick the _right_ value out of NoPlural/TwoForms/French... If not sure what "
    "to do mail thd@kde.org and coolo@kde.org, they will tell you. Better leave "
    "that out if unsure, the programs will crash!!\n"
    "Definition of PluralForm - to be set by the translator of kdelibs.po";

    QString formsTranslation = locale.translate(formsString);

    // no translation found
    if(formsTranslation == formsString || formsTranslation.isEmpty())
    {
        kdDebug(KBABEL) << "no translation of PluralForms found" << endl;
        return -1;
    }
    if ( formsTranslation == "NoPlural" )
      nr = 1;
    else if ( formsTranslation == "TwoForms" )
      nr = 2;
    else if ( formsTranslation == "French" )
      nr = 2;
    else if ( formsTranslation == "Gaeilge" || formsTranslation == "OneTwoRest" )
      nr = 3;
    else if ( formsTranslation == "Russian" )
      nr = 3;
    else if ( formsTranslation == "Polish" )
      nr = 3;
    else if ( formsTranslation == "Slovenian" )
      nr = 4;
    else if ( formsTranslation == "Lithuanian" )
      nr = 3;
    else if ( formsTranslation == "Czech" )
      nr = 3;
    else if ( formsTranslation == "Slovak" )
      nr = 3;
    else if ( formsTranslation == "Maltese" )
      nr = 4;
    else if ( formsTranslation == "Arabic" )
      nr = 4;
    else if ( formsTranslation == "Balcan" )
      nr = 3;
    else
    {
        kdDebug(KBABEL) << "unknown translation of PluralForms: "
            << formsTranslation << endl;
        nr=-1;
    }

    return nr;
}

int Catalog::numberOfPluralForms( uint index ) const
{
    if( index > numberOfEntries() ) return -1;

    if (  d->_entries.isEmpty() )
        return -1;
    if( d->_entries[index].pluralForm() == NoPluralForm  ) return 1;

    if( d->numberOfPluralForms > 0 )  return d->numberOfPluralForms;

    return 2; //default
}

bool Catalog::isModified() const
{
    return d->_modified;
}

void Catalog::setEntries(QValueVector<CatalogItem> entries)
{
    d->_entries=entries;
}

void Catalog::setObsoleteEntries(QValueList<CatalogItem> entries)
{
    d->_obsoleteEntries=entries;
}

QValueList<CatalogItem> Catalog::obsoleteEntries() const
{
    return d->_obsoleteEntries;
}

void Catalog::setCatalogExtraData(const QStringList& data)
{
    d->_catalogExtra = data;
}

QStringList Catalog::catalogExtraData() const
{
    return d->_catalogExtra;
}

QString Catalog::importPluginID() const
{
    return d->_importID;
}

QTextCodec* Catalog::fileCodec() const
{
    return d->fileCodec;
}

void Catalog::setGeneratedFromDocbook(const bool generated)
{
    d->_generatedFromDocbook = generated;
}

void Catalog::setFileCodec( QTextCodec* codec )
{
    d->fileCodec = codec;
}

void Catalog::setErrorIndex( const QValueList<uint>& list )
{
    d->_errorIndex = list;
}

void Catalog::setImportPluginID( const QString& id )
{
    d->_importID = id;
}

void Catalog::stop()
{
    if( d->_active )
	emit signalStopActivity();
}

void Catalog::stopInternal()
{
    d->_stop = true;
}

bool Catalog::isActive()
{
    return  d->_active;
}

void Catalog::setMimeTypes( const QString& mimeTypes )
{
    d->_mimeTypes = mimeTypes;
}

QString Catalog::mimeTypes() const
{
    return d->_mimeTypes;
}

#include "catalog.moc"
