summaryrefslogtreecommitdiffstats
path: root/kbabel/common/catalog.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'kbabel/common/catalog.cpp')
-rw-r--r--kbabel/common/catalog.cpp3509
1 files changed, 3509 insertions, 0 deletions
diff --git a/kbabel/common/catalog.cpp b/kbabel/common/catalog.cpp
new file mode 100644
index 00000000..ffd939b7
--- /dev/null
+++ b/kbabel/common/catalog.cpp
@@ -0,0 +1,3509 @@
+/* ****************************************************************************
+ This file is part of KBabel
+
+ Copyright (C) 1999-2000 by Matthias Kiefer <matthias.kiefer@gmx.de>
+ 2001-2005 by Stanislav Visnovsky <visnovsky@kde.org>
+ Copyright (C) 2006 by Nicolas GOUTTE <goutte@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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ In addition, as a special exception, the copyright holders give
+ permission to link the code of this program with any edition of
+ the Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+
+**************************************************************************** */
+#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 "kbprojectmanager.h"
+#include "catalog.h"
+#include "catalog_private.h"
+#include "catalogitem.h"
+#include "diff.h"
+#include "findoptions.h"
+#include "catalogview.h"
+#include "editcmd.h"
+#include "kbprojectsettings.h"
+
+#include "resources.h"
+#include "version.h"
+#include "stringdistance.h"
+
+#include <kmessagebox.h>
+using namespace KBabel;
+
+Catalog::Catalog(QObject* parent, const char* name, QString projectFile)
+ : QObject(parent,name)
+{
+ if ( projectFile.isEmpty() )
+ projectFile = KBabel::ProjectManager::defaultProjectName();
+ d = new CatalogPrivate(ProjectManager::open(projectFile));
+ readPreferences();
+}
+
+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;
+}
+
+QString Catalog::msgctxt(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].msgctxt();
+}
+
+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(true).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())
+ {
+ // normalize the values - ensure every key:value pair is only on a single line
+ QString values = newHeader.msgstr().first();
+ values.replace ("\n", "");
+ values.replace ("\\n", "\\n\n");
+
+ kdDebug () << "Normalized header: " << values << endl;
+
+ d->_header=newHeader;
+ d->_header.setMsgstr (values);
+
+ 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;
+
+ const IdentitySettings identityOptions = identitySettings();
+ const SaveSettings saveOptions = saveSettings();
+
+ if(!usePrefs || saveOptions.updateLastTranslator)
+ {
+ found=false;
+
+ temp="Last-Translator: "+identityOptions.authorName;
+ if(!identityOptions.authorEmail.isEmpty())
+ {
+ temp+=(" <"+identityOptions.authorEmail+">");
+ }
+ temp+="\\n";
+ for( it = headerList.begin(); it != headerList.end(); ++it )
+ {
+ if((*it).contains(QRegExp("^ *Last-Translator:.*")))
+ {
+ (*it) = temp;
+ found=true;
+ break;
+ }
+ }
+ if(!found)
+ {
+ headerList.append(temp);
+ }
+ }
+ if(!usePrefs || saveOptions.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) = temp;
+ found=true;
+ break;
+ }
+ }
+ if(!found)
+ {
+ headerList.append(temp);
+ }
+ }
+ if(!usePrefs || saveOptions.updateProject)
+ {
+ found=false;
+
+ temp="Project-Id-Version: "+saveOptions.projectString+"\\n";
+ temp.replace( "@PACKAGE@", packageName());
+
+ for( it = headerList.begin(); it != headerList.end(); ++it )
+ {
+ if((*it).contains(QRegExp("^ *Project-Id-Version:.*")))
+ {
+ (*it) = temp;
+ found=true;
+ break;
+ }
+ }
+ if(!found)
+ {
+ headerList.append(temp);
+ }
+ }
+ if(!usePrefs || saveOptions.updateLanguageTeam)
+ {
+ found=false;
+
+ temp="Language-Team: "+identityOptions.languageName;
+ if(!identityOptions.mailingList.isEmpty())
+ {
+ temp+=(" <"+identityOptions.mailingList+">");
+ }
+ temp+="\\n";
+ for( it = headerList.begin(); it != headerList.end(); ++it )
+ {
+ if((*it).contains(QRegExp("^ *Language-Team:.*")))
+ {
+ (*it) = temp;
+ found=true;
+ break;
+ }
+ }
+ if(!found)
+ {
+ headerList.append(temp);
+ }
+ }
+ if(!usePrefs || saveOptions.updateCharset)
+ {
+
+ found=false;
+
+ QString encodingStr;
+ if(saveOptions.useOldEncoding && d->fileCodec)
+ {
+ encodingStr = charsetString(d->fileCodec);
+ }
+ else
+ {
+ encodingStr=charsetString(saveOptions.encoding);
+ }
+
+ temp = "Content-Type: text/plain; charset=" + encodingStr + "\\n";
+
+ it = headerList.begin();
+ while( it != headerList.end() )
+ {
+ if( (*it).find( QRegExp( "^ *Content-Type:.*" ) ) != -1 )
+ {
+ if ( found )
+ {
+ // We had already a Content-Type, so we do not need a duplicate
+ it = headerList.remove( it );
+ }
+ else
+ {
+ found=true;
+ QRegExp regexp( "^ *Content-Type:(.*/.*);?\\s*charset=.*$" );
+ QString mimeType;
+ if ( regexp.search( *it ) )
+ {
+ mimeType = regexp.cap( 1 ).stripWhiteSpace();
+ }
+ if ( mimeType.isEmpty() )
+ {
+ mimeType = "text/plain";
+ }
+ temp = "Content-Type: ";
+ temp += mimeType;
+ temp += "; charset=";
+ temp += encodingStr;
+ temp += "\\n";
+ (*it) = temp;
+ }
+ }
+ ++it;
+ }
+ if(!found)
+ {
+ headerList.append(temp);
+ }
+ }
+ if(!usePrefs || saveOptions.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) = temp;
+ found=true;
+ break;
+ }
+ }
+ 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) = temp;
+ found=true;
+ break;
+ }
+ }
+ 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:")))
+ {
+ (*it) = temp;
+ found=true;
+ break;
+ }
+ }
+ if( !found )
+ {
+ headerList.append(temp);
+ }
+
+
+ temp="Plural-Forms: %1\\n";
+ temp=temp.arg(identityOptions.gnuPluralFormHeader);
+ found=false;
+
+ // update plural form header
+ if( !identityOptions.gnuPluralFormHeader.isEmpty() )
+ {
+ for( it = headerList.begin(); it != headerList.end(); ++it )
+ {
+ if((*it).contains(QRegExp("^ *Plural-Forms:")))
+ {
+ (*it) = temp;
+ found=true;
+ break;
+ }
+ }
+ if( !found )
+ {
+ headerList.append(temp);
+ }
+ }
+
+ oldHeader.setMsgstr( headerList.join( "\n" ) );
+
+ //comment = description, copyrights
+ if(!usePrefs || (saveOptions.FSFCopyright != ProjectSettingsBase::NoChange))
+ {
+ found=false;
+
+ for( it = commentList.begin(); it != commentList.end(); ++it )
+ {
+ // U+00A9 is the Copyright sign
+ if ( (*it).find( QRegExp("^# *Copyright (\\(C\\)|\\x00a9).*Free Software Foundation, Inc") ) != -1 )
+ {
+ found=true;
+ break;
+ }
+ }
+ if(found)
+ {
+ if ( (*it).find( QRegExp("^# *Copyright (\\(C\\)|\\x00a9) YEAR Free Software Foundation, Inc\\.") ) != -1 )
+ {
+ //template string
+ if( saveOptions.FSFCopyright == ProjectSettingsBase::Remove)
+ (*it).remove(" YEAR Free Software Foundation, Inc");
+ else
+ (*it).replace("YEAR", QDate::currentDate().toString("yyyy"));
+ } else
+ if( saveOptions.FSFCopyright == ProjectSettingsBase::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 || saveOptions.updateDescription )
+ && ( !saveOptions.descriptionString.isEmpty() ) )
+ {
+ temp = "# "+saveOptions.descriptionString;
+ temp.replace( "@PACKAGE@", packageName());
+ temp.replace( "@LANGUAGE@", identityOptions.languageName);
+ temp = temp.stripWhiteSpace();
+
+ // The description strings has often buggy variants already in the file, these must be removed
+ QString regexpstr = "^#\\s+" + QRegExp::escape( saveOptions.descriptionString.stripWhiteSpace() ) + "\\s*$";
+ regexpstr.replace( "@PACKAGE@", ".*" );
+ regexpstr.replace( "@LANGUAGE@", ".*" );
+ //kdDebug() << "REGEXPSTR: " << regexpstr << endl;
+ QRegExp regexp ( regexpstr );
+
+ // The buggy variants exist in English too (of a time before KBabel got a translation for the corresponding language)
+ QRegExp regexpUntranslated ( "^#\\s+Translation of .* into .*\\s*$" );
+
+ kdDebug () << "Temp is '" << temp << "'" << endl;
+
+ found=false;
+//not used anyway bool foundTemplate=false;
+
+ it = commentList.begin();
+ while ( it != commentList.end() )
+ {
+ kdDebug () << "testing '" << (*it) << "'" << endl;
+ bool deleteItem = false;
+
+ if ( (*it) == temp )
+ {
+ kdDebug () << "Match " << endl;
+ if ( found )
+ deleteItem = true;
+ else
+ found=true;
+ }
+ else if ( regexp.search( *it ) >= 0 )
+ {
+ // We have a similar (translated) string (from another project or another language (perhaps typos)). Remove it.
+ deleteItem = true;
+ }
+ else if ( regexpUntranslated.search( *it ) >= 0 )
+ {
+ // We have a similar (untranslated) string (from another project or another language (perhaps typos)). Remove it.
+ deleteItem = true;
+ }
+ else if ( (*it) == "# SOME DESCRIPTIVE TITLE." )
+ {
+ // We have the standard title placeholder, remove it
+ deleteItem = true;
+ }
+
+ if ( deleteItem )
+ it = commentList.remove( it );
+ else
+ ++it;
+ }
+ if(!found) commentList.prepend(temp);
+ }
+
+ // kdDebug() << "HEADER COMMENT: " << commentList << endl;
+
+ if ( (!usePrefs || saveOptions.updateTranslatorCopyright)
+ && ( ! identityOptions.authorName.isEmpty() )
+ && ( ! identityOptions.authorEmail.isEmpty() ) ) // An email address can be used as ersatz of a name
+ {
+ QStringList foundAuthors;
+
+ temp = "# ";
+ temp += identityOptions.authorName;
+ if(!identityOptions.authorEmail.isEmpty())
+ {
+ temp+=(" <"+identityOptions.authorEmail+">");
+ }
+ temp+=", "+QDate::currentDate().toString("yyyy")+".";
+
+ // ### TODO: it would be nice if the entry could start with "COPYRIGHT" and have the "(C)" symbol (both not mandatory)
+ QRegExp regexpAuthorYear( "^#.*(<.+@.+>)?,\\s*([\\d]+[\\d\\-, ]*|YEAR)" );
+ QRegExp regexpYearAlone( "^# , \\d{4}.?\\s*$" );
+ it = commentList.begin();
+ while ( it != commentList.end() )
+ {
+ bool deleteItem = false;
+ if ( (*it).find ( "copyright", 0, false ) != -1 )
+ {
+ // We have a line with a copyright. It should not be moved.
+ }
+ else if ( (*it).find ( regexpYearAlone ) != -1 )
+ {
+ // We have found a year number that is preceeded by a comma.
+ // That is typical of KBabel 1.10 (and earlier?) when there is neither an author name nor an email
+ // Remove the entry
+ deleteItem = true;
+ }
+ else if ( (*it) == "# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR." )
+ {
+ // Typical placeholder, remove it.
+ deleteItem = true;
+ }
+ else if ( (*it).find ( regexpAuthorYear ) != -1 ) // email address followed by year
+ {
+ if ( foundAuthors.find( (*it) ) == foundAuthors.end() )
+ {
+ // The author line is new (and not a duplicate), so add it to the author line list
+ foundAuthors.append( (*it) );
+ }
+ // Delete also non-duplicated entry, as now all what is needed will be processed in foundAuthors
+ deleteItem = true;
+ }
+
+ if ( deleteItem )
+ it = commentList.remove( it );
+ else
+ ++it;
+ }
+
+ if( !foundAuthors.isEmpty() )
+ {
+ found = false;
+ bool foundAuthor = false;
+
+ const QString cy = QDate::currentDate().toString("yyyy");
+
+ ait = foundAuthors.end();
+ for( it = foundAuthors.begin() ; it!=foundAuthors.end(); ++it )
+ {
+ if ( (*it).find( QRegExp(
+ QRegExp::escape( identityOptions.authorName )+".*"
+ + QRegExp::escape( identityOptions.authorEmail ) ) ) != -1 )
+ {
+ foundAuthor = true;
+ if( (*it).find( cy ) != -1 )
+ found = true;
+ else
+ ait = it;
+ }
+ }
+ if( !found )
+ {
+ if ( !foundAuthor )
+ foundAuthors.append(temp);
+ else if ( ait != foundAuthors.end() )
+ {
+ //update years
+ const int index = (*ait).findRev( QRegExp("[\\d]+[\\d\\-, ]*") );
+ if ( index == -1 )
+ (*ait)+=", "+cy;
+ else
+ (*ait).insert(index+1, QString(", ")+cy);
+ }
+ else
+ kdDebug() << "INTERNAL ERROR: author found but iterator dangling!" << endl;
+ }
+
+ }
+ else
+ foundAuthors.append(temp);
+ it=commentList.end();
+ do
+ --it;
+ while( ( it != commentList.begin() ) && ( (*it).find( QRegExp( "^#(\\s*$|[:,\\.])" ) ) == -1 ) );
+ ++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);
+ }
+ }
+
+ oldHeader.setComment( commentList.join( "\n" ) );
+
+ 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("(\\."+identitySettings().languageCode+")?\\.pot?$"));
+
+ if(index>0)
+ package=package.left(index);
+
+ return package;
+}
+
+void Catalog::setPackage(const QString& package )
+{
+ const int pos = package.findRev( '/' );
+ if( pos < 0 )
+ {
+ d->_packageDir = QString();
+ d->_packageName = package;
+ }
+ else
+ {
+ d->_packageDir = package.left( pos + 1 ); // We want the / included
+ d->_packageName = package.mid( pos + 1 ); // We do not want /
+ }
+ kdDebug() << k_funcinfo << " " << package << " => " << d->_packageDir << " + " << d->_packageName << endl;
+}
+
+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
+{
+ SaveSettings options = saveSettings();
+
+ QString encodingStr;
+ if(options.useOldEncoding && d->fileCodec)
+ {
+ encodingStr = charsetString(d->fileCodec);
+ }
+ else
+ {
+ encodingStr= charsetString(options.encoding);
+ }
+
+ return encodingStr;
+}
+
+ConversionStatus Catalog::openURL(const KURL& url, const QString& package)
+{
+ QString target;
+ ConversionStatus error = OK;
+
+ if(KIO::NetAccess::download(url, target, NULL))
+ {
+ CatalogImportPlugin* filter=0;
+
+ // gimme plugin for this MIME type
+ KMimeType::Ptr mime = KMimeType::findByURL( url, 0, true );
+ kdDebug() << "Found mimetype: " << mime->name() << endl;
+ 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, will try PO" << endl;
+ offers = KTrader::self()->query("KBabelFilter", "('application/x-gettext' in [X-KDE-Import])");
+ ptr = offers.first();
+ if( !ptr )
+ {
+ kdDebug(KBABEL) << "No plugin for PO files, giving up" << 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 == RECOVERED_HEADER_ERROR )
+ {
+ const uint entries = numberOfEntries();
+
+ if ( !entries )
+ {
+ // KBabel cannot work correctly with not any entry
+ kdWarning() << k_funcinfo << " No entries! Assuming parse error!" << endl;
+ delete filter;
+ return NO_ENTRY_ERROR;
+ }
+
+ //kdDebug( KBABEL ) << k_funcinfo << " Success (full or partial) " << entries << endl;
+ setModified(false);
+ d->_url=url;
+
+ if( package.isEmpty() )
+ {
+ d->_packageName=QString::null;
+ d->_packageDir=QString::null;
+ }
+ else setPackage(package);
+
+ emit signalFileOpened(d->_readOnly);
+ emit signalNumberOfFuzziesChanged(numberOfFuzzies());
+ emit signalNumberOfUntranslatedChanged(numberOfUntranslated());
+ emit signalTotalNumberChanged( entries );
+ }
+
+ 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, NULL))
+ {
+ CatalogImportPlugin* filter=0;
+
+ // gimme plugin for this MIME type
+ KMimeType::Ptr mime = KMimeType::findByURL( openUrl, 0, true );
+ 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;
+ KIO::NetAccess::removeTempFile(target);
+ return STOPPED;
+ }
+
+ // Templates should not have recoverable errors (or they are bad templates)
+ if( error == OK )
+ {
+ const uint entries = numberOfEntries();
+
+ if ( !entries )
+ {
+ // KBabel cannot work correctly with not any entry
+ kdWarning() << k_funcinfo << " No entries! Assuming parse error!" << endl;
+ delete filter;
+ KIO::NetAccess::removeTempFile(target);
+ return NO_ENTRY_ERROR;
+ }
+
+ setModified(false);
+ d->_url = saveURL;
+ if( package.isEmpty() )
+ {
+ d->_packageName=QString::null;
+ d->_packageDir=QString::null;
+ }
+ else setPackage(package);
+
+ emit signalFileOpened(d->_readOnly);
+ emit signalNumberOfFuzziesChanged(numberOfFuzzies());
+ emit signalNumberOfUntranslatedChanged(numberOfUntranslated());
+ emit signalTotalNumberChanged( entries );
+ }
+
+ 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;
+
+ // ### KDE4: return "lines" not "output"
+ const QStringList lines = QStringList::split("\n",output);
+ for ( QStringList::const_iterator it = lines.constBegin(); it != lines.constEnd(); ++it )
+ {
+ if( (*it).find(QRegExp("^.+:\\d+:")) >= 0 )
+ {
+ const int begin=(*it).find(":",0)+1;
+ const int end=(*it).find(":",begin);
+
+ const 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->_errorIndex.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::useProject(Project::Ptr project)
+{
+ d->_project->config()->sync();
+ d->_project = project;
+ readPreferences();
+ emit signalSettingsChanged(saveSettings());
+ emit signalSettingsChanged(identitySettings());
+ emit signalSettingsChanged(miscSettings());
+ emit signalSettingsChanged(tagSettings());
+}
+
+Project::Ptr Catalog::project() const
+{
+ return d->_project;
+}
+
+void Catalog::readPreferences()
+{
+ getNumberOfPluralForms();
+
+ d->_project->config()->setGroup("Tags");
+ d->_tagSettings.tagExpressions=d->_project->config()->readListEntry("TagExpressions");
+ if( d->_tagSettings.tagExpressions.empty() ) d->_tagSettings.tagExpressions = Defaults::Tag::tagExpressions();
+ d->_tagExtractor->setRegExpList(d->_tagSettings.tagExpressions) ;
+
+ d->_tagSettings.argExpressions=d->_project->config()->readListEntry("ArgExpressions");
+ if( d->_tagSettings.argExpressions.empty() ) d->_tagSettings.argExpressions = Defaults::Tag::argExpressions();
+ d->_argExtractor->setRegExpList(d->_tagSettings.argExpressions) ;
+}
+
+void Catalog::savePreferences()
+{
+ d->_project->config()->setGroup("Tags");
+
+ d->_project->config()->writeEntry( "TagExpressions", d->_tagSettings.tagExpressions );
+ d->_project->config()->writeEntry( "ArgExpressions", d->_tagSettings.argExpressions );
+
+ d->_project->config()->sync();
+}
+
+IdentitySettings Catalog::identitySettings() const
+{
+ return d->_project->identitySettings();
+}
+
+SaveSettings Catalog::saveSettings() const
+{
+ return d->_project->saveSettings();
+
+}
+
+MiscSettings Catalog::miscSettings() const
+{
+ return d->_project->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->_project->setSettings(settings);
+}
+
+void Catalog::setSettings(IdentitySettings settings)
+{
+ IdentitySettings oldsettings = d->_project->identitySettings();
+
+ QString oldLanguageCode = oldsettings.languageCode;
+ int oldForms = oldsettings.numberOfPluralForms;
+
+
+ d->_project->setSettings(settings);
+
+ if(oldLanguageCode != settings.languageCode)
+ {
+ getNumberOfPluralForms();
+ }
+
+ if(oldForms != settings.numberOfPluralForms)
+ {
+ getNumberOfPluralForms();
+ }
+
+ emit signalSettingsChanged(settings);
+}
+
+void Catalog::setSettings(MiscSettings settings)
+{
+ d->_project->setSettings(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;
+ }
+
+ if ( it == list.constBegin() )
+ {
+ // Decremeniting the iterator at the begin is undefined, so break the loop
+ break;
+ }
+ }
+
+
+ return prevIndex;
+}
+
+
+QString Catalog::dateTime() const
+{
+ const QDateTime dt = QDateTime::currentDateTime();
+ QString dateTimeString;
+
+ const SaveSettings options = d->_project->saveSettings();
+
+ switch(options.dateFormat)
+ {
+ case Qt::LocalDate:
+ {
+ dateTimeString = KGlobal::locale()->formatDateTime( dt );
+ break;
+ }
+ case Qt::ISODate:
+ {
+ dateTimeString = dt.toString("yyyy-MM-dd hh:mm");
+ QTime t;
+ const int offset = KRFCDate::localUTCOffset();
+ const int correction = offset < 0 ? -60 : 60 ;
+ t = t.addSecs( offset * correction );
+ dateTimeString += ( offset < 0 ? "-" : "+" );
+ dateTimeString += t.toString("hhmm");
+ break;
+ }
+ case Qt::TextDate:
+ {
+ dateTimeString = options.customDateFormat;
+
+ const QDate date = dt.date();
+ const QTime time = dt.time();
+
+ // 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; // AM or PM
+ int hour = time.hour();
+ if( hour > 12 )
+ {
+ zone="PM";
+ hour -= 12;
+ }
+ else
+ {
+ zone="AM";
+ }
+
+ 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->_project->identitySettings().timeZone );
+ QTime t;
+ const int offset = KRFCDate::localUTCOffset();
+ const int correction = offset < 0 ? -60 : 60;
+ t = t.addSecs( offset * correction );
+ dateTimeString.replace( "%z", ( offset < 0 ? "-" : "+" ) + t.toString("hhmm") );
+ break;
+ }
+ }
+ kdDebug(KBABEL) << "New date: " << dateTimeString << endl;
+ 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->_project->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)
+ {
+ if( !KIO::NetAccess::upload( tempFile, targetURL, NULL ) )
+ {
+ 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( KURL::fromPathOrURL( 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)
+{
+ kdDebug(KBABEL) << "Catalog::processCommand()" << endl;
+ 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() ));
+
+ MiscSettings miscOptions = miscSettings();
+
+ 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 = miscOptions.contextInfo.search(targetStr);
+ contextInfoLength = miscOptions.contextInfo.matchedLength();
+ if(contextInfoPos >= 0)
+ {
+ targetStr.remove(contextInfoPos,contextInfoLength);
+
+ if(docPos.offset > (uint)contextInfoPos)
+ docPos.offset -= contextInfoLength;
+ }
+ }
+
+ if(findOpts->ignoreAccelMarker
+ && targetStr.contains(miscOptions.accelMarker))
+ {
+ accelMarkerPos = targetStr.find(miscOptions.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++;
+ docPos.form = 0;
+ }
+ }
+ 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++;
+ docPos.form = 0;
+ }
+ }
+ break;
+ case Comment:
+ if(findOpts->inMsgid) {
+ if(docPos.item >= numberOfEntries()-1)
+ {
+ endReached=true;
+ }
+ else
+ {
+ docPos.part = Msgid;
+ docPos.item++;
+ docPos.form = 0;
+ }
+ }
+ 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++;
+ docPos.form = 0;
+ }
+ }
+ 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() ));
+
+ MiscSettings miscOptions = miscSettings();
+
+ 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 = miscOptions.contextInfo.search(targetStr);
+ contextInfoLength = miscOptions.contextInfo.matchedLength();
+ if(contextInfoPos >= 0)
+ {
+ targetStr.remove(contextInfoPos,contextInfoLength);
+
+ if(docPos.offset > (uint)contextInfoPos)
+ docPos.offset -= contextInfoLength;
+ }
+ }
+
+ if(findOpts->ignoreAccelMarker
+ && targetStr.contains(miscOptions.accelMarker))
+ {
+ accelMarkerPos = targetStr.find(miscOptions.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 = LevenshteinDistance()(id, (*it));
+ 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 = LevenshteinDistance()( id, (*it) );
+ 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 = wordDiff(idForDiff,id);
+
+ //esp for plural forms
+ *result = r.replace("\\n<KBABELADD>" + QString(QChar(0x00B6)) + "</KBABELADD>", "\\n\n");
+
+ 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()
+{
+ IdentitySettings options = identitySettings();
+
+ if(options.numberOfPluralForms > 0)
+ {
+ d->numberOfPluralForms = options.numberOfPluralForms;
+ return;
+ }
+
+ QString lang=options.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;
+
+ // update the project for entries
+ for ( QValueVector<CatalogItem>::Iterator it = d->_entries.begin();
+ it != d->_entries.end(); ++it)
+ {
+ it->setProject( d->_project );
+ }
+}
+
+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;
+}
+
+void Catalog::wordCount (uint &total, uint &fuzzy, uint &untranslated) const
+{
+ total = 0;
+ fuzzy = 0;
+ untranslated = 0;
+
+ QRegExp separator( "[ \n\t]+" );
+
+ for ( QValueVector<CatalogItem>::Iterator it = d->_entries.begin();
+ it != d->_entries.end(); ++it)
+ {
+ // find out the number of words for this message
+
+ // join all forms together
+ QString message = (*it).msgid ().join (" ");
+
+ // remove tags first
+ d->_tagExtractor->setString( message );
+ message = d->_tagExtractor->plainString(false);
+
+ QStringList words = QStringList::split ( separator, message );
+
+ total += words.count();
+
+ if ( (*it).isFuzzy() )
+ fuzzy += words.count();
+ else if ( (*it).isUntranslated() )
+ untranslated += words.count();
+ }
+}
+
+#include "catalog.moc"
+
+// kate: space-indent on; indent-width 4; replace-tabs on;