diff --git a/src/alttransview.cpp b/src/alttransview.cpp index f351a4e..a1089be 100644 --- a/src/alttransview.cpp +++ b/src/alttransview.cpp @@ -1,330 +1,328 @@ /* **************************************************************************** This file is part of Lokalize Copyright (C) 2007-2014 by Nick Shaforostoff 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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, see . **************************************************************************** */ #include "alttransview.h" #include "lokalize_debug.h" #include "diff.h" #include "catalog.h" #include "cmd.h" #include "project.h" #include "xlifftextedit.h" #include "tmview.h" //TextBrowser #include "mergecatalog.h" #include "prefs_lokalize.h" -#include "kdemacros.h" - #include #include #include #include #include #include #include #include #include #include AltTransView::AltTransView(QWidget* parent, Catalog* catalog,const QVector& actions) : QDockWidget ( i18nc("@title:window","Alternate Translations"), parent) , m_browser(new TM::TextBrowser(this)) , m_catalog(catalog) , m_normTitle(i18nc("@title:window","Alternate Translations")) , m_hasInfoTitle(m_normTitle+QStringLiteral(" [*]")) , m_hasInfo(false) , m_everShown(false) , m_actions(actions) { setObjectName(QStringLiteral("msgIdDiff")); setWidget(m_browser); hide(); m_browser->setReadOnly(true); m_browser->viewport()->setBackgroundRole(QPalette::Background); QTimer::singleShot(0,this,SLOT(initLater())); } void AltTransView::initLater() { setAcceptDrops(true); #ifndef NOKDE KConfig config; KConfigGroup group(&config,"AltTransView"); m_everShown=group.readEntry("EverShown",false); #endif QSignalMapper* signalMapper=new QSignalMapper(this); int i=m_actions.size(); while(--i>=0) { connect(m_actions.at(i),SIGNAL(triggered()),signalMapper,SLOT(map())); signalMapper->setMapping(m_actions.at(i), i); } connect(signalMapper, SIGNAL(mapped(int)), this, SLOT(slotUseSuggestion(int))); connect(m_browser,SIGNAL(textInsertRequested(QString)),this,SIGNAL(textInsertRequested(QString))); //connect(m_browser,SIGNAL(customContextMenuRequested(QPoint)),this,SLOT(contextMenu(QPoint))); } AltTransView::~AltTransView() { } void AltTransView::dragEnterEvent(QDragEnterEvent* event) { if(event->mimeData()->hasUrls() && Catalog::extIsSupported(event->mimeData()->urls().first().path())) event->acceptProposedAction(); } void AltTransView::dropEvent(QDropEvent *event) { event->acceptProposedAction(); attachAltTransFile(event->mimeData()->urls().first().toLocalFile()); //update m_prevEntry.entry=-1; QTimer::singleShot(0,this,SLOT(process())); } void AltTransView::attachAltTransFile(const QString& path) { MergeCatalog* altCat=new MergeCatalog(m_catalog, m_catalog, /*saveChanges*/false); altCat->loadFromUrl(path); m_catalog->attachAltTransCatalog(altCat); } void AltTransView::addAlternateTranslation(int entry, const QString& trans, bool temp) { AltTrans altTrans; altTrans.target=trans; m_catalog->attachAltTrans(entry, altTrans); m_prevEntry=DocPos(); QTimer::singleShot(0,this,SLOT(process())); } void AltTransView::fileLoaded() { m_prevEntry.entry=-1; QString absPath=m_catalog->url(); QString relPath=QDir(Project::instance()->projectDir()).relativeFilePath(absPath); QFileInfo info(Project::instance()->altTransDir()%'/'%relPath); if (info.canonicalFilePath()!=absPath && info.exists()) attachAltTransFile(info.canonicalFilePath()); else qCWarning(LOKALIZE_LOG)<<"alt trans file doesn't exist:"<altTransDir()%'/'%relPath; } void AltTransView::slotNewEntryDisplayed(const DocPosition& pos) { m_entry=DocPos(pos); QTimer::singleShot(0,this,SLOT(process())); } void AltTransView::process() { if (m_entry==m_prevEntry) return; if (m_catalog->numberOfEntries()<=m_entry.entry) return;//because of Qt::QueuedConnection m_prevEntry=m_entry; m_browser->clear(); m_entryPositions.clear(); const QVector& entries=m_catalog->altTrans(m_entry.toDocPosition()); m_entries=entries; if (entries.isEmpty()) { if (m_hasInfo) { m_hasInfo=false; setWindowTitle(m_normTitle); } return; } if (!m_hasInfo) { m_hasInfo=true; setWindowTitle(m_hasInfoTitle); } if(!isVisible() && !Settings::altTransViewEverShownWithData()) { if (KMessageBox::questionYesNo(this,i18n("There is useful data available in Alternate Translations view.\n\n" "For Gettext PO files it displays difference between current source text " "and the source text corresponding to the fuzzy translation found by msgmerge when updating PO based on POT template.\n\n" "Do you want to show the view with the data?"), m_normTitle)==KMessageBox::Yes) show(); Settings::setAltTransViewEverShownWithData(true); } CatalogString source=m_catalog->sourceWithTags(m_entry.toDocPosition()); QTextBlockFormat blockFormatBase; QTextBlockFormat blockFormatAlternate; blockFormatAlternate.setBackground(QPalette().alternateBase()); QTextCharFormat noncloseMatchCharFormat; QTextCharFormat closeMatchCharFormat; closeMatchCharFormat.setFontWeight(QFont::Bold); int i=0; int limit=entries.size(); forever { const AltTrans& entry=entries.at(i); QTextCursor cur=m_browser->textCursor(); QString html; html.reserve(1024); if (!entry.source.isEmpty()) { html+=QStringLiteral("

"); QString result=userVisibleWordDiff(entry.source.string, source.string,Project::instance()->accel(),Project::instance()->markup()).toHtmlEscaped(); //result.replace("&","&"); //result.replace("<","<"); //result.replace(">",">"); result.replace(QStringLiteral("{KBABELADD}"),QStringLiteral("")); result.replace(QStringLiteral("{/KBABELADD}"),QStringLiteral("")); result.replace(QStringLiteral("{KBABELDEL}"),QStringLiteral("")); result.replace(QStringLiteral("{/KBABELDEL}"),QStringLiteral("")); result.replace(QStringLiteral("\\n"),QStringLiteral("\\n

")); html+=result; html+=QStringLiteral("
"); cur.insertHtml(html); html.clear(); } if (!entry.target.isEmpty()) { - if (KDE_ISLIKELY( isetStatusTip(entry.target.string); html+=QString(QStringLiteral("[%1] ")).arg(m_actions.at(i)->shortcut().toString(QKeySequence::NativeText)); } else html+=QStringLiteral("[ - ] "); cur.insertText(html); html.clear(); insertContent(cur,entry.target); } m_entryPositions.insert(cur.anchor(),i); html+=i?QStringLiteral("

"):QStringLiteral("

"); cur.insertHtml(html); - if (KDE_ISUNLIKELY( ++i>=limit )) + if (Q_UNLIKELY( ++i>=limit )) break; cur.insertBlock(i%2?blockFormatAlternate:blockFormatBase); } #ifndef NOKDE if (!m_everShown) { m_everShown=true; show(); KConfig config; KConfigGroup group(&config,"AltTransView"); group.writeEntry("EverShown",true); } #endif } bool AltTransView::event(QEvent *event) { if (event->type()==QEvent::ToolTip) { QHelpEvent *helpEvent = static_cast(event); if (m_entryPositions.isEmpty()) { QString tooltip=i18nc("@info:tooltip","

Sometimes, if source text is changed, its translation becomes deprecated and is either marked as needing review (i.e. looses approval status), " "or (only in case of XLIFF file) moved to the alternate translations section accompanying the unit.

" "

This toolview also shows the difference between current source string and the previous source string, so that you can easily see which changes should be applied to existing translation to make it reflect current source.

" "

Double-clicking any word in this toolview inserts it into translation.

" "

Drop translation file onto this toolview to use it as a source for additional alternate translations.

" ); QToolTip::showText(helpEvent->globalPos(),tooltip); return true; } int block1=m_browser->cursorForPosition(m_browser->viewport()->mapFromGlobal(helpEvent->globalPos())).blockNumber(); int block=*m_entryPositions.lowerBound(m_browser->cursorForPosition(m_browser->viewport()->mapFromGlobal(helpEvent->globalPos())).anchor()); if (block1!=block) qCWarning(LOKALIZE_LOG)<<"block numbers don't match"; if (block>=m_entries.size()) return false; QString origin=m_entries.at(block).origin; if (origin.isEmpty()) return false; QString tooltip=i18nc("@info:tooltip","Origin: %1",origin); QToolTip::showText(helpEvent->globalPos(),tooltip); return true; } return QWidget::event(event); } void AltTransView::slotUseSuggestion(int i) { - if (KDE_ISUNLIKELY( i>=m_entries.size() )) + if (Q_UNLIKELY( i>=m_entries.size() )) return; TM::TMEntry tmEntry; tmEntry.target=m_entries.at(i).target; CatalogString source=m_catalog->sourceWithTags(m_entry.toDocPosition()); tmEntry.diff=userVisibleWordDiff(m_entries.at(i).source.string, source.string,Project::instance()->accel(),Project::instance()->markup()); CatalogString target=TM::targetAdapted(tmEntry, source); qCWarning(LOKALIZE_LOG)<<"0"<beginMacro(i18nc("@item Undo action","Use alternate translation")); QString old=m_catalog->targetWithTags(m_entry.toDocPosition()).string; if (!old.isEmpty()) { //FIXME test! removeTargetSubstring(m_catalog, m_entry.toDocPosition(), 0, old.size()); //m_catalog->push(new DelTextCmd(m_catalog,m_pos,m_catalog->msgstr(m_pos))); } qCWarning(LOKALIZE_LOG)<<"1"<push(new InsTextCmd(m_catalog,m_pos,target)/*,true*/); insertCatalogString(m_catalog, m_entry.toDocPosition(), target, 0); m_catalog->endMacro(); emit refreshRequested(); } diff --git a/src/catalog/catalog.cpp b/src/catalog/catalog.cpp index f19385f..0ba14da 100644 --- a/src/catalog/catalog.cpp +++ b/src/catalog/catalog.cpp @@ -1,1084 +1,1084 @@ /* **************************************************************************** This file is part of Lokalize This file contains parts of KBabel code Copyright (C) 1999-2000 by Matthias Kiefer 2001-2005 by Stanislav Visnovsky 2006 by Nicolas Goutte 2007-2014 by Nick Shaforostoff 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 "catalog.h" #include "catalog_private.h" #include "project.h" #ifndef NOKDE #include "projectmodel.h" //to notify about modification #endif #include "catalogstorage.h" #include "gettextstorage.h" #include "gettextimport.h" #include "gettextexport.h" #include "xliffstorage.h" #include "tsstorage.h" #include "mergecatalog.h" #include "version.h" #include "prefs_lokalize.h" #include "jobs.h" #include "dbfilesmodel.h" #include #include #include #include #include #include #include #include #ifdef Q_OS_WIN #define U QLatin1String #else #define U QStringLiteral #endif //QString Catalog::supportedMimeFilters("text/x-gettext-translation application/x-xliff application/x-linguist"); //" text/x-gettext-translation-template") QString Catalog::supportedFileTypes(bool includeTemplates) { QString sep=QStringLiteral(";;"); QString all=i18n("All supported files (*.po *.pot *.xlf *.xliff *.ts)")+sep; return all+(includeTemplates ? i18n("Gettext (*.po *.pot)") : i18n("Gettext (*.po)"))+sep+i18n("XLIFF (*.xlf *.xliff)")+sep+i18n("Linguist (*.ts)"); } static const QString extensions[]={U(".po"),U(".pot"),U(".xlf"),U(".xliff"),U(".ts")}; static const char* const xliff_states[]={ I18N_NOOP("New"),I18N_NOOP("Needs translation"),I18N_NOOP("Needs full localization"),I18N_NOOP("Needs adaptation"),I18N_NOOP("Translated"), I18N_NOOP("Needs translation review"),I18N_NOOP("Needs full localization review"),I18N_NOOP("Needs adaptation review"),I18N_NOOP("Final"), I18N_NOOP("Signed-off")}; const char* const* Catalog::states() { return xliff_states; } QStringList Catalog::supportedExtensions() { QStringList result; int i=sizeof(extensions)/sizeof(QString); while (--i>=0) result.append(extensions[i]); return result; } bool Catalog::extIsSupported(const QString& path) { QStringList ext=supportedExtensions(); int i=ext.size(); while (--i>=0 && !path.endsWith(ext.at(i))) ; return i!=-1; } Catalog::Catalog(QObject *parent) : QUndoStack(parent) , d(this) , m_storage(0) { #ifndef NOKDE //cause refresh events for files modified from lokalize itself aint delivered automatically connect(this,SIGNAL(signalFileSaved(QString)), Project::instance()->model(),SLOT(slotFileSaved(QString)),Qt::QueuedConnection); QTimer* t=&(d._autoSaveTimer); t->setInterval(2*60*1000); t->setSingleShot(false); connect(t, &QTimer::timeout, this, &Catalog::doAutoSave); connect(this,SIGNAL(signalFileSaved()), t,SLOT(start())); connect(this,SIGNAL(signalFileLoaded()), t,SLOT(start())); connect(this, &Catalog::indexChanged, this, &Catalog::setAutoSaveDirty); #endif connect(Project::local(),SIGNAL(configChanged()),this,SLOT(projectConfigChanged())); } Catalog::~Catalog() { clear(); //delete m_storage; //deleted in clear(); } void Catalog::clear() { setIndex(cleanIndex());//to keep TM in sync QUndoStack::clear(); d._errorIndex.clear(); d._nonApprovedIndex.clear(); d._emptyIndex.clear(); delete m_storage;m_storage=0; d._filePath.clear(); d._lastModifiedPos=DocPosition(); d._modifiedEntries.clear(); while (!d._altTransCatalogs.isEmpty()) d._altTransCatalogs.takeFirst()->deleteLater(); d._altTranslations.clear(); /* d.msgidDiffList.clear(); d.msgstr2MsgidDiffList.clear(); d.diffCache.clear(); */ } void Catalog::push(QUndoCommand* cmd) { generatePhaseForCatalogIfNeeded(this); QUndoStack::push(cmd); } //BEGIN STORAGE TRANSLATION int Catalog::capabilities() const { if (Q_UNLIKELY( !m_storage )) return 0; return m_storage->capabilities(); } int Catalog::numberOfEntries() const { if (Q_UNLIKELY( !m_storage )) return 0; return m_storage->size(); } static DocPosition alterForSinglePlural(const Catalog* th, DocPosition pos) { //if source lang is english (implied) and target lang has only 1 plural form (e.g. Chinese) if (Q_UNLIKELY(th->numberOfPluralForms()==1 && th->isPlural(pos))) pos.form=1; return pos; } QString Catalog::msgid(const DocPosition& pos) const { if (Q_UNLIKELY( !m_storage )) return QString(); return m_storage->source(alterForSinglePlural(this, pos)); } QString Catalog::msgstr(const DocPosition& pos) const { if (Q_UNLIKELY( !m_storage )) return QString(); return m_storage->target(pos); } CatalogString Catalog::sourceWithTags(const DocPosition& pos) const { if (Q_UNLIKELY( !m_storage )) return CatalogString(); return m_storage->sourceWithTags(alterForSinglePlural(this, pos)); } CatalogString Catalog::targetWithTags(const DocPosition& pos) const { if (Q_UNLIKELY( !m_storage )) return CatalogString(); return m_storage->targetWithTags(pos); } CatalogString Catalog::catalogString(const DocPosition& pos) const { if (Q_UNLIKELY( !m_storage )) return CatalogString(); return m_storage->catalogString(pos.part==DocPosition::Source?alterForSinglePlural(this, pos):pos); } QVector Catalog::notes(const DocPosition& pos) const { if (Q_UNLIKELY( !m_storage )) return QVector(); return m_storage->notes(pos); } QVector Catalog::developerNotes(const DocPosition& pos) const { if (Q_UNLIKELY( !m_storage )) return QVector(); return m_storage->developerNotes(pos); } Note Catalog::setNote(const DocPosition& pos, const Note& note) { if (Q_UNLIKELY( !m_storage )) return Note(); return m_storage->setNote(pos,note); } QStringList Catalog::noteAuthors() const { if (Q_UNLIKELY( !m_storage )) return QStringList(); return m_storage->noteAuthors(); } void Catalog::attachAltTransCatalog(Catalog* altCat) { d._altTransCatalogs.append(altCat); if (numberOfEntries()!=altCat->numberOfEntries()) qCWarning(LOKALIZE_LOG)<url()<<"has different number of entries"; } void Catalog::attachAltTrans(int entry, const AltTrans& trans) { d._altTranslations.insert(entry, trans); } QVector Catalog::altTrans(const DocPosition& pos) const { QVector result; if (m_storage) result=m_storage->altTrans(pos); foreach(Catalog* altCat, d._altTransCatalogs) { if (pos.entry>=altCat->numberOfEntries()) { qCDebug(LOKALIZE_LOG)<<"ignoring"<url()<<"this time because"<numberOfEntries(); continue; } if (altCat->source(pos)!=source(pos)) { qCDebug(LOKALIZE_LOG)<<"ignoring"<url()<<"this time because s don't match"; continue; } QString target=altCat->msgstr(pos); if (!target.isEmpty() && altCat->isApproved(pos)) { result<url(); } } if (d._altTranslations.contains(pos.entry)) result<sourceFiles(pos); } QString Catalog::id(const DocPosition& pos) const { if (Q_UNLIKELY( !m_storage )) return QString(); return m_storage->id(pos); } QStringList Catalog::context(const DocPosition& pos) const { if (Q_UNLIKELY( !m_storage )) return QStringList(); return m_storage->context(pos); } QString Catalog::setPhase(const DocPosition& pos, const QString& phase) { if (Q_UNLIKELY( !m_storage )) return QString(); return m_storage->setPhase(pos,phase); } void Catalog::setActivePhase(const QString& phase, ProjectLocal::PersonRole role) { //qCDebug(LOKALIZE_LOG)<<"setting active phase"<size(); while (pos.entryisEmpty(pos)) d._emptyIndex << pos.entry; ++(pos.entry); } emit signalNumberOfFuzziesChanged(); emit signalNumberOfEmptyChanged(); } QString Catalog::phase(const DocPosition& pos) const { if (Q_UNLIKELY( !m_storage )) return QString(); return m_storage->phase(pos); } Phase Catalog::phase(const QString& name) const { return m_storage->phase(name); } QList Catalog::allPhases() const { return m_storage->allPhases(); } QVector Catalog::phaseNotes(const QString& phase) const { return m_storage->phaseNotes(phase); } QVector Catalog::setPhaseNotes(const QString& phase, QVector notes) { return m_storage->setPhaseNotes(phase, notes); } QMap Catalog::allTools() const { return m_storage->allTools(); } bool Catalog::isPlural(uint index) const { return m_storage && m_storage->isPlural(DocPosition(index)); } bool Catalog::isApproved(uint index) const { if (Q_UNLIKELY( !m_storage )) return false; bool extendedStates=m_storage->capabilities()&ExtendedStates; return (extendedStates&&::isApproved(state(DocPosition(index)),activePhaseRole())) ||(!extendedStates&&m_storage->isApproved(DocPosition(index))); } TargetState Catalog::state(const DocPosition& pos) const { if (Q_UNLIKELY( !m_storage )) return NeedsTranslation; if (m_storage->capabilities()&ExtendedStates) return m_storage->state(pos); else return closestState(m_storage->isApproved(pos), activePhaseRole()); } bool Catalog::isEmpty(uint index) const { return m_storage && m_storage->isEmpty(DocPosition(index)); } bool Catalog::isEmpty(const DocPosition& pos) const { return m_storage && m_storage->isEmpty(pos); } bool Catalog::isEquivTrans(const DocPosition& pos) const { return m_storage && m_storage->isEquivTrans(pos); } int Catalog::binUnitsCount() const { return m_storage?m_storage->binUnitsCount():0; } int Catalog::unitById(const QString& id) const { return m_storage?m_storage->unitById(id):0; } QString Catalog::mimetype() { if (Q_UNLIKELY( !m_storage )) return QString(); return m_storage->mimetype(); } QString Catalog::fileType() { if (Q_UNLIKELY( !m_storage )) return QString(); return m_storage->fileType(); } CatalogType Catalog::type() { if (Q_UNLIKELY( !m_storage )) return Gettext; return m_storage->type(); } QString Catalog::sourceLangCode() const { if (Q_UNLIKELY( !m_storage )) return QString(); return m_storage->sourceLangCode(); } QString Catalog::targetLangCode() const { if (Q_UNLIKELY( !m_storage )) return QString(); return m_storage->targetLangCode(); } void Catalog::setTargetLangCode(const QString& targetLangCode) { if (Q_UNLIKELY( !m_storage )) return; bool notify = m_storage->targetLangCode()!=targetLangCode; m_storage->setTargetLangCode(targetLangCode); if (notify) emit signalFileLoaded(); } //END STORAGE TRANSLATION //BEGIN OPEN/SAVE #define DOESNTEXIST -1 #define ISNTREADABLE -2 #define UNKNOWNFORMAT -3 KAutoSaveFile* Catalog::checkAutoSave(const QString& url) { #ifndef NOKDE KAutoSaveFile* autoSave=0; QList staleFiles = KAutoSaveFile::staleFiles(QUrl::fromLocalFile(url)); foreach (KAutoSaveFile *stale, staleFiles) { if (stale->open(QIODevice::ReadOnly) && !autoSave) { autoSave=stale; autoSave->setParent(this); } else stale->deleteLater(); } if (autoSave) qCInfo(LOKALIZE_LOG)<<"autoSave"<fileName(); return autoSave; #else return 0; #endif } int Catalog::loadFromUrl(const QString& filePath, const QString& saidUrl, int* fileSize, bool fast) { QFileInfo info(filePath); if(Q_UNLIKELY( !info.exists() || info.isDir()) ) return DOESNTEXIST; if(Q_UNLIKELY( !info.isReadable() )) return ISNTREADABLE; bool readOnly=!info.isWritable(); QTime a;a.start(); QFile file(filePath); if (!file.open(QIODevice::ReadOnly)) return ISNTREADABLE;//TODO CatalogStorage* storage=0; if (filePath.endsWith(QLatin1String(".po"))||filePath.endsWith(QLatin1String(".pot"))) storage=new GettextCatalog::GettextStorage; else if (filePath.endsWith(QLatin1String(".xlf"))||filePath.endsWith(QLatin1String(".xliff"))) storage=new XliffStorage; else if (filePath.endsWith(QLatin1String(".ts"))) storage=new TsStorage; else { //try harder QTextStream in(&file); int i=0; bool gettext=false; while (!in.atEnd()&& ++i<64 && !gettext) gettext=in.readLine().contains(QLatin1String("msgid")); if (gettext) storage=new GettextCatalog::GettextStorage; else return UNKNOWNFORMAT; } int line=storage->load(&file); file.close(); if (Q_UNLIKELY(line!=0 || (!storage->size() && (line==-1) ) )) { delete storage; return line; } if (a.elapsed()>100) qCDebug(LOKALIZE_LOG)<numberOfPluralForms(); d._autoSaveDirty=true; d._readOnly=readOnly; d._filePath=saidUrl.isEmpty()?filePath:saidUrl; //set some sane role, a real phase with a nmae will be created later with the first edit command setActivePhase(QString(),Project::local()->role()); #ifndef NOKDE if (!fast) { KAutoSaveFile* autoSave=checkAutoSave(d._filePath); d._autoSaveRecovered=autoSave; if (autoSave) { d._autoSave->deleteLater(); d._autoSave=autoSave; //restore 'modified' status for entries MergeCatalog* mergeCatalog=new MergeCatalog(this,this); int errorLine=mergeCatalog->loadFromUrl(autoSave->fileName()); - if (KDE_ISLIKELY(errorLine==0)) + if (Q_LIKELY(errorLine==0)) mergeCatalog->copyToBaseCatalog(); mergeCatalog->deleteLater(); d._autoSave->close(); } else d._autoSave->setManagedFile(QUrl::fromLocalFile(d._filePath)); } #endif if (fileSize) *fileSize=file.size(); emit signalFileLoaded(); emit signalFileLoaded(d._filePath); return 0; } bool Catalog::save() { return saveToUrl(d._filePath); } //this function is not called if QUndoStack::isClean() ! bool Catalog::saveToUrl(QString localFilePath) { if (Q_UNLIKELY( !m_storage )) return true; bool nameChanged=localFilePath.length(); - if (KDE_ISLIKELY( !nameChanged )) + if (Q_LIKELY( !nameChanged )) localFilePath = d._filePath; QString localPath=QFileInfo(localFilePath).absolutePath(); if (!QFileInfo::exists(localPath)) if (!QDir::root().mkpath(localPath)) return false; QFile file(localFilePath); if (Q_UNLIKELY( !file.open(QIODevice::WriteOnly) )) //i18n("Wasn't able to open file %1",filename.ascii()); return false; bool belongsToProject=localFilePath.contains(Project::instance()->poDir()); if (Q_UNLIKELY( !m_storage->save(&file, belongsToProject) )) return false; file.close(); #ifndef NOKDE d._autoSave->remove(); d._autoSaveRecovered=false; #endif setClean(); //undo/redo if (nameChanged) { d._filePath=localFilePath; #ifndef NOKDE d._autoSave->setManagedFile(QUrl::fromLocalFile(localFilePath)); #endif } //Settings::self()->setCurrentGroup("Bookmarks"); //Settings::self()->addItemIntList(d._filePath.url(),d._bookmarkIndex); emit signalFileSaved(); emit signalFileSaved(localFilePath); return true; /* else if (status==NO_PERMISSIONS) { if (KMessageBox::warningContinueCancel(this, i18n("You do not have permission to write to file:\n%1\n" "Do you want to save to another file or cancel?", _currentURL.prettyUrl()), i18n("Error"),KStandardGuiItem::save())==KMessageBox::Continue) return fileSaveAs(); } */ } void Catalog::doAutoSave() { #ifndef NOKDE if (isClean()||!(d._autoSaveDirty)) return; if (Q_UNLIKELY( !m_storage )) return; if (!d._autoSave->open(QIODevice::WriteOnly)) { emit signalFileAutoSaveFailed(d._autoSave->fileName()); return; } qCInfo(LOKALIZE_LOG)<<"doAutoSave"<fileName(); m_storage->save(d._autoSave); d._autoSave->close(); d._autoSaveDirty=false; #endif } void Catalog::projectConfigChanged() { setActivePhase(activePhase(), Project::local()->role()); } QByteArray Catalog::contents() { QBuffer buf; buf.open(QIODevice::WriteOnly); m_storage->save(&buf); buf.close(); return buf.data(); } //END OPEN/SAVE /** * helper method to keep db in a good shape :) * called on * 1) entry switch * 2) automatic editing code like replace or undo/redo operation **/ static void updateDB( const QString& filePath, const QString& ctxt, const CatalogString& english, const CatalogString& newTarget, int form, bool approved, const QString& dbName //const DocPosition&,//for back tracking ) { TM::UpdateJob* j=new TM::UpdateJob(filePath,ctxt,english,newTarget,form,approved, dbName); TM::threadPool()->start(j); } //BEGIN UNDO/REDO const DocPosition& Catalog::undo() { QUndoStack::undo(); return d._lastModifiedPos; } const DocPosition& Catalog::redo() { QUndoStack::redo(); return d._lastModifiedPos; } void Catalog::flushUpdateDBBuffer() { if (!Settings::autoaddTM()) return; DocPosition pos=d._lastModifiedPos; if (pos.entry==-1 || pos.entry>=numberOfEntries()) { //nothing to flush //qCWarning(LOKALIZE_LOG)<<"nothing to flush or new file opened"; return; } QString dbName; if (Project::instance()->targetLangCode()==targetLangCode()) { dbName=Project::instance()->projectID(); } else { dbName=sourceLangCode()%'-'%targetLangCode(); qCInfo(LOKALIZE_LOG)<<"updating"<targetLangCode()<m_configurations.contains(dbName)) { TM::OpenDBJob* openDBJob=new TM::OpenDBJob(dbName, TM::Local, true); connect(openDBJob,SIGNAL(done(OpenDBJob*)),TM::DBFilesModel::instance(),SLOT(updateProjectTmIndex())); openDBJob->m_setParams=true; openDBJob->m_tmConfig.markup=Project::instance()->markup(); openDBJob->m_tmConfig.accel=Project::instance()->accel(); openDBJob->m_tmConfig.sourceLangCode=sourceLangCode(); openDBJob->m_tmConfig.targetLangCode=targetLangCode(); TM::DBFilesModel::instance()->openDB(openDBJob); } } int form=-1; if (isPlural(pos.entry)) form=pos.form; updateDB(url(), context(pos.entry).first(), sourceWithTags(pos), targetWithTags(pos), form, isApproved(pos.entry), dbName); d._lastModifiedPos=DocPosition(); } void Catalog::setLastModifiedPos(const DocPosition& pos) { if (pos.entry>=numberOfEntries()) //bin-units return; bool entryChanged=DocPos(d._lastModifiedPos)!=DocPos(pos); if (entryChanged) flushUpdateDBBuffer(); d._lastModifiedPos=pos; } bool CatalogPrivate::addToEmptyIndexIfAppropriate(CatalogStorage* storage, const DocPosition& pos, bool alreadyEmpty) { if ((!pos.offset)&&(storage->target(pos).isEmpty())&&(!alreadyEmpty)) { insertInList(_emptyIndex,pos.entry); return true; } return false; } void Catalog::targetDelete(const DocPosition& pos, int count) { if (Q_UNLIKELY( !m_storage )) return; bool alreadyEmpty = m_storage->isEmpty(pos); m_storage->targetDelete(pos,count); if (d.addToEmptyIndexIfAppropriate(m_storage,pos,alreadyEmpty)) emit signalNumberOfEmptyChanged(); emit signalEntryModified(pos); } bool CatalogPrivate::removeFromUntransIndexIfAppropriate(CatalogStorage* storage, const DocPosition& pos) { if ((!pos.offset)&&(storage->isEmpty(pos))) { _emptyIndex.removeAll(pos.entry); return true; } return false; } void Catalog::targetInsert(const DocPosition& pos, const QString& arg) { if (Q_UNLIKELY( !m_storage )) return; if (d.removeFromUntransIndexIfAppropriate(m_storage,pos)) emit signalNumberOfEmptyChanged(); m_storage->targetInsert(pos,arg); emit signalEntryModified(pos); } void Catalog::targetInsertTag(const DocPosition& pos, const InlineTag& tag) { if (Q_UNLIKELY( !m_storage )) return; if (d.removeFromUntransIndexIfAppropriate(m_storage,pos)) emit signalNumberOfEmptyChanged(); m_storage->targetInsertTag(pos,tag); emit signalEntryModified(pos); } InlineTag Catalog::targetDeleteTag(const DocPosition& pos) { if (Q_UNLIKELY( !m_storage )) return InlineTag(); bool alreadyEmpty = m_storage->isEmpty(pos); InlineTag tag=m_storage->targetDeleteTag(pos); if (d.addToEmptyIndexIfAppropriate(m_storage,pos,alreadyEmpty)) emit signalNumberOfEmptyChanged(); emit signalEntryModified(pos); return tag; } void Catalog::setTarget(DocPosition pos, const CatalogString& s) { //TODO for case of markup present m_storage->setTarget(pos, s.string); } TargetState Catalog::setState(const DocPosition& pos, TargetState state) { bool extendedStates = m_storage && m_storage->capabilities()&ExtendedStates; bool approved=::isApproved(state,activePhaseRole()); if (Q_UNLIKELY( !m_storage || (extendedStates && m_storage->state(pos)==state) || (!extendedStates && m_storage->isApproved(pos)==approved))) return this->state(pos); TargetState prevState; if (extendedStates) { prevState=m_storage->setState(pos,state); d._statesIndex[prevState].removeAll(pos.entry); insertInList(d._statesIndex[state],pos.entry); } else { prevState=closestState(!approved,activePhaseRole()); m_storage->setApproved(pos,approved); } if (!approved) insertInList(d._nonApprovedIndex,pos.entry); else d._nonApprovedIndex.removeAll(pos.entry); emit signalNumberOfFuzziesChanged(); emit signalEntryModified(pos); return prevState; } Phase Catalog::updatePhase(const Phase& phase) { return m_storage->updatePhase(phase); } void Catalog::setEquivTrans(const DocPosition& pos, bool equivTrans) { if (m_storage) m_storage->setEquivTrans(pos, equivTrans); } bool Catalog::setModified(DocPos entry, bool modified) { if (modified) { if (d._modifiedEntries.contains(entry)) return false; d._modifiedEntries.insert(entry); } else d._modifiedEntries.remove(entry); return true; } bool Catalog::isModified(DocPos entry) const { return d._modifiedEntries.contains(entry); } bool Catalog::isModified(int entry) const { if (!isPlural(entry)) return isModified(DocPos(entry,0)); int f=numberOfPluralForms(); while(--f>=0) if (isModified(DocPos(entry,f))) return true; return false; } //END UNDO/REDO int findNextInList(const QLinkedList& list, int index) { int nextIndex=-1; foreach(int key, list) { if (Q_UNLIKELY( key>index )) { nextIndex = key; break; } } return nextIndex; } int findPrevInList(const QLinkedList& list, int index) { int prevIndex=-1; foreach(int key, list) { if (Q_UNLIKELY( key>=index )) break; prevIndex = key; } return prevIndex; } void insertInList(QLinkedList& list, int index) { QLinkedList::Iterator it=list.begin(); while(it != list.end() && index > *it) ++it; list.insert(it,index); } void Catalog::setBookmark(uint idx, bool set) { if (set) insertInList(d._bookmarkIndex,idx); else d._bookmarkIndex.removeAll(idx); } bool isApproved(TargetState state, ProjectLocal::PersonRole role) { static const TargetState marginStates[]={Translated, Final, SignedOff}; return state>=marginStates[role]; } bool isApproved(TargetState state) { static const TargetState marginStates[]={Translated, Final, SignedOff}; return state==marginStates[0] || state==marginStates[1] || state==marginStates[2]; } TargetState closestState(bool approved, ProjectLocal::PersonRole role) { Q_ASSERT(role!=ProjectLocal::Undefined); static const TargetState approvementStates[][3]={ {NeedsTranslation, NeedsReviewTranslation, NeedsReviewTranslation}, {Translated, Final, SignedOff} }; return approvementStates[approved][role]; } bool Catalog::isObsolete(int entry) const { if (Q_UNLIKELY( !m_storage )) return false; return m_storage->isObsolete(entry); } QString Catalog::originalOdfFilePath() { if (Q_UNLIKELY( !m_storage )) return QString(); return m_storage->originalOdfFilePath(); } void Catalog::setOriginalOdfFilePath(const QString& odfFilePath) { if (Q_UNLIKELY( !m_storage )) return; m_storage->setOriginalOdfFilePath(odfFilePath); } diff --git a/src/catalog/gettext/catalogfileplugin.h b/src/catalog/gettext/catalogfileplugin.h index e0ac987..e26111b 100644 --- a/src/catalog/gettext/catalogfileplugin.h +++ b/src/catalog/gettext/catalogfileplugin.h @@ -1,150 +1,149 @@ /* **************************************************************************** This file is part of KAider This file contains parts of KBabel code Copyright (C) 2002-2003 by Stanislav Visnovsky 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. **************************************************************************** */ #ifndef KATALOGFILEPLUGIN_H #define KATALOGFILEPLUGIN_H -#include #include #include class QIODevice; class QString; class QStringList; namespace GettextCatalog { class GettextStorage; class CatalogItem; class CatalogImportPluginPrivate; class CatalogExportPluginPrivate; /** * Result of the conversion */ enum ConversionStatus { OK=0, NOT_IMPLEMENTED, NO_FILE, NO_PERMISSIONS, PARSE_ERROR, RECOVERED_PARSE_ERROR, OS_ERROR, NO_PLUGIN, UNSUPPORTED_TYPE, RECOVERED_HEADER_ERROR, ///< Header error that could be recovered @since 1.11.2 (KDE 3.5.2) STOPPED, BUSY, NO_ENTRY_ERROR ///< The loaded catalog has not any entry! @since 1.11.2 (KDE 3.5.2) }; /** * HISTORY: this was a base class for Catalog import plugins in KBabel, * but this architecture isn't not suitable for XML-based files * (when whole DOM-tree is stored in memory to prevent file clashes) * * This class is the base for import plugins for catalogs. * It provides "transactional behavior", so the changes are stored in * catalog only if the import process finishes successfully. * * To use it, just subclass and redefine load() and id() methods. * When importing, you can use the protected methods for setting * the catalog. New catalog items can be added using appendCatalogItem. * * @short Base class for GettextCatalog import plugins * @author Stanislav Visnovsky */ class CatalogImportPlugin { public: CatalogImportPlugin(); virtual ~CatalogImportPlugin(); /** * Load the file and fill the corresponding catalog. The file * is considered to be of @ref mimetype MIME type. * * @param file local file name to be opened * @param mimetype the MIME type is should be handled as * @param catalog the catalog to be filled * @return result of the operation */ ConversionStatus open(QIODevice*, GettextStorage* catalog, int* errorLine); /** * Reimplement this method to load the local file passed as an argument. * Throughout the run, you can use the protected methods for setting * the contents of the resulting catalog. * This method must call \see setMimeTypes to setup correct MIME types * for the loaded file. Also, it should use \see isStopped to * abort loading and the signals for providing user feedback. * @param file file to be loaded * @param mimetype the expected MIME type (the type used for plugin selection */ virtual ConversionStatus load(QIODevice*) = 0; protected: /** Append a new catalog item, either as normal or as an obsolete one * @param item the new item * @param obsolete flag that the item is obsolete */ void appendCatalogItem( const CatalogItem& item, const bool obsolete = false ); /** set flag that the file is generated from DocBook */ void setGeneratedFromDocbook(const bool fromDocbook); /** set the list of parse error indexes */ void setErrorIndex(const QList& errors); /** set extra data for the catalog, which can't be stored in * @ref CatalogItem. The format can be arbitrary */ void setCatalogExtraData( const QStringList& data ); /** set the header catalog item */ void setHeader( const CatalogItem& header ); /** Set the character encoding used in the catalog file. */ void setCodec( QTextCodec* codec ); /** start a new transaction. You should never call this method. */ void startTransaction(); /** commit the data in the current transaction. You should never call this method. */ void commitTransaction(); short _maxLineLength; short _trailingNewLines; int _errorLine; private: CatalogImportPluginPrivate* d; }; } #endif diff --git a/src/catalog/gettext/catalogitem.cpp b/src/catalog/gettext/catalogitem.cpp index 9c30258..5e32d8b 100644 --- a/src/catalog/gettext/catalogitem.cpp +++ b/src/catalog/gettext/catalogitem.cpp @@ -1,353 +1,352 @@ /* **************************************************************************** This file is based on the one from KBabel Copyright (C) 1999-2000 by Matthias Kiefer 2002 by Stanislav Visnovsky Copyright (C) 2006 by Nicolas GOUTTE 2007-2012 by Nick Shaforostoff 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 "catalogitem.h" #include "lokalize_debug.h" -#include #include using namespace GettextCatalog; QString CatalogItem::msgctxt(const bool noNewlines) const { QString msgctxt=d._msgctxt; if (noNewlines) return msgctxt.replace(QLatin1Char('\n'), QLatin1Char(' ')); //" " or "" ? else return msgctxt; } const QString& CatalogItem::msgstr(const int form) const { - if (KDE_ISLIKELY (form& CatalogItem::msgstrPlural() const { return d._msgstrPlural; } QStringList CatalogItem::allPluralForms(CatalogItem::Part part, bool stripNewLines) const { QStringList result=(part==CatalogItem::Source?d._msgidPlural:d._msgstrPlural).toList(); if (stripNewLines) { result.replaceInStrings(QStringLiteral("\n"), QString()); } return result; } void CatalogItem::setMsgctxt(const QString& msg) { d._msgctxt=msg; d._msgctxt.squeeze(); d._keepEmptyMsgCtxt=msg.isEmpty(); } void CatalogItem::setMsgid(const QString& msg, const int form) { if (form>=d._msgidPlural.size()) d._msgidPlural.resize(form+1); d._msgidPlural[form]=msg; } void CatalogItem::setMsgid(const QStringList& msg) { d._msgidPlural=msg.toVector(); //TODO for (QVector::iterator it=d._msgidPlural.begin();it!=d._msgidPlural.end();++it) it->squeeze(); } void CatalogItem::setMsgid(const QStringList& msg, bool prependEmptyLine) { d._prependMsgIdEmptyLine=prependEmptyLine; d._msgidPlural=msg.toVector(); //TODO for (QVector::iterator it=d._msgidPlural.begin();it!=d._msgidPlural.end();++it) it->squeeze(); } void CatalogItem::setMsgid(const QVector& msg) { d._msgidPlural=msg; for (QVector::iterator it=d._msgidPlural.begin();it!=d._msgidPlural.end();++it) it->squeeze(); } void CatalogItem::setMsgstr(const QString& msg, const int form) { if (form>=d._msgstrPlural.size()) d._msgstrPlural.resize(form+1); d._msgstrPlural[form]=msg; } void CatalogItem::setMsgstr(const QStringList& msg) { //TODO d._msgstrPlural=msg.toVector(); } void CatalogItem::setMsgstr(const QStringList& msg, bool prependEmptyLine) { d._prependMsgStrEmptyLine=prependEmptyLine; d._msgstrPlural=msg.toVector(); } void CatalogItem::setMsgstr(const QVector& msg) { d._msgstrPlural=msg; } void CatalogItem::setComment(const QString& com) { { //static QMutex reMutex; //QMutexLocker reLock(&reMutex); //avoid crash #281033 //now we have a bigger scale mutex in GettextStorage static QRegExp fuzzyRegExp(QStringLiteral("((?:^|\n)#(?:,[^,]*)*),\\s*fuzzy")); d._fuzzyCached=com.contains( fuzzyRegExp ); } d._comment=com; d._comment.squeeze(); } bool CatalogItem::isUntranslated() const { return d.isUntranslated(); } bool CatalogItem::isUntranslated(uint form) const { return d.isUntranslated(form); } #if 0 QStringList CatalogItem::errors() const { return d._errors; } bool CatalogItem::isCformat() const { // Allow "possible-c-format" (from xgettext --debug) or "c-format" // Note the regexp (?: ) is similar to () but it does not capture (so it is faster) return d._comment.indexOf( QRegExp(",\\s*(?:possible-)c-format") ) == -1; } bool CatalogItem::isNoCformat() const { return d._comment.indexOf( QRegExp(",\\s*no-c-format") ) == -1; } bool CatalogItem::isQtformat() const { return d._comment.indexOf( QRegExp(",\\s*qt-format") ) == -1; } bool CatalogItem::isNoQtformat() const { return d._comment.indexOf( QRegExp(",\\s*no-qt-format") ) == -1; } bool CatalogItem::isUntranslated() const { return d._msgstr.first().isEmpty(); } int CatalogItem::totalLines() const { int lines=0; if(!d._comment.isEmpty()) { lines = d._comment.count('\n')+1; } int msgctxtLines=0; if(!d._msgctxt.isEmpty()) { msgctxtLines=d._msgctxt.count('\n')+1; } int msgidLines=0; QStringList::ConstIterator it; for(it=d._msgid.begin(); it != d._msgid.end(); ++it) { msgidLines += (*it).count('\n')+1; } int msgstrLines=0; for(it=d._msgstr.begin(); it != d._msgstr.end(); ++it) { msgstrLines += (*it).count('\n')+1; } if(msgctxtLines>1) msgctxtLines++; if(msgidLines>1) msgidLines++; if(msgstrLines>1) msgstrLines++; lines+=( msgctxtLines+msgidLines+msgstrLines ); return lines; } void CatalogItem::setSyntaxError(bool on) { if(on && !d._errors.contains("syntax error")) d._errors.append("syntax error"); else d._errors.removeAll("syntax error"); } #endif QStringList CatalogItem::msgstrAsList() const { if (d._msgstrPlural.isEmpty()) { qCWarning(LOKALIZE_LOG)<<"This should never happen!"; return QStringList(); } QStringList list(d._msgstrPlural.first().split('\n', QString::SkipEmptyParts )); if(d._msgstrPlural.first()==QLatin1String("\n")) list.prepend(QString()); if(list.isEmpty()) list.append(QString()); return list; } void CatalogItem::setFuzzy() { d._fuzzyCached=true; if (d._comment.isEmpty()) { d._comment=QStringLiteral("#, fuzzy"); return; } int p=d._comment.indexOf(QLatin1String("#,")); if(p!=-1) { d._comment.replace(p,2,QStringLiteral("#, fuzzy,")); return; } QString comment=d._comment; static QRegExp a("\\#\\:[^\n]*\n"); p=a.indexIn(comment); if (p!=-1) { d._comment=comment.insert(p+a.matchedLength(),QLatin1String("#, fuzzy\n")); return; } p=d._comment.indexOf(QLatin1String("\n#|")); if (p!=-1) { d._comment.insert(p,QLatin1String("\n#, fuzzy")); return; } if (d._comment.startsWith(QLatin1String("#|"))) { d._comment.prepend(QLatin1String("#, fuzzy\n")); return; } if( !(d._comment.endsWith(QLatin1Char('\n'))) ) d._comment+=QLatin1Char('\n'); d._comment+=QLatin1String("#, fuzzy"); } void CatalogItem::unsetFuzzy() { d._fuzzyCached=false; static const QRegExp rmFuzzyRe(QStringLiteral(",\\s*fuzzy")); d._comment.remove( rmFuzzyRe ); // remove empty comment lines d._comment.remove( QRegExp(QStringLiteral("\n#\\s*$")) ); d._comment.remove( QRegExp(QStringLiteral("^#\\s*$")) ); d._comment.remove( QRegExp(QStringLiteral("#\\s*\n")) ); d._comment.remove( QRegExp(QStringLiteral("^#\\s*\n")) ); } #if 0 QString CatalogItem::nextError() const { return d._errors.first(); } void CatalogItem::clearErrors() { d._errors.clear(); } void CatalogItem::appendError(const QString& error ) { if( !d._errors.contains( error ) ) d._errors.append(error); } void CatalogItem::removeError(const QString& error ) { d._errors.removeAt( d._errors.indexOf( error ) ); } #endif // kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/src/catalog/gettextheader.cpp b/src/catalog/gettextheader.cpp index d958ecd..fdcb4ad 100644 --- a/src/catalog/gettextheader.cpp +++ b/src/catalog/gettextheader.cpp @@ -1,746 +1,745 @@ /* **************************************************************************** This file is part of Lokalize Copyright (C) 2008-2014 by Nick Shaforostoff 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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, see . **************************************************************************** */ #include "gettextheader.h" #include "lokalize_debug.h" #include "project.h" #include "version.h" #include "prefs_lokalize.h" #include "prefs.h" #include #include #include #include #include #include #include #include #include -#include #include /** * this data was obtained by running GNUPluralForms() * on all languages KDE knows of **/ struct langPInfo { const char *lang; const char *plural; }; static const langPInfo langsWithPInfo[] = { { "ar", "nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;" }, { "be@latin", "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" }, { "be", "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" }, { "br", "nplurals=1; plural=0;" }, { "bs", "nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;" }, { "csb", "nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)" }, { "cs", "nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;" }, { "da", "nplurals=2; plural=(n != 1);" }, { "de", "nplurals=2; plural=(n != 1);" }, { "el", "nplurals=2; plural=(n != 1);" }, { "en", "nplurals=2; plural=(n != 1);" }, { "en_GB", "nplurals=2; plural=(n != 1);" }, { "en_US", "nplurals=2; plural=(n != 1);" }, { "eo", "nplurals=2; plural=(n != 1);" }, { "es", "nplurals=2; plural=(n != 1);" }, { "et", "nplurals=2; plural=(n != 1);" }, { "fa", "nplurals=1; plural=0;" }, { "fi", "nplurals=2; plural=(n != 1);" }, { "fo", "nplurals=2; plural=(n != 1);" }, { "fr", "nplurals=2; plural=(n > 1);" }, { "ga", "nplurals=5; plural=n==1 ? 0 : n==2 ? 1 : n<7 ? 2 : n < 11 ? 3 : 4" }, { "gd", "nplurals=4; plural=(n==1 || n==11) ? 0 : (n==2 || n==12) ? 1 : (n > 2 && n < 20) ? 2 : 3;" }, { "gu", "nplurals=2; plural=(n!=1);" }, { "he", "nplurals=2; plural=(n != 1);" }, { "hi", "nplurals=2; plural=(n!=1);" }, { "hne", "nplurals=2; plural=(n!=1);" }, { "hr", "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" }, { "hsb", "nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3;" }, { "hu", "nplurals=2; plural=(n != 1);" }, { "hy", "nplurals=4; plural=n==1 ? 3 : n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;" }, { "id", "nplurals=1; plural=0;" }, { "it", "nplurals=2; plural=(n != 1);" }, { "ja", "nplurals=1; plural=0;" }, { "ka", "nplurals=1; plural=0;" }, { "kk", "nplurals=1; plural=0;" }, { "km", "nplurals=1; plural=0;" }, { "ko", "nplurals=1; plural=0;" }, { "lt", "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2);" }, { "lv", "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2);" }, { "mai", "nplurals=2; plural=(n!=1);" }, { "mk", "nplurals=3; plural=n%10==1 ? 0 : n%10==2 ? 1 : 2;" }, { "mr", "nplurals=2; plural=(n!=1);" }, { "ms", "nplurals=2; plural=1;" }, { "nb", "nplurals=2; plural=(n != 1);" }, { "nl", "nplurals=2; plural=(n != 1);" }, { "nn", "nplurals=2; plural=(n != 1);" }, { "oc", "nplurals=2; plural=(n > 1);" }, { "or", "nplurals=2; plural=(n!=1);" }, { "pl", "nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" }, { "pt", "nplurals=2; plural=(n != 1);" }, { "pt_BR", "nplurals=2; plural=(n > 1);" }, { "ro", "nplurals=3; plural=n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < 20)) ? 1 : 2;" }, { "ru", "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" }, { "sk", "nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;" }, { "sl", "nplurals=4; plural=(n%100==1 ? 1 : n%100==2 ? 2 : n%100==3 || n%100==4 ? 3 : 0);" }, { "sr", "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" }, { "sr@latin", "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" }, { "sv", "nplurals=2; plural=(n != 1);" }, { "te", "nplurals=5; plural=n==1 ? 0 : n==2 ? 1 : n<7 ? 2 : n<11 ? 3 : 4;" }, { "th", "nplurals=1; plural=0;" }, { "tr", "nplurals=2; plural=(n > 1);" }, { "ug", "nplurals=1; plural=0;" }, { "uk", "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" }, { "uz", "nplurals=1; plural=0;" }, { "uz@cyrillic", "nplurals=1; plural=0;" }, { "vi", "nplurals=1; plural=0;" }, { "zh_CN", "nplurals=1; plural=0;" }, { "zh_HK", "nplurals=1; plural=0;" }, { "zh_TW", "nplurals=1; plural=0;" } }; static const size_t langsWithPInfoCount = sizeof (langsWithPInfo) / sizeof (langsWithPInfo[0]); int numberOfPluralFormsFromHeader(const QString& header) { QRegExp rxplural(QStringLiteral("Plural-Forms:\\s*nplurals=(.);")); if (rxplural.indexIn(header) == -1) return 0; bool ok; int result=rxplural.cap(1).toShort(&ok); return ok?result:0; } int numberOfPluralFormsForLangCode(const QString& langCode) { QString expr=GNUPluralForms(langCode); QRegExp rxplural(QStringLiteral("nplurals=(.);")); if (rxplural.indexIn(expr) == -1) return 0; bool ok; int result=rxplural.cap(1).toShort(&ok); return ok?result:0; } QString GNUPluralForms(const QString& lang) { QByteArray l(lang.toUtf8()); int i=langsWithPInfoCount; while(--i>=0 && l!=langsWithPInfo[i].lang) ; - if (KDE_ISLIKELY( i>=0 )) + if (Q_LIKELY( i>=0 )) return QString::fromLatin1(langsWithPInfo[i].plural); i=langsWithPInfoCount; while(--i>=0 && !l.startsWith(langsWithPInfo[i].lang)) ; - if (KDE_ISLIKELY( i>=0 )) + if (Q_LIKELY( i>=0 )) return QString::fromLatin1(langsWithPInfo[i].plural); //BEGIN alternative // NOTE does this work under M$ OS? qCWarning(LOKALIZE_LOG)<<"gonna call msginit"; QString def=QStringLiteral("nplurals=2; plural=n != 1;"); QStringList arguments; arguments << QLatin1String("-l") << lang << QLatin1String("-i") << QLatin1String("-") << QLatin1String("-o") << QLatin1String("-") << QLatin1String("--no-translator") << QLatin1String("--no-wrap"); QProcess msginit; msginit.start(QLatin1String("msginit"), arguments); msginit.waitForStarted(5000); if (Q_UNLIKELY( msginit.state()!=QProcess::Running )) { //qCWarning(LOKALIZE_LOG)<<"msginit error"; return def; } msginit.write( "# SOME DESCRIPTIVE TITLE.\n" "# Copyright (C) YEAR Free Software Foundation, Inc.\n" "# FIRST AUTHOR , YEAR.\n" "#\n" "#, fuzzy\n" "msgid \"\"\n" "msgstr \"\"\n" "\"Project-Id-Version: PACKAGE VERSION\\n\"\n" "\"POT-Creation-Date: 2002-06-25 03:23+0200\\n\"\n" "\"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n\"\n" "\"Last-Translator: FULL NAME \\n\"\n" "\"Language-Team: LANGUAGE \\n\"\n" "\"Language: LL\\n\"\n" "\"MIME-Version: 1.0\\n\"\n" "\"Content-Type: text/plain; charset=UTF-8\\n\"\n" "\"Content-Transfer-Encoding: ENCODING\\n\"\n" // "\"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\\n\"\n" ); msginit.closeWriteChannel(); if (Q_UNLIKELY( !msginit.waitForFinished(5000) )) { qCWarning(LOKALIZE_LOG)<<"msginit error"; return def; } QByteArray result = msginit.readAll(); int pos = result.indexOf("Plural-Forms: "); if (Q_UNLIKELY( pos==-1 )) { //qCWarning(LOKALIZE_LOG)<<"msginit error"<'); temp=QStringLiteral("Last-Translator: ") % authorNameEmail % BACKSLASH_N; QRegExp lt(QStringLiteral("^ *Last-Translator:.*")); for ( it = headerList.begin(),found=false; it != headerList.end() && !found; ++it ) { if (it->contains(lt)) { if (forSaving) *it = temp; found=true; } } if (Q_UNLIKELY( !found )) headerList.append(temp); QLocale cLocale(QLocale::C); QString dateTimeString = cLocale.toString(QDateTime::currentDateTime(), QStringLiteral("yyyy-MM-dd hh:mm")); QString zoneOffsetString1 = QTimeZone(QTimeZone::systemTimeZoneId()).displayName(QTimeZone::GenericTime, QTimeZone::OffsetName); int zpos=qMax(qMax(0, zoneOffsetString1.indexOf('+')), zoneOffsetString1.indexOf('-')); QString zoneOffsetString = QString::fromRawData(zoneOffsetString1.unicode()+zpos, zoneOffsetString1.length()-zpos); temp=QStringLiteral("PO-Revision-Date: ") % dateTimeString % zoneOffsetString.remove(':') % BACKSLASH_N; QRegExp poRevDate(QStringLiteral("^ *PO-Revision-Date:.*")); for ( it = headerList.begin(),found=false; it != headerList.end() && !found; ++it ) { found=it->contains(poRevDate); if (found && forSaving) *it = temp; } if (Q_UNLIKELY( !found )) headerList.append(temp); temp=QStringLiteral("Project-Id-Version: ") % CatalogProjectId % BACKSLASH_N; //temp.replace( "@PACKAGE@", packageName()); QRegExp projectIdVer(QStringLiteral("^ *Project-Id-Version:.*")); for ( it = headerList.begin(),found=false; it != headerList.end() && !found; ++it ) { found=it->contains(projectIdVer); if (found && it->contains(QLatin1String("PACKAGE VERSION"))) *it = temp; } if (Q_UNLIKELY( !found )) headerList.append(temp); langCode=Project::instance()->isLoaded()? Project::instance()->langCode(): Settings::defaultLangCode(); QString language; //initialized with preexisting value or later QString mailingList; //initialized with preexisting value or later static QMap langEnums; if (!langEnums.size()) for (int l=QLocale::Abkhazian; l<=QLocale::Akoose; ++l) langEnums[QLocale::languageToString((QLocale::Language)l)]=(QLocale::Language)l; static QRegExp langTeamRegExp(QStringLiteral("^ *Language-Team:.*")); for ( it = headerList.begin(),found=false; it != headerList.end() && !found; ++it ) { found=it->contains(langTeamRegExp); if (found) { //really parse header QRegExp re(QStringLiteral("^ *Language-Team: *(.*) *<([^>]*)>")); if (re.indexIn(*it) != -1 ) { if (langEnums.contains( re.cap(1).trimmed() )) { language=re.cap(1).trimmed(); mailingList=re.cap(2).trimmed(); QList locales = QLocale::matchingLocales(langEnums.value(language), QLocale::AnyScript, QLocale::AnyCountry); if (locales.size()) langCode=locales.first().name().left(2); } } ait=it; } } if (language.isEmpty()) { language=QLocale::languageToString(QLocale(langCode).language()); if (language.isEmpty()) language=langCode; } if (mailingList.isEmpty() || belongsToProject) { if (Project::instance()->isLoaded()) mailingList=Project::instance()->mailingList(); else //if (mailingList.isEmpty()) mailingList=Settings::defaultMailingList(); } temp=QStringLiteral("Language-Team: ")%language%QStringLiteral(" <")%mailingList%QStringLiteral(">\\n"); - if (KDE_ISLIKELY( found )) + if (Q_LIKELY( found )) (*ait) = temp; else headerList.append(temp); static QRegExp langCodeRegExp(QStringLiteral("^ *Language: *([^ \\\\]*)")); temp=QStringLiteral("Language: ") % langCode % BACKSLASH_N; for ( it = headerList.begin(),found=false; it != headerList.end() && !found; ++it ) { found=(langCodeRegExp.indexIn(*it)!=-1); if (found && langCodeRegExp.cap(1).isEmpty()) *it=temp; //if (found) qCWarning(LOKALIZE_LOG)<<"got explicit lang code:"<contains(ctRe); if (found) *it=temp; } if (Q_UNLIKELY( !found )) headerList.append(temp); temp=QStringLiteral("Content-Transfer-Encoding: 8bit\\n"); QRegExp cteRe(QStringLiteral("^ *Content-Transfer-Encoding:.*")); for ( it = headerList.begin(),found=false; it != headerList.end() && !found; ++it ) found=it->contains(cteRe); if (!found) headerList.append(temp); // ensure MIME-Version header temp=QStringLiteral("MIME-Version: 1.0\\n"); QRegExp mvRe(QStringLiteral("^ *MIME-Version:")); for ( it = headerList.begin(),found=false; it != headerList.end()&& !found; ++it ) { found=it->contains(mvRe); if (found) *it = temp; } if (Q_UNLIKELY( !found )) headerList.append(temp); //qCDebug(LOKALIZE_LOG)<<"testing for GNUPluralForms"; // update plural form header QRegExp pfRe(QStringLiteral("^ *Plural-Forms:")); for ( it = headerList.begin(),found=false; it != headerList.end()&& !found; ++it ) found=it->contains(pfRe); if (found) { --it; //qCDebug(LOKALIZE_LOG)<<"GNUPluralForms found"; int num=numberOfPluralFormsFromHeader(header); if (!num) { if (generatedFromDocbook) num=1; else { qCWarning(LOKALIZE_LOG)<<"No plural form info in header, using project-defined one"<replace(pf,temp); num=numberOfPluralFormsFromHeader(temp); } else { qCWarning(LOKALIZE_LOG)<<"no... smth went wrong :(\ncheck your gettext install"; num=2; } } } numberOfPluralForms=num; } else if ( !generatedFromDocbook) { //qCDebug(LOKALIZE_LOG)<<"generating GNUPluralForms"<contains(xgRe); if (found) *it = temp; } if (Q_UNLIKELY( !found )) headerList.append(temp); //m_header.setMsgstr( headerList.join( "\n" ) ); header=headerList.join(QStringLiteral("\n")); //END header itself //BEGIN comment = description, copyrights // U+00A9 is the Copyright sign QRegExp fsfc(QStringLiteral("^# *Copyright (\\(C\\)|\\x00a9).*Free Software Foundation, Inc")); for ( it = commentList.begin(),found=false; it != commentList.end()&&!found; ++it ) { found=it->contains( fsfc ) ; if (found) it->replace(QStringLiteral("YEAR"), cLocale.toString(QDate::currentDate(), QStringLiteral("yyyy"))); } /* if( saveOptions.FSFCopyright == ProjectSettingsBase::Update ) { //update years QString cy = cLocale.toString(QDate::currentDate(), "yyyy"); if( !it->contains( QRegExp(cy)) ) // is the year already included? { int index = it->lastIndexOf( 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 0 if ( ( !usePrefs || saveOptions.updateDescription ) && ( !saveOptions.descriptionString.isEmpty() ) ) { temp = "# "+saveOptions.descriptionString; temp.replace( "@PACKAGE@", packageName()); temp.replace( "@LANGUAGE@", identityOptions.languageName); temp = temp.trimmed(); // The description strings has often buggy variants already in the file, these must be removed QString regexpstr = "^#\\s+" + QRegExp::escape( saveOptions.descriptionString.trimmed() ) + "\\s*$"; regexpstr.replace( "@PACKAGE@", ".*" ); regexpstr.replace( "@LANGUAGE@", ".*" ); //qCDebug(LOKALIZE_LOG) << "REGEXPSTR: " << regexpstr; 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 .* to .*\\s*$" ); qCDebug(LOKALIZE_LOG) << "Temp is '" << temp << "'"; found=false; bool foundTemplate=false; it = commentList.begin(); while ( it != commentList.end() ) { qCDebug(LOKALIZE_LOG) << "testing '" << (*it) << "'"; bool deleteItem = false; if ( (*it) == temp ) { qCDebug(LOKALIZE_LOG) << "Match "; if ( found ) deleteItem = true; else found=true; } else if ( regexp.indexIn( *it ) >= 0 ) { // We have a similar (translated) string (from another project or another language (perhaps typos)). Remove it. deleteItem = true; } else if ( regexpUntranslated.indexIn( *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.erase( it ); else ++it; } if (!found) commentList.prepend(temp); } #endif // qCDebug(LOKALIZE_LOG) << "HEADER COMMENT: " << commentList; /* if ( (!usePrefs || saveOptions.updateTranslatorCopyright) && ( ! identityOptions->readEntry("authorName","").isEmpty() ) && ( ! identityOptions->readEntry("Email","").isEmpty() ) ) // An email address can be used as ersatz of a name {*/ // return; QStringList foundAuthors; temp=QStringLiteral("# ")%authorNameEmail%QStringLiteral(", ")%cLocale.toString(QDate::currentDate(), QStringLiteral("yyyy"))%'.'; // ### TODO: it would be nice if the entry could start with "COPYRIGHT" and have the "(C)" symbol (both not mandatory) QRegExp regexpAuthorYear( QStringLiteral("^#.*(<.+@.+>)?,\\s*([\\d]+[\\d\\-, ]*|YEAR)") ); QRegExp regexpYearAlone( QStringLiteral("^# , \\d{4}.?\\s*$") ); if (commentList.isEmpty()) { commentList.append(temp); commentList.append(QString()); } else { it = commentList.begin(); while ( it != commentList.end() ) { bool deleteItem = false; if ( it->indexOf( QLatin1String("copyright"), 0, Qt::CaseInsensitive ) != -1 ) { // We have a line with a copyright. It should not be moved. } else if ( it->contains( QRegExp(QStringLiteral("#, *fuzzy")) ) ) deleteItem = true; else if ( it->contains( regexpYearAlone ) ) { // We have found a year number that is preceded 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->contains( QLatin1String("# FIRST AUTHOR , YEAR.")) ) deleteItem = true; else if ( it->contains( QLatin1String("# SOME DESCRIPTIVE TITLE"))) deleteItem = true; else if ( it->contains( regexpAuthorYear ) ) // email address followed by year { if ( !foundAuthors.contains( (*it) ) ) { // 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.erase( it ); else ++it; } if ( !foundAuthors.isEmpty() ) { found = false; bool foundAuthor = false; const QString cy = cLocale.toString(QDate::currentDate(), QStringLiteral("yyyy")); ait = foundAuthors.end(); for ( it = foundAuthors.begin() ; it!=foundAuthors.end(); ++it ) { if ( it->contains(Settings::authorName()) || it->contains(Settings::authorEmail()) ) { foundAuthor = true; if ( it->contains( cy ) ) found = true; else ait = it; } } if ( !found ) { if ( !foundAuthor ) foundAuthors.append(temp); else if ( ait != foundAuthors.end() ) { //update years const int index = (*ait).lastIndexOf( QRegExp(QStringLiteral("[\\d]+[\\d\\-, ]*")) ); if ( index == -1 ) (*ait)+=QStringLiteral(", ")%cy; else ait->insert(index+1, QStringLiteral(", ")%cy); } else qCDebug(LOKALIZE_LOG) << "INTERNAL ERROR: author found but iterator dangling!"; } } else foundAuthors.append(temp); foreach (QString author, foundAuthors) { // ensure dot at the end of copyright if ( !author.endsWith(QLatin1Char('.')) ) author += QLatin1Char('.'); commentList.append(author); } } //m_header.setComment( commentList.join( "\n" ) ); comment=commentList.join(QStringLiteral("\n")); //END comment = description, copyrights } QString fullUserName();// defined in helpers.cpp bool askAuthorInfoIfEmpty() { if(QThread::currentThread() == qApp->thread()) { if (Settings::authorName().isEmpty()) { bool ok; QString contact = QInputDialog::getText( SettingsController::instance()->mainWindowPtr(), i18nc("@window:title", "Author name missing"), i18n("Your name:"), QLineEdit::Normal, fullUserName(), &ok); #ifndef NOKDE Settings::self()->authorNameItem()->setValue(ok?contact:fullUserName()); Settings::self()->save(); #else Settings::self()->setAuthorName(ok?contact:fullUserName()); #endif } if (Settings::authorEmail().isEmpty()) { bool ok; QString email = QInputDialog::getText( SettingsController::instance()->mainWindowPtr(), i18nc("@window:title", "Author email missing"), i18n("Your email:"), QLineEdit::Normal, QString(), &ok); if (ok) { #ifndef NOKDE Settings::self()->authorEmailItem()->setValue(email); Settings::self()->save(); #else Settings::self()->setAuthorEmail(email); #endif } } } return !Settings::authorName().isEmpty() && !Settings::authorEmail().isEmpty(); } diff --git a/src/catalog/phase.cpp b/src/catalog/phase.cpp index ed0a6cb..6f5decf 100644 --- a/src/catalog/phase.cpp +++ b/src/catalog/phase.cpp @@ -1,100 +1,98 @@ /* **************************************************************************** This file is part of Lokalize Copyright (C) 2009 by Nick Shaforostoff 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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, see . **************************************************************************** */ #include "phase.h" #include "cmd.h" #include "catalog.h" #include "project.h" #include "prefs_lokalize.h" #include "gettextheader.h" #include -#include "kdemacros.h" - #include const char* const* processes() { static const char* const processes[]={"translation","review","approval"}; return processes; } //guess role ProjectLocal::PersonRole roleForProcess(const QString& process) { int i=ProjectLocal::Undefined; while (i>=0 && !process.startsWith(processes()[--i])) ; return (i==-1)?Project::local()->role():ProjectLocal::PersonRole(i); } void generatePhaseForCatalogIfNeeded(Catalog* catalog) { - if (KDE_ISLIKELY( !(catalog->capabilities()&Phases) || catalog->activePhaseRole()==ProjectLocal::Undefined )) + if (Q_LIKELY( !(catalog->capabilities()&Phases) || catalog->activePhaseRole()==ProjectLocal::Undefined )) return; Phase phase; phase.process=processes()[Project::local()->role()]; if (initPhaseForCatalog(catalog, phase)) static_cast(catalog)->push(new UpdatePhaseCmd(catalog, phase)); catalog->setActivePhase(phase.name, roleForProcess(phase.process)); } bool initPhaseForCatalog(Catalog* catalog, Phase& phase, int options) { askAuthorInfoIfEmpty(); phase.contact=Settings::authorName(); QSet names; QList phases=catalog->allPhases(); qSort(phases.begin(), phases.end(), qGreater()); foreach (const Phase& p, phases) { if (!(options&ForceAdd) && p.contact==phase.contact && p.process==phase.process) { phase=p; break; } names.insert(p.name); } if (phase.name.isEmpty()) { int i=0; while (names.contains(phase.name=phase.process+QStringLiteral("-%1").arg(++i))) ; phase.date=QDate::currentDate(); phase.email=Settings::authorEmail(); return true; } return false; } Phase::Phase() : date(QDate::currentDate()) , tool(QStringLiteral("lokalize-" LOKALIZE_VERSION)) {} diff --git a/src/catalog/pos.cpp b/src/catalog/pos.cpp index 7deadc6..8f77500 100644 --- a/src/catalog/pos.cpp +++ b/src/catalog/pos.cpp @@ -1,166 +1,165 @@ /* **************************************************************************** This file is part of Lokalize Copyright (C) 2007 by Nick Shaforostoff 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 "pos.h" #include "catalog.h" -#include "kdemacros.h" bool switchPrev(Catalog*& catalog,DocPosition& pos,int parts) { bool switchEntry=false; bool switchCommentIndex=false; if (pos.part==DocPosition::Comment) switchCommentIndex=true; else if (pos.part==DocPosition::Target) { if (parts&DocPosition::Source) pos.part=DocPosition::Source; switchEntry=!(parts&DocPosition::Source); } else if (pos.part==DocPosition::Source) switchEntry=true; bool skipCommentThisTime=false; if (switchCommentIndex) { if (pos.form) pos.form--; switchEntry=pos.form; //pos.form is zero again skipCommentThisTime=pos.form; } if (!switchEntry) return true; - if (KDE_ISUNLIKELY( pos.form>0 + if (Q_UNLIKELY( pos.form>0 && catalog->isPlural(pos.entry))) pos.form--; - else if (KDE_ISUNLIKELY( pos.entry==0 )) + else if (Q_UNLIKELY( pos.entry==0 )) return false; else { pos.entry--; pos.form=catalog->isPlural(pos.entry)*(catalog->numberOfPluralForms()-1); } pos.offset=0; if (parts&DocPosition::Comment && !skipCommentThisTime && pos.form==0 && catalog->notes(pos).size()) { pos.part=DocPosition::Comment; pos.form=catalog->notes(pos).size()-1; } else pos.part=DocPosition::Target; return true; } bool switchNext(Catalog*& catalog,DocPosition& pos,int parts) { bool switchEntry=false; bool switchCommentIndex=false; if (pos.part==DocPosition::Source) pos.part=DocPosition::Target; else if (pos.part==DocPosition::Target) { if (parts&DocPosition::Comment && pos.form==0 && catalog->notes(pos).size()) pos.part=DocPosition::Comment; else switchEntry=true; } else if (pos.part==DocPosition::Comment) switchCommentIndex=true; if (switchCommentIndex) { pos.form++; if (catalog->notes(pos).size()==pos.form) { pos.form=0; switchEntry=true; } } if (!switchEntry) return true; - if (KDE_ISUNLIKELY( pos.entry!=-1 + if (Q_UNLIKELY( pos.entry!=-1 && pos.form+1 < catalog->numberOfPluralForms() && catalog->isPlural(pos.entry))) pos.form++; - else if (KDE_ISUNLIKELY( pos.entry==catalog->numberOfEntries()-1 )) + else if (Q_UNLIKELY( pos.entry==catalog->numberOfEntries()-1 )) return false; else { pos.entry++; pos.form=0; } pos.offset=0; pos.part=(parts&DocPosition::Source)?DocPosition::Source:DocPosition::Target; return true; } #ifndef NOKDE #include const QDBusArgument &operator>>(const QDBusArgument &argument, DocPosition& pos) { int entry; int form; uint offset; argument.beginStructure(); argument >> entry >> form >> offset; argument.endStructure(); pos.entry=entry; pos.form=form; pos.offset=offset; return argument; } QDBusArgument &operator<<(QDBusArgument &argument, const DocPosition &pos) { int entry=pos.entry; int form=pos.form; uint offset=pos.offset; argument.beginStructure(); argument << entry << form << offset; argument.endStructure(); return argument; } #endif diff --git a/src/catalog/xliff/xliffstorage.cpp b/src/catalog/xliff/xliffstorage.cpp index a8e02fa..b03d1ab 100644 --- a/src/catalog/xliff/xliffstorage.cpp +++ b/src/catalog/xliff/xliffstorage.cpp @@ -1,1068 +1,1068 @@ /* Copyright 2008-2009 Nick Shaforostoff 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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, see . */ #include "xliffstorage.h" #include "lokalize_debug.h" #include "gettextheader.h" #include "project.h" #include "version.h" #include "prefs_lokalize.h" #include #include #include #include #include #include #include #include #include #ifdef Q_OS_WIN #define U QLatin1String #else #define U QStringLiteral #endif static const QString noyes[]={U("no"),U("yes")}; static const QString bintargettarget[]={U("bin-target"),U("target")}; static const QString binsourcesource[]={U("bin-source"),U("source")}; static const QString NOTE=U("note"); XliffStorage::XliffStorage() : CatalogStorage() { } XliffStorage::~XliffStorage() { } int XliffStorage::capabilities() const { return KeepsNoteAuthors|MultipleNotes|Phases|ExtendedStates|Tags; } //BEGIN OPEN/SAVE int XliffStorage::load(QIODevice* device) { QTime chrono;chrono.start(); QXmlSimpleReader reader; reader.setFeature(QStringLiteral("http://qt-project.org/xml/features/report-whitespace-only-CharData"),true); reader.setFeature(QStringLiteral("http://xml.org/sax/features/namespaces"),false); QXmlInputSource source(device); QString errorMsg; int errorLine;//+errorColumn; bool success=m_doc.setContent(&source, &reader, &errorMsg, &errorLine/*,errorColumn*/); QString FILE=QStringLiteral("file"); if (!success || m_doc.elementsByTagName(FILE).isEmpty()) { qCWarning(LOKALIZE_LOG)<0 && (++i) msgstr(item.msgstrPlural()); // while (msgstr.count() tags; QString stringToInsert; int pos; int lengthOfStringToRemove; ActionType actionType; ///Get ContentEditingData(ActionType type=Get) : pos(-1) , lengthOfStringToRemove(-1) , actionType(type) {} ///DeleteText ContentEditingData(int p, int l) : pos(p) , lengthOfStringToRemove(l) , actionType(DeleteText) {} ///InsertText ContentEditingData(int p,const QString& s) : stringToInsert(s) , pos(p) , lengthOfStringToRemove(-1) , actionType(InsertText) {} ///InsertTag ContentEditingData(int p,const InlineTag& range) : pos(p) , lengthOfStringToRemove(-1) , actionType(InsertTag) { tags.append(range); } ///DeleteTag ContentEditingData(int p) : pos(p) , lengthOfStringToRemove(-1) , actionType(DeleteTag) {} }; static QString doContent(QDomElement elem, int startingPos, ContentEditingData* data); /** * walks through XLIFF XML and performs actions depending on ContentEditingData: * - reads content * - deletes content, or * - inserts content */ static QString content(QDomElement elem, ContentEditingData* data=0) { return doContent(elem, 0, data); } static QString doContent(QDomElement elem, int startingPos, ContentEditingData* data) { //actually startingPos is current pos QString result; if (elem.isNull() || (!result.isEmpty() && data && data->actionType==ContentEditingData::CheckLength)) return QString(); bool seenCharacterDataAfterElement=false; QDomNode n = elem.firstChild(); while (!n.isNull()) { if (n.isCharacterData()) { seenCharacterDataAfterElement=true; QDomCharacterData c=n.toCharacterData(); QString cData=c.data(); if (data && data->pos!=-1 && data->pos>=startingPos && data->pos<=startingPos+cData.size()) { // time to do some action! ;) int localStartPos=data->pos-startingPos; //BEGIN DELETE TEXT if (data->actionType==ContentEditingData::DeleteText) //(data->lengthOfStringToRemove!=-1) { if (localStartPos+data->lengthOfStringToRemove>cData.size()) { //text is fragmented into several QDomCharacterData int localDelLen=cData.size()-localStartPos; //qCWarning(LOKALIZE_LOG)<<"text is fragmented into several QDomCharacterData. localDelLen:"<lengthOfStringToRemove=data->lengthOfStringToRemove-localDelLen; //data->pos=startingPos; //qCWarning(LOKALIZE_LOG)<<"\tsetup:"<pos<lengthOfStringToRemove; } else { //qCWarning(LOKALIZE_LOG)<<"simple delete"<lengthOfStringToRemove; c.deleteData(localStartPos,data->lengthOfStringToRemove); data->actionType=ContentEditingData::CheckLength; return QString('a');//so it exits 100% } } //END DELETE TEXT //INSERT else if (data->actionType==ContentEditingData::InsertText) { c.insertData(localStartPos,data->stringToInsert); data->actionType=ContentEditingData::CheckLength; return QString('a');//so it exits 100% } //BEGIN INSERT TAG else if (data->actionType==ContentEditingData::InsertTag) { const InlineTag& tag=data->tags.first(); QString mid=cData.mid(localStartPos); qCDebug(LOKALIZE_LOG)<<"inserting tag"<pos<(tag.start+1)) { //qCWarning(LOKALIZE_LOG)<<"isPaired"; int len=tag.end-tag.start-1;//-image symbol int localLen=qMin(len,mid.size()); if (localLen)//appending text { //qCWarning(LOKALIZE_LOG)<<"localLen. appending"<missingLen (or siblings end) int childrenCumulativeLen=0; QDomNode sibling=newNode.nextSibling(); while(!sibling.isNull())//&&(childrenCumulativeLenmissingLen) { if (tmp.isCharacterData()) { //divide the last string const QString& endData=tmp.toCharacterData().data(); QString last=endData.left(endData.size()-(childrenCumulativeLen-missingLen)); newNode.appendChild( elem.ownerDocument().createTextNode(last)); tmp.toCharacterData().deleteData(0,last.size()); //qCWarning(LOKALIZE_LOG)<<"end of add"<actionType=ContentEditingData::CheckLength; return QStringLiteral("a");//we're done here } //END INSERT TAG cData=c.data(); } //else // if (data&&data->pos!=-1/*&& n.nextSibling().isNull()*/) // qCWarning(LOKALIZE_LOG)<<"arg!"<pos"<pos; result += cData; startingPos+=cData.size(); } else if (n.isElement()) { QDomElement el=n.toElement(); //BEGIN DELETE TAG if (data&&data->actionType==ContentEditingData::DeleteTag &&data->pos==startingPos) { //qCWarning(LOKALIZE_LOG)<<"start deleting tag"; data->tags.append(InlineTag(startingPos, -1, InlineTag::getElementType(el.tagName().toUtf8()), el.attribute("id"), el.attribute("xid"))); if (data->tags.first().isPaired()) { //get end position ContentEditingData subData(ContentEditingData::Get); QString subContent=doContent(el,startingPos,&subData); data->tags[0].end=1+startingPos+subContent.size();//tagsymbol+text //qCWarning(LOKALIZE_LOG)<<"get end position"<actionType=ContentEditingData::CheckLength; return QStringLiteral("a");//we're done here } //END DELETE TAG if (!seenCharacterDataAfterElement) //add empty charData child so that user could add some text elem.insertBefore( elem.ownerDocument().createTextNode(QString()),n); seenCharacterDataAfterElement=false; if (data) {result += QChar(TAGRANGE_IMAGE_SYMBOL); ++startingPos;} int oldStartingPos=startingPos; //detect type of the tag InlineTag::InlineElement i=InlineTag::getElementType(el.tagName().toUtf8()); //1 or 2 images to represent it? //2 = there may be content inside if (InlineTag::isPaired(i)) { QString recursiveContent=doContent(el,startingPos,data); if (!recursiveContent.isEmpty()) {result += recursiveContent; startingPos+=recursiveContent.size();} if (data) {result += QChar(TAGRANGE_IMAGE_SYMBOL); ++startingPos;} } if (data&&data->actionType==ContentEditingData::Get) { QString id=el.attribute(QStringLiteral("id")); if (i==InlineTag::mrk)//TODO attr map id=el.attribute(QStringLiteral("mtype")); //qCWarning(LOKALIZE_LOG)<<"tagName"<tags.append(InlineTag(oldStartingPos-1,startingPos-1,i,id,el.attribute(QStringLiteral("xid")))); } } n = n.nextSibling(); } if (!seenCharacterDataAfterElement) { //add empty charData child so that user could add some text elem.appendChild( elem.ownerDocument().createTextNode(QString())); } return result; } //flat-model interface (ignores XLIFF grouping) CatalogString XliffStorage::catalogString(QDomElement unit, DocPosition::Part part) const { static const QString names[]={U("source"),U("target"), U("seg-source")}; CatalogString catalogString; ContentEditingData data(ContentEditingData::Get); int nameIndex=part==DocPosition::Target; if (nameIndex==0 && !unit.firstChildElement(names[2]).isNull()) nameIndex=2; catalogString.string=content(unit.firstChildElement( names[nameIndex]), &data ); catalogString.tags=data.tags; return catalogString; } CatalogString XliffStorage::catalogString(const DocPosition& pos) const { return catalogString(unitForPos(pos.entry), pos.part); } CatalogString XliffStorage::targetWithTags(DocPosition pos) const { return catalogString(unitForPos(pos.entry), DocPosition::Target); } CatalogString XliffStorage::sourceWithTags(DocPosition pos) const { return catalogString(unitForPos(pos.entry), DocPosition::Source); } static QString genericContent(QDomElement elem, bool nonbin) { return nonbin?content(elem):elem.firstChildElement(QStringLiteral("external-file")).attribute(QStringLiteral("href")); } QString XliffStorage::source(const DocPosition& pos) const { return genericContent(sourceForPos(pos.entry),pos.entry if (targetEl.isNull()) { QDomNode unitEl=unitForPos(pos.entry); QDomNode refNode=unitEl.firstChildElement(QStringLiteral("seg-source"));//obey standard if (refNode.isNull()) refNode=unitEl.firstChildElement(binsourcesource[pos.entry if (arg.isEmpty()) return; //means we were called just to add tag if (pos.entry>=size()) { QDomElement ef=targetEl.firstChildElement(QStringLiteral("external-file")); if (ef.isNull()) ef=targetEl.appendChild(m_doc.createElement(QStringLiteral("external-file"))).toElement(); ef.setAttribute(QStringLiteral("href"),arg); return; } ContentEditingData data(pos.offset,arg); content(targetEl,&data); } void XliffStorage::targetInsertTag(const DocPosition& pos, const InlineTag& tag) { targetInsert(pos,QString()); //adds if needed ContentEditingData data(tag.start,tag); content(targetForPos(pos.entry),&data); } InlineTag XliffStorage::targetDeleteTag(const DocPosition& pos) { ContentEditingData data(pos.offset); content(targetForPos(pos.entry),&data); if (data.tags[0].end==-1) data.tags[0].end=data.tags[0].start; return data.tags.first(); } void XliffStorage::setTarget(const DocPosition& pos, const QString& arg) { Q_UNUSED(pos); Q_UNUSED(arg); //TODO } QVector XliffStorage::altTrans(const DocPosition& pos) const { QVector result; QDomElement elem = unitForPos(pos.entry).firstChildElement(QStringLiteral("alt-trans")); while (!elem.isNull()) { AltTrans aTrans; aTrans.source=catalogString(elem, DocPosition::Source); aTrans.target=catalogString(elem, DocPosition::Target); aTrans.phase=elem.attribute(QStringLiteral("phase-name")); aTrans.origin=elem.attribute(QStringLiteral("origin")); aTrans.score=elem.attribute(QStringLiteral("match-quality")).toInt(); aTrans.lang=elem.firstChildElement(QStringLiteral("target")).attribute(QStringLiteral("xml:lang")); const char* const types[]={ "proposal", "previous-version", "rejected", "reference", "accepted" }; QString typeStr=elem.attribute(QStringLiteral("alttranstype")); int i=-1; while (++i XliffStorage::allPhases() const { QList result; QDomElement file=m_doc.elementsByTagName(QStringLiteral("file")).at(0).toElement(); QDomElement header=file.firstChildElement(QStringLiteral("header")); QDomElement phasegroup=header.firstChildElement(QStringLiteral("phase-group")); QDomElement phaseElem=phasegroup.firstChildElement(QStringLiteral("phase")); while (!phaseElem.isNull()) { result.append(phaseFromElement(phaseElem)); phaseElem=phaseElem.nextSiblingElement(QStringLiteral("phase")); } return result; } Phase XliffStorage::phase(const QString& name) const { QDomElement phasegroup; QDomElement phaseElem=phaseElement(m_doc,name,phasegroup); return phaseFromElement(phaseElem); } QMap XliffStorage::allTools() const { QMap result; QDomElement file=m_doc.elementsByTagName(QStringLiteral("file")).at(0).toElement(); QDomElement header=file.firstChildElement(QStringLiteral("header")); QDomElement toolElem=header.firstChildElement(QStringLiteral("tool")); while (!toolElem.isNull()) { Tool tool; tool.tool =toolElem.attribute(QStringLiteral("tool-id")); tool.name =toolElem.attribute(QStringLiteral("tool-name")); tool.version =toolElem.attribute(QStringLiteral("tool-version")); tool.company =toolElem.attribute(QStringLiteral("tool-company")); result.insert(tool.tool, tool); toolElem=toolElem.nextSiblingElement(QStringLiteral("tool")); } return result; } QStringList XliffStorage::sourceFiles(const DocPosition& pos) const { QStringList result; QDomElement elem = unitForPos(pos.entry).firstChildElement(QStringLiteral("context-group")); while (!elem.isNull()) { if (elem.attribute(QStringLiteral("purpose")).contains(QLatin1String("location"))) { QDomElement context = elem.firstChildElement(QStringLiteral("context")); while (!context.isNull()) { QString sourcefile; QString linenumber; const QString contextType = context.attribute(QStringLiteral("context-type")); if (contextType == QLatin1String("sourcefile")) sourcefile = context.text(); else if (contextType == QLatin1String("linenumber")) linenumber = context.text(); if (!( sourcefile.isEmpty() && linenumber.isEmpty() )) result.append(sourcefile % ':' % linenumber); context=context.nextSiblingElement(QStringLiteral("context")); } } elem=elem.nextSiblingElement(QStringLiteral("context-group")); } //qSort(result); return result; } static void initNoteFromElement(Note& note, QDomElement elem) { note.content=elem.text(); note.from=elem.attribute(QStringLiteral("from")); note.lang=elem.attribute(QStringLiteral("xml:lang")); if (elem.attribute(QStringLiteral("annotates"))==QLatin1String("source")) note.annotates=Note::Source; else if (elem.attribute(QStringLiteral("annotates"))==QLatin1String("target")) note.annotates=Note::Target; bool ok; note.priority=elem.attribute(QStringLiteral("priority")).toInt(&ok); if (!ok) note.priority=0; } QVector XliffStorage::notes(const DocPosition& pos) const { QList result; QDomElement elem = entries.at(m_map.at(pos.entry)).firstChildElement(NOTE); while (!elem.isNull()) { Note note; initNoteFromElement(note,elem); result.append(note); elem=elem.nextSiblingElement(NOTE); } qSort(result); return result.toVector(); } QVector XliffStorage::developerNotes(const DocPosition& pos) const { Q_UNUSED(pos); //TODO return QVector(); } Note XliffStorage::setNote(DocPosition pos, const Note& note) { //qCWarning(LOKALIZE_LOG)< result; QDomNodeList notes=m_doc.elementsByTagName(NOTE); int i=notes.size(); while (--i>=0) { QString from=notes.at(i).toElement().attribute(QStringLiteral("from")); if (!from.isEmpty()) result.insert(from); } return result.toList(); } QVector phaseNotes(QDomDocument m_doc, const QString& phasename, bool remove=false) { QVector result; QDomElement phasegroup; QDomElement phaseElem=phaseElement(m_doc,phasename,phasegroup); QDomElement noteElem=phaseElem.firstChildElement(NOTE); while (!noteElem.isNull()) { Note note; initNoteFromElement(note,noteElem); result.append(note); QDomElement old=noteElem; noteElem=noteElem.nextSiblingElement(NOTE); if (remove) phaseElem.removeChild(old); } return result; } QVector XliffStorage::phaseNotes(const QString& phasename) const { return ::phaseNotes(m_doc, phasename, false); } QVector XliffStorage::setPhaseNotes(const QString& phasename, QVector notes) { QVector result=::phaseNotes(m_doc, phasename, true); QDomElement phasegroup; QDomElement phaseElem=phaseElement(m_doc,phasename,phasegroup); foreach(const Note& note, notes) { QDomElement elem=phaseElem.appendChild(m_doc.createElement(NOTE)).toElement(); elem.appendChild(m_doc.createTextNode(note.content)); if (!note.from.isEmpty()) elem.setAttribute(QStringLiteral("from"),note.from); if (note.priority) elem.setAttribute(QStringLiteral("priority"),note.priority); } return result; } QString XliffStorage::setPhase(const DocPosition& pos, const QString& phase) { QString PHASENAME=QStringLiteral("phase-name"); targetInsert(pos,QString()); //adds if needed QDomElement target=targetForPos(pos.entry); QString result=target.attribute(PHASENAME); if (phase.isEmpty()) target.removeAttribute(PHASENAME); else if (phase!=result) target.setAttribute(PHASENAME,phase); return result; } QString XliffStorage::phase(const DocPosition& pos) const { QDomElement target=targetForPos(pos.entry); return target.attribute(QStringLiteral("phase-name")); } QStringList XliffStorage::context(const DocPosition& pos) const { Q_UNUSED(pos); //TODO return QStringList(QString()); } QStringList XliffStorage::matchData(const DocPosition& pos) const { Q_UNUSED(pos); return QStringList(); } QString XliffStorage::id(const DocPosition& pos) const { return unitForPos(pos.entry).attribute(QStringLiteral("id")); } bool XliffStorage::isPlural(const DocPosition& pos) const { return m_plurals.contains(pos.entry); } /* bool XliffStorage::isApproved(const DocPosition& pos) const { return entries.at(m_map.at(pos.entry)).toElement().attribute("approved")=="yes"; } void XliffStorage::setApproved(const DocPosition& pos, bool approved) { static const char* const noyes[]={"no","yes"}; entries.at(m_map.at(pos.entry)).toElement().setAttribute("approved",noyes[approved]); } */ static const QString xliff_states[]={ U("new"), U("needs-translation"), U("needs-l10n"), U("needs-adaptation"), U("translated"), U("needs-review-translation"), U("needs-review-l10n"), U("needs-review-adaptation"), U("final"), U("signed-off")}; TargetState stringToState(const QString& state) { int i=sizeof(xliff_states)/sizeof(QString); while (--i>0 && state!=xliff_states[i]) ; return TargetState(i); } TargetState XliffStorage::setState(const DocPosition& pos, TargetState state) { targetInsert(pos,QString()); //adds if needed QDomElement target=targetForPos(pos.entry); TargetState prev=stringToState(target.attribute(QStringLiteral("state"))); target.setAttribute(QStringLiteral("state"),xliff_states[state]); unitForPos(pos.entry).setAttribute(QStringLiteral("approved"), noyes[state==SignedOff]); return prev; } TargetState XliffStorage::state(const DocPosition& pos) const { QDomElement target=targetForPos(pos.entry); if (!target.hasAttribute(QStringLiteral("state")) && unitForPos(pos.entry).attribute(QStringLiteral("approved"))==QLatin1String("yes")) return SignedOff; return stringToState(target.attribute(QStringLiteral("state"))); } bool XliffStorage::isEmpty(const DocPosition& pos) const { ContentEditingData data(ContentEditingData::CheckLength); return content(targetForPos(pos.entry),&data).isEmpty(); } bool XliffStorage::isEquivTrans(const DocPosition& pos) const { return targetForPos(pos.entry).attribute(QStringLiteral("equiv-trans"))!=QLatin1String("no"); } void XliffStorage::setEquivTrans(const DocPosition& pos, bool equivTrans) { targetForPos(pos.entry).setAttribute(QStringLiteral("equiv-trans"),noyes[equivTrans]); } QDomElement XliffStorage::unitForPos(int pos) const { if (pos= 3 -# define KDE_ISLIKELY( x ) __builtin_expect(!!(x),1) -# define KDE_ISUNLIKELY( x ) __builtin_expect(!!(x),0) -#else -# define KDE_ISLIKELY( x ) ( x ) -# define KDE_ISUNLIKELY( x ) ( x ) -#endif -#endif diff --git a/src/editortab.cpp b/src/editortab.cpp index 9e20614..21c1fba 100644 --- a/src/editortab.cpp +++ b/src/editortab.cpp @@ -1,1723 +1,1721 @@ /* **************************************************************************** This file is part of Lokalize Copyright (C) 2007-2014 by Nick Shaforostoff 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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, see . **************************************************************************** */ #include "editortab.h" #include "lokalize_debug.h" #include "actionproxy.h" #include "editorview.h" #include "catalog.h" #include "pos.h" #include "cmd.h" #include "completionstorage.h" #ifndef NOKDE #define WEBQUERY_ENABLE #endif //views #include "msgctxtview.h" #include "alttransview.h" #include "mergeview.h" #include "cataloglistview.h" #include "glossaryview.h" #ifdef WEBQUERY_ENABLE #include "webqueryview.h" #endif #include "tmview.h" #include "binunitsview.h" #include "phaseswindow.h" #include "projectlocal.h" #include "project.h" #include "prefs.h" #include "languagelistmodel.h" -#include "kdemacros.h" #ifndef NOKDE #include -#include #include #include #include #include #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include EditorTab::EditorTab(QWidget* parent, bool valid) : LokalizeSubwindowBase2(parent) , m_project(Project::instance()) , m_catalog(new Catalog(this)) , m_view(new EditorView(this,m_catalog/*,new keyEventHandler(this,m_catalog)*/)) #ifndef NOKDE , m_sonnetDialog(0) , m_spellcheckStartUndoIndex(0) , m_spellcheckStop(false) #endif , m_currentIsApproved(true) , m_currentIsUntr(true) , m_fullPathShown(false) #ifndef NOKDE , m_doReplaceCalled(false) , m_find(0) , m_replace(0) #endif , m_syncView(0) , m_syncViewSecondary(0) #ifndef NOKDE , m_valid(valid) , m_dbusId(-1) #endif { //QTime chrono;chrono.start(); setAcceptDrops(true); setCentralWidget(m_view); setupStatusBar(); //--NOT called from initLater() ! setupActions(); #ifndef NOKDE dbusObjectPath(); #endif connect(m_view, SIGNAL(signalChanged(uint)), this, SLOT(msgStrChanged())); msgStrChanged(); connect(SettingsController::instance(),SIGNAL(generalSettingsChanged()),m_view, SLOT(settingsChanged())); connect(m_view->tabBar(),&QTabBar::currentChanged,this,&EditorTab::switchForm); connect(m_view, SIGNAL(gotoEntryRequested(DocPosition)),this,SLOT(gotoEntry(DocPosition))); connect(m_view, SIGNAL(tmLookupRequested(DocPosition::Part,QString)), this, SLOT(lookupSelectionInTranslationMemory())); connect(this, SIGNAL(fileOpened()), this, SLOT(indexWordsForCompletion()),Qt::QueuedConnection); connect (m_catalog,&Catalog::signalFileAutoSaveFailed,this,&EditorTab::fileAutoSaveFailedWarning); //defer some work to make window appear earlier (~200 msec on my Core Duo) //QTimer::singleShot(0,this,SLOT(initLater())); //qCWarning(LOKALIZE_LOG)<isEmpty()) { emit fileAboutToBeClosed(); emit fileClosed(); emit fileClosed(currentFile()); } #ifndef NOKDE ids.removeAll(m_dbusId); #endif } void EditorTab::setupStatusBar() { statusBarItems.insert(ID_STATUS_CURRENT,i18nc("@info:status message entry","Current: %1",0)); statusBarItems.insert(ID_STATUS_TOTAL,i18nc("@info:status message entries","Total: %1",0)); statusBarItems.insert(ID_STATUS_FUZZY,i18nc("@info:status message entries\n'fuzzy' in gettext terminology","Not ready: %1",0)); statusBarItems.insert(ID_STATUS_UNTRANS,i18nc("@info:status message entries","Untranslated: %1",0)); statusBarItems.insert(ID_STATUS_ISFUZZY,QString()); connect(m_catalog,&Catalog::signalNumberOfFuzziesChanged,this,&EditorTab::numberOfFuzziesChanged); connect(m_catalog,&Catalog::signalNumberOfEmptyChanged,this,&EditorTab::numberOfUntranslatedChanged); } #ifndef NOKDE void LokalizeSubwindowBase::reflectNonApprovedCount(int count, int total) { QString text=i18nc("@info:status message entries\n'fuzzy' in gettext terminology","Not ready: %1", count); if (count && total) text+=i18nc("percentages in statusbar", " (%1%)", int(100.0*count/total)); statusBarItems.insert(ID_STATUS_FUZZY,text); } void LokalizeSubwindowBase::reflectUntranslatedCount(int count, int total) { QString text=i18nc("@info:status message entries","Untranslated: %1", count); if (count && total) text+=i18nc("percentages in statusbar", " (%1%)", int(100.0*count/total)); statusBarItems.insert(ID_STATUS_UNTRANS,text); } #endif void EditorTab::numberOfFuzziesChanged() { reflectNonApprovedCount(m_catalog->numberOfNonApproved(),m_catalog->numberOfEntries()); } void EditorTab::numberOfUntranslatedChanged() { reflectUntranslatedCount(m_catalog->numberOfUntranslated(),m_catalog->numberOfEntries()); } void EditorTab::setupActions() { //all operations that can be done after initial setup //(via QTimer::singleShot) go to initLater() setXMLFile(QStringLiteral("editorui.rc")); QAction *action; KActionCollection* ac=actionCollection(); KActionCategory* actionCategory; KActionCategory* file=new KActionCategory(i18nc("@title actions category","File"), ac); KActionCategory* nav=new KActionCategory(i18nc("@title actions category","Navigation"), ac); KActionCategory* edit=new KActionCategory(i18nc("@title actions category","Editing"), ac); KActionCategory* sync1=new KActionCategory(i18n("Synchronization 1"), ac); KActionCategory* sync2=new KActionCategory(i18n("Synchronization 2"), ac); KActionCategory* tm=new KActionCategory(i18n("Translation Memory"), ac); KActionCategory* glossary=new KActionCategory(i18nc("@title actions category","Glossary"), actionCollection()); //KActionCategory* tools=new KActionCategory(i18nc("@title actions category","Tools"), actionCollection()); #ifndef Q_OS_DARWIN QLocale::Language systemLang = QLocale::system().language(); #endif //BEGIN dockwidgets int i=0; QVector altactions(ALTTRANS_SHORTCUTS); Qt::Key altlist[ALTTRANS_SHORTCUTS]= { Qt::Key_1, Qt::Key_2, Qt::Key_3, Qt::Key_4, Qt::Key_5, Qt::Key_6, Qt::Key_7, Qt::Key_8, Qt::Key_9 }; QAction* altaction; for (i=0;iaddAction(QStringLiteral("alttrans_insert_%1").arg(i)); ac->setDefaultShortcut(altaction, QKeySequence(Qt::ALT+altlist[i])); altaction->setText(i18nc("@action:inmenu","Insert alternate translation #%1",QString::number(i))); altactions[i]=altaction; } m_altTransView = new AltTransView(this,m_catalog,altactions); addDockWidget(Qt::BottomDockWidgetArea, m_altTransView); actionCollection()->addAction( QStringLiteral("showmsgiddiff_action"), m_altTransView->toggleViewAction() ); connect (this,SIGNAL(signalNewEntryDisplayed(DocPosition)),m_altTransView,SLOT(slotNewEntryDisplayed(DocPosition))); connect (m_altTransView,SIGNAL(textInsertRequested(QString)),m_view,SLOT(insertTerm(QString))); connect (m_altTransView,SIGNAL(refreshRequested()),m_view,SLOT(gotoEntry()),Qt::QueuedConnection); connect (m_catalog,SIGNAL(signalFileLoaded()),m_altTransView,SLOT(fileLoaded())); m_syncView = new MergeView(this,m_catalog,true); addDockWidget(Qt::BottomDockWidgetArea, m_syncView); sync1->addAction( QStringLiteral("showmergeview_action"), m_syncView->toggleViewAction() ); connect (this,SIGNAL(signalNewEntryDisplayed(DocPosition)),m_syncView,SLOT(slotNewEntryDisplayed(DocPosition))); connect (m_catalog,SIGNAL(signalFileLoaded()),m_syncView,SLOT(cleanup())); connect (m_syncView,SIGNAL(gotoEntry(DocPosition,int)), this,SLOT(gotoEntry(DocPosition,int))); m_syncViewSecondary = new MergeView(this,m_catalog,false); addDockWidget(Qt::BottomDockWidgetArea, m_syncViewSecondary); sync2->addAction( QStringLiteral("showmergeviewsecondary_action"), m_syncViewSecondary->toggleViewAction() ); connect (this,SIGNAL(signalNewEntryDisplayed(DocPosition)),m_syncViewSecondary,SLOT(slotNewEntryDisplayed(DocPosition))); connect (m_catalog,SIGNAL(signalFileLoaded()),m_syncViewSecondary,SLOT(cleanup())); connect (m_catalog,SIGNAL(signalFileLoaded(QString)),m_syncViewSecondary,SLOT(mergeOpen(QString)),Qt::QueuedConnection); connect (m_syncViewSecondary,SIGNAL(gotoEntry(DocPosition,int)), this,SLOT(gotoEntry(DocPosition,int))); m_transUnitsView = new CatalogView(this,m_catalog); addDockWidget(Qt::LeftDockWidgetArea, m_transUnitsView); actionCollection()->addAction( QStringLiteral("showcatalogtreeview_action"), m_transUnitsView->toggleViewAction() ); connect (this,SIGNAL(signalNewEntryDisplayed(DocPosition)),m_transUnitsView,SLOT(slotNewEntryDisplayed(DocPosition))); connect (m_transUnitsView,SIGNAL(gotoEntry(DocPosition,int)),this,SLOT(gotoEntry(DocPosition,int))); connect (m_transUnitsView,&CatalogView::escaped,this,&EditorTab::setProperFocus); connect (m_syncView,&MergeView::mergeCatalogPointerChanged, m_transUnitsView, &CatalogView::setMergeCatalogPointer); m_notesView = new MsgCtxtView(this,m_catalog); addDockWidget(Qt::LeftDockWidgetArea, m_notesView); actionCollection()->addAction( QStringLiteral("showmsgctxt_action"), m_notesView->toggleViewAction() ); connect(m_catalog,SIGNAL(signalFileLoaded()),m_notesView,SLOT(cleanup())); connect(m_notesView,SIGNAL(srcFileOpenRequested(QString,int)),this,SLOT(dispatchSrcFileOpenRequest(QString,int))); connect(m_view, SIGNAL(signalChanged(uint)), m_notesView, SLOT(removeErrorNotes())); connect(m_notesView,&MsgCtxtView::escaped,this,&EditorTab::setProperFocus); action=edit->addAction(QStringLiteral("edit_addnote"),m_notesView,SLOT(addNoteUI())); //action->setShortcut(Qt::CTRL+glist[i]); action->setText(i18nc("@action:inmenu","Add a note")); QVector tmactions(TM_SHORTCUTS); Qt::Key tmlist[TM_SHORTCUTS]= { Qt::Key_1, Qt::Key_2, Qt::Key_3, Qt::Key_4, Qt::Key_5, Qt::Key_6, Qt::Key_7, Qt::Key_8, Qt::Key_9, Qt::Key_0 }; QAction* tmaction; for (i=0;isetVisible(false); tmaction=tm->addAction(QStringLiteral("tmquery_insert_%1").arg(i)); ac->setDefaultShortcut(tmaction, QKeySequence(Qt::CTRL+tmlist[i])); tmaction->setText(i18nc("@action:inmenu","Insert TM suggestion #%1",QString::number(i+1))); tmactions[i]=tmaction; } #ifndef Q_OS_DARWIN if (systemLang==QLocale::Czech) ac->setDefaultShortcuts(tmactions[0], QList()<addAction( QStringLiteral("showtmqueryview_action"), _tmView->toggleViewAction() ); connect (_tmView,SIGNAL(refreshRequested()),m_view,SLOT(gotoEntry()),Qt::QueuedConnection); connect (_tmView,SIGNAL(refreshRequested()),this,SLOT(msgStrChanged()),Qt::QueuedConnection); connect (_tmView,SIGNAL(textInsertRequested(QString)),m_view,SLOT(insertTerm(QString))); connect (_tmView,SIGNAL(fileOpenRequested(QString,QString,QString)),this,SIGNAL(fileOpenRequested(QString,QString,QString))); connect (this,SIGNAL(fileAboutToBeClosed()),m_catalog,SLOT(flushUpdateDBBuffer())); connect (this,SIGNAL(signalNewEntryDisplayed(DocPosition)),m_catalog,SLOT(flushUpdateDBBuffer())); connect (this,SIGNAL(signalNewEntryDisplayed(DocPosition)),_tmView,SLOT(slotNewEntryDisplayed(DocPosition))); //do this after flushUpdateDBBuffer QVector gactions(GLOSSARY_SHORTCUTS); Qt::Key glist[GLOSSARY_SHORTCUTS]= { Qt::Key_E, Qt::Key_H, // Qt::Key_G, // Qt::Key_I, // Qt::Key_J, // Qt::Key_K, Qt::Key_K, Qt::Key_P, Qt::Key_N, // Qt::Key_Q, // Qt::Key_R, // Qt::Key_U, // Qt::Key_V, // Qt::Key_W, // Qt::Key_X, Qt::Key_Y, // Qt::Key_Z, Qt::Key_BraceLeft, Qt::Key_BraceRight, Qt::Key_Semicolon, Qt::Key_Apostrophe }; QAction* gaction; // int i=0; for (i=0;isetVisible(false); gaction=glossary->addAction(QStringLiteral("glossary_insert_%1").arg(i)); ac->setDefaultShortcut(gaction, QKeySequence(Qt::CTRL+glist[i])); gaction->setText(i18nc("@action:inmenu","Insert term translation #%1",QString::number(i))); gactions[i]=gaction; } GlossaryNS::GlossaryView* _glossaryView = new GlossaryNS::GlossaryView(this,m_catalog,gactions); addDockWidget(Qt::BottomDockWidgetArea, _glossaryView); glossary->addAction( QStringLiteral("showglossaryview_action"), _glossaryView->toggleViewAction() ); connect (this,&EditorTab::signalNewEntryDisplayed,_glossaryView,&GlossaryNS::GlossaryView::slotNewEntryDisplayed); connect (_glossaryView,SIGNAL(termInsertRequested(QString)),m_view,SLOT(insertTerm(QString))); gaction = glossary->addAction(QStringLiteral("glossary_define"),this,SLOT(defineNewTerm())); gaction->setText(i18nc("@action:inmenu","Define new term")); _glossaryView->addAction(gaction); _glossaryView->setContextMenuPolicy( Qt::ActionsContextMenu); BinUnitsView* binUnitsView=new BinUnitsView(m_catalog,this); addDockWidget(Qt::BottomDockWidgetArea, binUnitsView); edit->addAction( QStringLiteral("showbinunitsview_action"), binUnitsView->toggleViewAction() ); connect(m_view,&EditorView::binaryUnitSelectRequested,binUnitsView,&BinUnitsView::selectUnit); //#ifdef WEBQUERY_ENABLE #if 0 QVector wqactions(WEBQUERY_SHORTCUTS); Qt::Key wqlist[WEBQUERY_SHORTCUTS]= { Qt::Key_1, Qt::Key_2, Qt::Key_3, Qt::Key_4, Qt::Key_5, Qt::Key_6, Qt::Key_7, Qt::Key_8, Qt::Key_9, Qt::Key_0, }; QAction* wqaction; for (i=0;isetVisible(false); wqaction=actionCollection()->addAction(QString("webquery_insert_%1").arg(i)); wqaction->setShortcut(Qt::CTRL+Qt::ALT+wqlist[i]); //wqaction->setShortcut(Qt::META+wqlist[i]); wqaction->setText(i18nc("@action:inmenu","Insert WebQuery result #%1",i)); wqactions[i]=wqaction; } WebQueryView* _webQueryView = new WebQueryView(this,m_catalog,wqactions); addDockWidget(Qt::BottomDockWidgetArea, _webQueryView); actionCollection()->addAction( QStringLiteral("showwebqueryview_action"), _webQueryView->toggleViewAction() ); connect (this,SIGNAL(signalNewEntryDisplayed(DocPosition)),_webQueryView,SLOT(slotNewEntryDisplayed(DocPosition))); connect (_webQueryView,SIGNAL(textInsertRequested(QString)),m_view,SLOT(insertTerm(QString))); #endif //END dockwidgets actionCategory=file; // File action=file->addAction(KStandardAction::Save,this, SLOT(saveFile())); // action->setEnabled(false); // connect (m_catalog,SIGNAL(cleanChanged(bool)),action,SLOT(setDisabled(bool))); connect (m_catalog,SIGNAL(cleanChanged(bool)),this,SLOT(setModificationSign())); file->addAction(KStandardAction::SaveAs,this, SLOT(saveFileAs())); //action = KStandardAction::quit(qApp, SLOT(quit()), ac); //action->setText(i18nc("@action:inmenu","Close all Lokalize windows")); //KStandardAction::quit(kapp, SLOT(quit()), ac); //KStandardAction::quit(this, SLOT(deleteLater()), ac); #define ADD_ACTION_SHORTCUT_ICON(_name,_text,_shortcut,_icon)\ action = actionCategory->addAction(QStringLiteral(_name));\ action->setText(_text);\ action->setIcon(QIcon::fromTheme(QStringLiteral(_icon)));\ ac->setDefaultShortcut(action, QKeySequence( _shortcut )); #define ADD_ACTION_SHORTCUT(_name,_text,_shortcut)\ action = actionCategory->addAction(QStringLiteral(_name));\ action->setText(_text);\ ac->setDefaultShortcut(action, QKeySequence( _shortcut )); action = actionCategory->addAction(QStringLiteral("file_phases")); action->setText(i18nc("@action:inmenu","Phases...")); connect(action, SIGNAL(triggered()), SLOT(openPhasesWindow())); ADD_ACTION_SHORTCUT("file_wordcount",i18nc("@action:inmenu","Word count"),Qt::CTRL+Qt::ALT+Qt::Key_C) connect( action, SIGNAL(triggered(bool)), this, SLOT(displayWordCount()) ); ADD_ACTION_SHORTCUT("file_xliff2odf",i18nc("@action:inmenu","Merge translation into OpenDocument"),Qt::CTRL+Qt::Key_Backslash) connect( action, SIGNAL(triggered(bool)), this, SLOT(mergeIntoOpenDocument()) ); connect( this, SIGNAL(xliffFileOpened(bool)), action, SLOT(setVisible(bool)) ); action->setVisible(false); //Edit actionCategory=edit; action=edit->addAction(KStandardAction::Undo,this,SLOT(undo())); connect(m_view->viewPort(),SIGNAL(undoRequested()),this,SLOT(undo())); connect(m_catalog,SIGNAL(canUndoChanged(bool)),action,SLOT(setEnabled(bool)) ); action->setEnabled(false); action=edit->addAction(KStandardAction::Redo,this,SLOT(redo())); connect(m_view->viewPort(),SIGNAL(redoRequested()),this,SLOT(redo())); connect(m_catalog,SIGNAL(canRedoChanged(bool)),action,SLOT(setEnabled(bool)) ); action->setEnabled(false); action=nav->addAction(KStandardAction::Find,this,SLOT(find())); action=nav->addAction(KStandardAction::FindNext,this,SLOT(findNext())); action=nav->addAction(KStandardAction::FindPrev,this,SLOT(findPrev())); action->setText(i18nc("@action:inmenu","Change searching direction")); action=edit->addAction(KStandardAction::Replace,this,SLOT(replace())); connect(m_view,SIGNAL(findRequested()), this,SLOT(find())); connect(m_view,SIGNAL(findNextRequested()), this,SLOT(findNext())); connect(m_view,SIGNAL(replaceRequested()), this,SLOT(replace())); action = actionCategory->addAction(QStringLiteral("edit_approve"), new KToolBarPopupAction(QIcon::fromTheme(QStringLiteral("approved")), i18nc("@option:check whether message is marked as translated/reviewed/approved (depending on your role)","Approved"), this)); ac->setDefaultShortcut(action, QKeySequence(Qt::CTRL+Qt::Key_U)); action->setCheckable(true); connect(action, SIGNAL(triggered()), m_view,SLOT(toggleApprovement())); connect(m_view, SIGNAL(signalApprovedEntryDisplayed(bool)),this,SIGNAL(signalApprovedEntryDisplayed(bool))); connect(this, &EditorTab::signalApprovedEntryDisplayed,action,&QAction::setChecked); connect(this, SIGNAL(signalApprovedEntryDisplayed(bool)),this,SLOT(msgStrChanged()),Qt::QueuedConnection); m_approveAction=action; #ifdef NOKDE QMenu* am=new QMenu(i18nc("@option:check whether message is marked as translated/reviewed/approved (depending on your role)","State"),this); action=am->menuAction(); ac->addAction(QStringLiteral("edit_state"),action); #endif m_stateAction=action; connect(Project::local(), SIGNAL(configChanged()), SLOT(setApproveActionTitle())); connect(m_catalog, SIGNAL(activePhaseChanged()), SLOT(setApproveActionTitle())); connect(m_stateAction->menu(), SIGNAL(aboutToShow()),this,SLOT(showStatesMenu())); connect(m_stateAction->menu(), SIGNAL(triggered(QAction*)),this,SLOT(setState(QAction*))); action = actionCategory->addAction(QStringLiteral("edit_approve_go_fuzzyUntr")); action->setText(i18nc("@action:inmenu","Approve and go to next")); connect(action, SIGNAL(triggered()), SLOT(toggleApprovementGotoNextFuzzyUntr())); m_approveAndGoAction = action; setApproveActionTitle(); action = actionCategory->addAction(QStringLiteral("edit_nonequiv"),m_view,SLOT(setEquivTrans(bool))); action->setText(i18nc("@action:inmenu","Equivalent translation")); action->setCheckable(true); connect(this, SIGNAL(signalEquivTranslatedEntryDisplayed(bool)),action,SLOT(setChecked(bool))); #ifndef Q_OS_DARWIN int copyShortcut=Qt::CTRL+Qt::Key_Space; - if (KDE_ISUNLIKELY( systemLang==QLocale::Korean + if (Q_UNLIKELY( systemLang==QLocale::Korean || systemLang==QLocale::Japanese || systemLang==QLocale::Chinese )) copyShortcut=Qt::ALT+Qt::Key_Space; #else int copyShortcut=Qt::META+Qt::Key_Space; #endif ADD_ACTION_SHORTCUT_ICON("edit_msgid2msgstr",i18nc("@action:inmenu","Copy source to target"),copyShortcut,"msgid2msgstr") connect(action, SIGNAL(triggered(bool)), m_view->viewPort(),SLOT(source2target())); ADD_ACTION_SHORTCUT("edit_unwrap-target",i18nc("@action:inmenu","Unwrap target"),Qt::CTRL+Qt::Key_I) connect(action, SIGNAL(triggered(bool)), m_view,SLOT(unwrap())); action=edit->addAction(QStringLiteral("edit_clear-target"),m_view->viewPort(),SLOT(removeTargetSubstring())); ac->setDefaultShortcut(action, QKeySequence(Qt::CTRL+Qt::Key_D)); action->setText(i18nc("@action:inmenu","Clear")); action=edit->addAction(QStringLiteral("edit_tagmenu"),m_view->viewPort(),SLOT(tagMenu())); ac->setDefaultShortcut(action, QKeySequence(Qt::CTRL+Qt::Key_T)); action->setText(i18nc("@action:inmenu","Insert Tag")); action=edit->addAction(QStringLiteral("edit_tagimmediate"),m_view->viewPort(),SLOT(tagImmediate())); ac->setDefaultShortcut(action, QKeySequence(Qt::CTRL+Qt::Key_M)); action->setText(i18nc("@action:inmenu","Insert Next Tag")); #ifndef NOKDE action=edit->addAction(QStringLiteral("edit_completion"),m_view,SIGNAL(doExplicitCompletion())); ac->setDefaultShortcut(action, QKeySequence(Qt::CTRL+Qt::ALT+Qt::Key_Space)); action->setText(i18nc("@action:inmenu","Completion")); action=edit->addAction(QStringLiteral("edit_spellreplace"),m_view->viewPort(),SLOT(spellReplace())); ac->setDefaultShortcut(action, QKeySequence(Qt::CTRL+Qt::Key_Equal)); action->setText(i18nc("@action:inmenu","Replace with best spellcheck suggestion")); #endif // action = ac->addAction("glossary_define",m_view,SLOT(defineNewTerm())); // action->setText(i18nc("@action:inmenu","Define new term")); // Go actionCategory=nav; action=nav->addAction(KStandardAction::Next,this, SLOT(gotoNext())); action->setText(i18nc("@action:inmenu entry","&Next")); connect( this, &EditorTab::signalLastDisplayed, action, &QAction::setDisabled); connect(m_view->viewPort(),SIGNAL(gotoNextRequested()),this,SLOT(gotoNext())); action=nav->addAction(KStandardAction::Prior,this, SLOT(gotoPrev())); action->setText(i18nc("@action:inmenu entry","&Previous")); connect( this, &EditorTab::signalFirstDisplayed, action, &QAction::setDisabled); connect(m_view->viewPort(),SIGNAL(gotoPrevRequested()),this,SLOT(gotoPrev())); action=nav->addAction(KStandardAction::FirstPage,this, SLOT(gotoFirst())); connect(m_view->viewPort(),SIGNAL(gotoFirstRequested()),this,SLOT(gotoFirst())); action->setText(i18nc("@action:inmenu","&First Entry")); action->setShortcut(QKeySequence(Qt::CTRL+Qt::ALT+Qt::Key_Home)); connect( this, &EditorTab::signalFirstDisplayed, action, &QAction::setDisabled); action=nav->addAction(KStandardAction::LastPage,this, SLOT(gotoLast())); connect(m_view->viewPort(),SIGNAL(gotoLastRequested()),this,SLOT(gotoLast())); action->setText(i18nc("@action:inmenu","&Last Entry")); action->setShortcut(QKeySequence(Qt::CTRL+Qt::ALT+Qt::Key_End)); connect( this, &EditorTab::signalLastDisplayed,action,&QAction::setDisabled); action=nav->addAction(KStandardAction::GotoPage,this, SLOT(gotoEntry())); ac->setDefaultShortcut(action, QKeySequence(Qt::CTRL+Qt::Key_G)); action->setText(i18nc("@action:inmenu","Entry by number")); ADD_ACTION_SHORTCUT_ICON("go_prev_fuzzy",i18nc("@action:inmenu\n'not ready' means 'fuzzy' in gettext terminology","Previous non-empty but not ready"),Qt::CTRL+Qt::Key_PageUp,"prevfuzzy") connect( action, SIGNAL(triggered(bool)), this, SLOT(gotoPrevFuzzy()) ); connect( m_view->viewPort(), SIGNAL(gotoPrevFuzzyRequested()), this, SLOT(gotoPrevFuzzy()) ); connect( this, &EditorTab::signalPriorFuzzyAvailable,action,&QAction::setEnabled); ADD_ACTION_SHORTCUT_ICON("go_next_fuzzy",i18nc("@action:inmenu\n'not ready' means 'fuzzy' in gettext terminology","Next non-empty but not ready"),Qt::CTRL+Qt::Key_PageDown,"nextfuzzy") connect( action, SIGNAL(triggered(bool)), this, SLOT(gotoNextFuzzy()) ); connect( m_view->viewPort(), SIGNAL(gotoNextFuzzyRequested()), this, SLOT(gotoNextFuzzy()) ); connect( this, &EditorTab::signalNextFuzzyAvailable,action,&QAction::setEnabled); ADD_ACTION_SHORTCUT_ICON("go_prev_untrans",i18nc("@action:inmenu","Previous untranslated"),Qt::ALT+Qt::Key_PageUp,"prevuntranslated") connect( action, SIGNAL(triggered(bool)), this, SLOT(gotoPrevUntranslated())); connect( m_view->viewPort(), SIGNAL(gotoPrevUntranslatedRequested()), this, SLOT(gotoPrevUntranslated()) ); connect( this, &EditorTab::signalPriorUntranslatedAvailable,action,&QAction::setEnabled); ADD_ACTION_SHORTCUT_ICON("go_next_untrans",i18nc("@action:inmenu","Next untranslated"),Qt::ALT+Qt::Key_PageDown,"nextuntranslated") connect( action, SIGNAL(triggered(bool)), this, SLOT(gotoNextUntranslated())); connect( m_view->viewPort(), SIGNAL(gotoNextUntranslatedRequested()), this, SLOT(gotoNextUntranslated()) ); connect( this, &EditorTab::signalNextUntranslatedAvailable,action,&QAction::setEnabled); ADD_ACTION_SHORTCUT_ICON("go_prev_fuzzyUntr",i18nc("@action:inmenu\n'not ready' means 'fuzzy' in gettext terminology","Previous not ready"),Qt::CTRL+Qt::SHIFT/*ALT*/+Qt::Key_PageUp,"prevfuzzyuntrans") connect( action, SIGNAL(triggered(bool)), this, SLOT(gotoPrevFuzzyUntr()) ); connect( m_view->viewPort(), SIGNAL(gotoPrevFuzzyUntrRequested()), this, SLOT(gotoPrevFuzzyUntr()) ); connect( this, &EditorTab::signalPriorFuzzyOrUntrAvailable,action,&QAction::setEnabled); ADD_ACTION_SHORTCUT_ICON("go_next_fuzzyUntr",i18nc("@action:inmenu\n'not ready' means 'fuzzy' in gettext terminology","Next not ready"),Qt::CTRL+Qt::SHIFT+Qt::Key_PageDown,"nextfuzzyuntrans") connect( action, SIGNAL(triggered(bool)), this, SLOT(gotoNextFuzzyUntr()) ); connect( m_view->viewPort(), SIGNAL(gotoNextFuzzyUntrRequested()), this, SLOT(gotoNextFuzzyUntr()) ); connect( this, &EditorTab::signalNextFuzzyOrUntrAvailable,action,&QAction::setEnabled); action=nav->addAction(QStringLiteral("go_focus_earch_line"),m_transUnitsView, SLOT(setFocus())); ac->setDefaultShortcut(action, QKeySequence(Qt::CTRL+Qt::Key_L)); action->setText(i18nc("@action:inmenu","Focus the search line of Translation Units view")); //Bookmarks action=nav->addAction(KStandardAction::AddBookmark,m_view,SLOT(toggleBookmark(bool))); //action = ac->addAction("bookmark_do"); action->setText(i18nc("@option:check","Bookmark message")); action->setCheckable(true); //connect(action, SIGNAL(triggered(bool)),m_view,SLOT(toggleBookmark(bool))); connect( this, &EditorTab::signalBookmarkDisplayed,action,&QAction::setChecked); action=nav->addAction(QStringLiteral("bookmark_prior"),this,SLOT(gotoPrevBookmark())); action->setText(i18nc("@action:inmenu","Previous bookmark")); connect( this, &EditorTab::signalPriorBookmarkAvailable,action,&QAction::setEnabled); action=nav->addAction(QStringLiteral("bookmark_next"),this,SLOT(gotoNextBookmark())); action->setText(i18nc("@action:inmenu","Next bookmark")); connect( this, &EditorTab::signalNextBookmarkAvailable,action,&QAction::setEnabled); //Tools edit->addAction(KStandardAction::Spelling,this,SLOT(spellcheck())); actionCategory=tm; // xgettext: no-c-format ADD_ACTION_SHORTCUT("tools_tm_batch",i18nc("@action:inmenu","Fill in all exact suggestions"),Qt::CTRL+Qt::ALT+Qt::Key_B) connect( action, SIGNAL(triggered(bool)), _tmView, SLOT(slotBatchTranslate()) ); // xgettext: no-c-format ADD_ACTION_SHORTCUT("tools_tm_batch_fuzzy",i18nc("@action:inmenu","Fill in all exact suggestions and mark as fuzzy"),Qt::CTRL+Qt::ALT+Qt::Key_N) connect( action, SIGNAL(triggered(bool)), _tmView, SLOT(slotBatchTranslateFuzzy()) ); //MergeMode action = sync1->addAction(QStringLiteral("merge_open"),m_syncView,SLOT(mergeOpen())); action->setText(i18nc("@action:inmenu","Open file for sync/merge")); action->setStatusTip(i18nc("@info:status","Open catalog to be merged into the current one / replicate base file changes to")); action->setToolTip(action->statusTip()); action->setWhatsThis(action->statusTip()); m_syncView->addAction(action); action = sync1->addAction(QStringLiteral("merge_prev"),m_syncView,SLOT(gotoPrevChanged())); action->setText(i18nc("@action:inmenu","Previous different")); action->setStatusTip(i18nc("@info:status","Previous entry which is translated differently in the file being merged, including empty translations in merge source")); action->setToolTip(action->statusTip()); action->setWhatsThis(action->statusTip()); ac->setDefaultShortcut(action, QKeySequence(Qt::ALT+Qt::Key_Up)); connect( m_syncView, &MergeView::signalPriorChangedAvailable,action,&QAction::setEnabled); m_syncView->addAction(action); action = sync1->addAction(QStringLiteral("merge_next"),m_syncView,SLOT(gotoNextChanged())); action->setText(i18nc("@action:inmenu","Next different")); action->setStatusTip(i18nc("@info:status","Next entry which is translated differently in the file being merged, including empty translations in merge source")); action->setToolTip(action->statusTip()); action->setWhatsThis(action->statusTip()); ac->setDefaultShortcut(action, QKeySequence(Qt::ALT+Qt::Key_Down)); connect( m_syncView, &MergeView::signalNextChangedAvailable,action,&QAction::setEnabled); m_syncView->addAction(action); action = sync1->addAction(QStringLiteral("merge_nextapproved"),m_syncView,SLOT(gotoNextChangedApproved())); action->setText(i18nc("@action:inmenu","Next different approved")); ac->setDefaultShortcut(action, QKeySequence(Qt::ALT+Qt::META+Qt::Key_Down)); connect( m_syncView, &MergeView::signalNextChangedAvailable,action,&QAction::setEnabled); m_syncView->addAction(action); action = sync1->addAction(QStringLiteral("merge_accept"),m_syncView,SLOT(mergeAccept())); action->setText(i18nc("@action:inmenu","Copy from merging source")); action->setEnabled(false); ac->setDefaultShortcut(action, QKeySequence(Qt::ALT+Qt::Key_Return)); connect( m_syncView, &MergeView::signalEntryWithMergeDisplayed,action,&QAction::setEnabled); m_syncView->addAction(action); action = sync1->addAction(QStringLiteral("merge_acceptnew"),m_syncView,SLOT(mergeAcceptAllForEmpty())); action->setText(i18nc("@action:inmenu","Copy all new translations")); action->setStatusTip(i18nc("@info:status","This changes only empty and non-ready entries in base file")); action->setToolTip(action->statusTip()); action->setWhatsThis(action->statusTip()); ac->setDefaultShortcut(action, QKeySequence(Qt::CTRL+Qt::ALT+Qt::Key_A)); connect( m_syncView, &MergeView::mergeCatalogAvailable,action,&QAction::setEnabled); m_syncView->addAction(action); //action->setShortcut(Qt::ALT+Qt::Key_E); action = sync1->addAction(QStringLiteral("merge_back"),m_syncView,SLOT(mergeBack())); action->setText(i18nc("@action:inmenu","Copy to merging source")); connect( m_syncView, &MergeView::mergeCatalogAvailable,action,&QAction::setEnabled); ac->setDefaultShortcut(action, QKeySequence(Qt::CTRL+Qt::ALT+Qt::Key_Return)); m_syncView->addAction(action); //Secondary merge action = sync2->addAction(QStringLiteral("mergesecondary_open"),m_syncViewSecondary,SLOT(mergeOpen())); action->setText(i18nc("@action:inmenu","Open file for secondary sync")); action->setStatusTip(i18nc("@info:status","Open catalog to be merged into the current one / replicate base file changes to")); action->setToolTip(action->statusTip()); action->setWhatsThis(action->statusTip()); m_syncViewSecondary->addAction(action); action = sync2->addAction(QStringLiteral("mergesecondary_prev"),m_syncViewSecondary,SLOT(gotoPrevChanged())); action->setText(i18nc("@action:inmenu","Previous different")); action->setStatusTip(i18nc("@info:status","Previous entry which is translated differently in the file being merged, including empty translations in merge source")); action->setToolTip(action->statusTip()); action->setWhatsThis(action->statusTip()); connect( m_syncView, &MergeView::signalPriorChangedAvailable,action,&QAction::setEnabled); m_syncViewSecondary->addAction(action); action = sync2->addAction(QStringLiteral("mergesecondary_next"),m_syncViewSecondary,SLOT(gotoNextChanged())); action->setText(i18nc("@action:inmenu","Next different")); action->setStatusTip(i18nc("@info:status","Next entry which is translated differently in the file being merged, including empty translations in merge source")); action->setToolTip(action->statusTip()); action->setWhatsThis(action->statusTip()); connect( m_syncView, &MergeView::signalNextChangedAvailable,action,&QAction::setEnabled); m_syncViewSecondary->addAction(action); action = sync2->addAction(QStringLiteral("mergesecondary_accept"),m_syncViewSecondary,SLOT(mergeAccept())); action->setText(i18nc("@action:inmenu","Copy from merging source")); connect( m_syncView, &MergeView::signalEntryWithMergeDisplayed,action,&QAction::setEnabled); m_syncViewSecondary->addAction(action); action = sync2->addAction(QStringLiteral("mergesecondary_acceptnew"),m_syncViewSecondary,SLOT(mergeAcceptAllForEmpty())); action->setText(i18nc("@action:inmenu","Copy all new translations")); action->setStatusTip(i18nc("@info:status","This changes only empty entries")); action->setToolTip(action->statusTip()); action->setWhatsThis(action->statusTip()); m_syncViewSecondary->addAction(action); action = sync2->addAction(QStringLiteral("mergesecondary_back"),m_syncViewSecondary,SLOT(mergeBack())); action->setText(i18nc("@action:inmenu","Copy to merging source")); m_syncViewSecondary->addAction(action); } void EditorTab::setProperFocus() { m_view->setProperFocus(); } void EditorTab::hideDocks() { if (m_transUnitsView->isFloating()) m_transUnitsView->hide(); } void EditorTab::showDocks() { return; if (m_transUnitsView->isFloating()) m_transUnitsView->show(); } void EditorTab::setProperCaption(QString title, bool modified) { if (m_catalog->autoSaveRecovered()) title+=' '+i18nc("editor tab name","(recovered)"); setWindowTitle(title+QStringLiteral(" [*]")); setWindowModified(modified); } void EditorTab::setFullPathShown(bool fullPathShown) { m_fullPathShown=fullPathShown; updateCaptionPath(); setModificationSign(); } void EditorTab::setModificationSign() { bool clean=m_catalog->isClean() && !m_syncView->isModified() && !m_syncViewSecondary->isModified(); setProperCaption(_captionPath,!clean); } void EditorTab::updateCaptionPath() { QString url=m_catalog->url(); if (!m_project->isLoaded()) { _captionPath=url; return; } if (!m_fullPathShown) { _captionPath=QFileInfo(url).fileName(); return; } _captionPath=QDir(QFileInfo(m_project->path()).absolutePath()).relativeFilePath(url); if (_captionPath.contains(QLatin1String("../.."))) _captionPath=url; else if (_captionPath.startsWith(QLatin1String("./"))) _captionPath=_captionPath.mid(2); } bool EditorTab::fileOpen(QString filePath, QString suggestedDirPath, bool silent) { if (!m_catalog->isClean()) { switch (KMessageBox::warningYesNoCancel(SettingsController::instance()->mainWindowPtr(), i18nc("@info","The document contains unsaved changes.\n" "Do you want to save your changes or discard them?"),i18nc("@title:window","Warning"), KStandardGuiItem::save(),KStandardGuiItem::discard()) ) { case KMessageBox::Yes: if (!saveFile()) return false; break; case KMessageBox::Cancel: return false; default:; } } if (suggestedDirPath.isEmpty()) suggestedDirPath=m_catalog->url(); QString saidPath; if (filePath.isEmpty()) { //Prevent crashes //Project::instance()->model()->weaver()->suspend(); //KDE5PORT use mutex if the crash is still there with kfilemetadata library filePath=QFileDialog::getOpenFileName(SettingsController::instance()->mainWindowPtr(), i18nc("@title:window", "Select translation file"), suggestedDirPath, Catalog::supportedFileTypes(true));//" text/x-gettext-translation-template"); //Project::instance()->model()->weaver()->resume(); //TODO application/x-xliff, windows: just extensions //originalPath=url.path(); never used } else if (!QFile::exists(filePath)&&Project::instance()->isLoaded()) { //check if we are opening template QString newPath=filePath; newPath.replace(Project::instance()->poDir(),Project::instance()->potDir()); if (QFile::exists(newPath) || QFile::exists(newPath+='t')) { saidPath=filePath; filePath=newPath; } } if (filePath.isEmpty()) return false; QApplication::setOverrideCursor(Qt::WaitCursor); QString prevFilePath=currentFilePath(); bool wasOpen=!m_catalog->isEmpty(); if (wasOpen) emit fileAboutToBeClosed(); int errorLine=m_catalog->loadFromUrl(filePath,saidPath); if (wasOpen&&errorLine==0) {emit fileClosed();emit fileClosed(prevFilePath);} QApplication::restoreOverrideCursor(); if (errorLine==0) { statusBarItems.insert(ID_STATUS_TOTAL,i18nc("@info:status message entries","Total: %1", m_catalog->numberOfEntries())); numberOfUntranslatedChanged(); numberOfFuzziesChanged(); m_currentPos.entry=-1;//so the signals are emitted DocPosition pos(0); //we delay gotoEntry(pos) until project is loaded; //Project if (!m_project->isLoaded()) { QFileInfo fileInfo(filePath); #ifndef NOKDE //search for it int i=4; QDir dir=fileInfo.dir(); QStringList proj(QStringLiteral("*.lokalize")); dir.setNameFilters(proj); while (--i && !dir.isRoot() && !m_project->isLoaded()) { if (dir.entryList().isEmpty()) {if (!dir.cdUp()) break;} else m_project->load(dir.absoluteFilePath(dir.entryList().first())); } #endif //enforce autosync m_syncViewSecondary->mergeOpen(filePath); if (!m_project->isLoaded()) { if (m_project->desirablePath().isEmpty()) m_project->setDesirablePath(fileInfo.absolutePath()+QStringLiteral("/index.lokalize")); if (m_catalog->targetLangCode().isEmpty() /*&& m_project->targetLangCode().length()*/) m_catalog->setTargetLangCode(getTargetLangCode(fileInfo.fileName())); //_project->setLangCode(m_catalog->targetLangCode()); } } if (m_catalog->targetLangCode().isEmpty() /*&& m_project->targetLangCode().length()*/) m_catalog->setTargetLangCode(Project::instance()->targetLangCode()); gotoEntry(pos); updateCaptionPath(); setModificationSign(); //OK!!! emit xliffFileOpened(m_catalog->type()==Xliff); emit fileOpened(); return true; } if (!silent) { #ifndef NOKDE if (errorLine>0) KMessageBox::error(this, i18nc("@info","Error opening the file %1, line: %2",filePath,errorLine) ); else KMessageBox::error(this, i18nc("@info","Error opening the file %1",filePath) ); #else KMessageBox::error(this, i18nc("@info","Error opening the file") ); #endif } return false; } bool EditorTab::saveFileAs() { QString filePath=QFileDialog::getSaveFileName(this, i18nc("@title:window", "Save File As"), QFileInfo(m_catalog->url()).absoluteFilePath(), m_catalog->fileType()); if (filePath.isEmpty()) return false; if (!Catalog::extIsSupported(filePath)&&m_catalog->url().contains('.')) filePath+=m_catalog->url().midRef(m_catalog->url().lastIndexOf('.')); return saveFile(filePath); } bool EditorTab::saveFile(const QString& filePath) { bool clean=m_catalog->isClean() && !m_syncView->isModified() && !m_syncViewSecondary->isModified(); if (clean) return true; if (m_catalog->isClean() && filePath.isEmpty()) { emit m_catalog->signalFileSaved(); return true; } if (m_catalog->saveToUrl(filePath)) { updateCaptionPath(); setModificationSign(); emit fileSaved(filePath); return true; } #ifndef NOKDE if ( KMessageBox::Continue==KMessageBox::warningContinueCancel(this, i18nc("@info","Error saving the file %1\n" "Do you want to save to another file or cancel?", m_catalog->url()), i18nc("@title","Error"),KStandardGuiItem::save()) ) #else if ( QMessageBox::Yes==QMessageBox::warning(this, QString(), i18nc("@info","Error saving the file %1\n" "Do you want to save to another file or cancel?").arg(m_catalog->url()), QMessageBox::Yes|QMessageBox::No) ) #endif return saveFileAs(); return false; } void EditorTab::fileAutoSaveFailedWarning(const QString& fileName) { KMessageBox::information(this, i18nc("@info","Could not perform file autosaving.\n" "The target file was %1.", fileName) ); } EditorState EditorTab::state() { EditorState state; state.dockWidgets=saveState(); state.filePath=m_catalog->url(); state.mergeFilePath=m_syncView->filePath(); state.entry=m_currentPos.entry; //state.offset=_currentPos.offset; return state; } bool EditorTab::queryClose() { bool clean=m_catalog->isClean() && !m_syncView->isModified() && !m_syncViewSecondary->isModified(); if (clean) return true; //TODO caption switch (KMessageBox::warningYesNoCancel(this, i18nc("@info","The document contains unsaved changes.\n" "Do you want to save your changes or discard them?"),i18nc("@title:window","Warning"), KStandardGuiItem::save(),KStandardGuiItem::discard())) { case KMessageBox::Yes: return saveFile(); case KMessageBox::No: return true; default: return false; } } void EditorTab::undo() { gotoEntry(m_catalog->undo(),0); msgStrChanged(); } void EditorTab::redo() { gotoEntry(m_catalog->redo(),0); msgStrChanged(); } void EditorTab::gotoEntry() { bool ok=false; DocPosition pos=m_currentPos; pos.entry=QInputDialog::getInt(this, i18nc("@title","Jump to Entry"), i18nc("@label:spinbox","Enter entry number:"), pos.entry,1,m_catalog->numberOfEntries(),1, &ok); if (ok) { --(pos.entry); gotoEntry(pos); } } void EditorTab::gotoEntry(DocPosition pos, int selection) { //specially for dbus users if (pos.entry>=m_catalog->numberOfEntries()||pos.entry<0) return; if (!m_catalog->isPlural(pos)) pos.form=0; m_currentPos.part=pos.part;//for searching; //UndefPart => called on fuzzy toggle bool newEntry=m_currentPos.entry!=pos.entry || m_currentPos.form!=pos.form; if (newEntry||pos.part==DocPosition::Comment) { m_notesView->gotoEntry(pos,pos.part==DocPosition::Comment?selection:0); if (pos.part==DocPosition::Comment) { pos.form=0; pos.offset=0; selection=0; } } m_view->gotoEntry(pos,selection); if (pos.part==DocPosition::UndefPart) m_currentPos.part=DocPosition::Target; //QTime time; time.start(); if (newEntry) { m_currentPos=pos; if (true) { emit signalNewEntryDisplayed(pos); emit entryDisplayed(); emit signalFirstDisplayed(pos.entry==m_transUnitsView->firstEntryNumber()); emit signalLastDisplayed(pos.entry==m_transUnitsView->lastEntryNumber()); emit signalPriorFuzzyAvailable(pos.entry>m_catalog->firstFuzzyIndex()); emit signalNextFuzzyAvailable(pos.entrylastFuzzyIndex()); emit signalPriorUntranslatedAvailable(pos.entry>m_catalog->firstUntranslatedIndex()); emit signalNextUntranslatedAvailable(pos.entrylastUntranslatedIndex()); emit signalPriorFuzzyOrUntrAvailable(pos.entry>m_catalog->firstFuzzyIndex() ||pos.entry>m_catalog->firstUntranslatedIndex() ); emit signalNextFuzzyOrUntrAvailable(pos.entrylastFuzzyIndex() ||pos.entrylastUntranslatedIndex()); emit signalPriorBookmarkAvailable(pos.entry>m_catalog->firstBookmarkIndex()); emit signalNextBookmarkAvailable(pos.entrylastBookmarkIndex()); emit signalBookmarkDisplayed(m_catalog->isBookmarked(pos.entry)); emit signalEquivTranslatedEntryDisplayed(m_catalog->isEquivTrans(pos)); emit signalApprovedEntryDisplayed(m_catalog->isApproved(pos)); } } statusBarItems.insert(ID_STATUS_CURRENT,i18nc("@info:status","Current: %1", m_currentPos.entry+1)); //qCDebug(LOKALIZE_LOG)<<"ELA "<msgstr(m_currentPos).isEmpty(); bool isApproved=m_catalog->isApproved(m_currentPos); if (isUntr==m_currentIsUntr && isApproved==m_currentIsApproved) return; QString msg; if (isUntr) msg=i18nc("@info:status","Untranslated"); else if (isApproved)msg=i18nc("@info:status 'non-fuzzy' in gettext terminology","Ready"); else msg=i18nc("@info:status 'fuzzy' in gettext terminology","Needs review"); /* else statusBar()->changeItem("",ID_STATUS_ISFUZZY);*/ statusBarItems.insert(ID_STATUS_ISFUZZY,msg); m_currentIsUntr=isUntr; m_currentIsApproved=isApproved; } void EditorTab::switchForm(int newForm) { if (m_currentPos.form==newForm) return; DocPosition pos=m_currentPos; pos.form=newForm; gotoEntry(pos); } void EditorTab::gotoNextUnfiltered() { DocPosition pos=m_currentPos; if (switchNext(m_catalog,pos)) gotoEntry(pos); } void EditorTab::gotoPrevUnfiltered() { DocPosition pos=m_currentPos; if (switchPrev(m_catalog,pos)) gotoEntry(pos); } void EditorTab::gotoFirstUnfiltered(){gotoEntry(DocPosition(0));} void EditorTab::gotoLastUnfiltered(){gotoEntry(DocPosition(m_catalog->numberOfEntries()-1));} void EditorTab::gotoFirst() { DocPosition pos=DocPosition(m_transUnitsView->firstEntryNumber()); if (pos.entry!=-1) gotoEntry(pos); } void EditorTab::gotoLast() { DocPosition pos=DocPosition(m_transUnitsView->lastEntryNumber()); if (pos.entry!=-1) gotoEntry(pos); } void EditorTab::gotoNext() { DocPosition pos=m_currentPos; if (m_catalog->isPlural(pos) && pos.form+1numberOfPluralForms()) pos.form++; else pos=DocPosition(m_transUnitsView->nextEntryNumber()); if (pos.entry!=-1) gotoEntry(pos); } void EditorTab::gotoPrev() { DocPosition pos=m_currentPos; if (m_catalog->isPlural(pos) && pos.form>0) pos.form--; else pos=DocPosition(m_transUnitsView->prevEntryNumber()); if (pos.entry!=-1) gotoEntry(pos); } void EditorTab::gotoPrevFuzzy() { DocPosition pos; if ( (pos.entry=m_catalog->prevFuzzyIndex(m_currentPos.entry)) == -1) return; gotoEntry(pos); } void EditorTab::gotoNextFuzzy() { DocPosition pos; if ( (pos.entry=m_catalog->nextFuzzyIndex(m_currentPos.entry)) == -1) return; gotoEntry(pos); } void EditorTab::gotoPrevUntranslated() { DocPosition pos; if ( (pos.entry=m_catalog->prevUntranslatedIndex(m_currentPos.entry)) == -1) return; gotoEntry(pos); } void EditorTab::gotoNextUntranslated() { DocPosition pos; if ( (pos.entry=m_catalog->nextUntranslatedIndex(m_currentPos.entry)) == -1) return; gotoEntry(pos); } void EditorTab::gotoPrevFuzzyUntr() { DocPosition pos; short fu = m_catalog->prevFuzzyIndex(m_currentPos.entry); short un = m_catalog->prevUntranslatedIndex(m_currentPos.entry); pos.entry=fu>un?fu:un; if ( pos.entry == -1) return; gotoEntry(pos); } bool EditorTab::gotoNextFuzzyUntr(const DocPosition& p) { int index=(p.entry==-1)?m_currentPos.entry:p.entry; DocPosition pos; short fu = m_catalog->nextFuzzyIndex(index); short un = m_catalog->nextUntranslatedIndex(index); if ( (fu == -1) && (un == -1) ) return false; if (fu == -1) fu=un; else if (un == -1) un=fu; pos.entry=fuisApproved(m_currentPos.entry)) m_view->toggleApprovement(); if (!gotoNextFuzzyUntr()) gotoNextFuzzyUntr(DocPosition(-2));//so that we don't skip the first } void EditorTab::setApproveActionTitle() { const char* const titles[]={ I18N_NOOP2("@option:check trans-unit state","Translated"), I18N_NOOP2("@option:check trans-unit state","Signed-off"), I18N_NOOP2("@option:check trans-unit state","Approved") }; const char* const helpText[]={ I18N_NOOP2("@info:tooltip","Translation is done (although still may need a review)"), I18N_NOOP2("@info:tooltip","Translation has received positive review"), I18N_NOOP2("@info:tooltip","Entry is fully localized (i.e. final)") }; int role=m_catalog->activePhaseRole(); if (role==ProjectLocal::Undefined) role=Project::local()->role(); m_approveAction->setText(i18nc("@option:check trans-unit state",titles[role])); m_approveAction->setToolTip(i18nc("@info:tooltip",helpText[role])); m_approveAndGoAction->setVisible(role==ProjectLocal::Approver); #ifdef NOKDE m_stateAction->setVisible(m_catalog->capabilities()&ExtendedStates); #endif } void EditorTab::showStatesMenu() { m_stateAction->menu()->clear(); if (!(m_catalog->capabilities()&ExtendedStates)) { QAction* a=m_stateAction->menu()->addAction(i18nc("@info:status 'fuzzy' in gettext terminology","Needs review")); a->setData(QVariant(-1)); a->setCheckable(true); a->setChecked(!m_catalog->isApproved(m_currentPos)); a=m_stateAction->menu()->addAction(i18nc("@info:status 'non-fuzzy' in gettext terminology","Ready")); a->setData(QVariant(-2)); a->setCheckable(true); a->setChecked(m_catalog->isApproved(m_currentPos)); return; } TargetState state=m_catalog->state(m_currentPos); const char* const* states=Catalog::states(); for (int i=0;imenu()->addAction(i18n(states[i])); a->setData(QVariant(i)); a->setCheckable(true); a->setChecked(state==i); if (i==New || i==Translated || i==Final) m_stateAction->menu()->addSeparator(); } } void EditorTab::setState(QAction* a) { if (!(m_catalog->capabilities()&ExtendedStates)) m_view->toggleApprovement(); else m_view->setState(TargetState(a->data().toInt())); m_stateAction->menu()->clear(); } void EditorTab::openPhasesWindow() { PhasesWindow w(m_catalog, this); w.exec(); } void EditorTab::gotoPrevBookmark() { DocPosition pos; if ( (pos.entry=m_catalog->prevBookmarkIndex(m_currentPos.entry)) == -1) return; gotoEntry(pos); } void EditorTab::gotoNextBookmark() { DocPosition pos; if ( (pos.entry=m_catalog->nextBookmarkIndex(m_currentPos.entry)) == -1) return; gotoEntry(pos); } //wrapper for cmdline handling... void EditorTab::mergeOpen(QString mergeFilePath) { m_syncView->mergeOpen(mergeFilePath); } //HACK to prevent redundant repaintings when widget isn't visible void EditorTab::paintEvent(QPaintEvent* event) { if (QMdiSubWindow* sw=qobject_cast(parent())) { if (sw->mdiArea()->currentSubWindow()!=sw) return; } LokalizeSubwindowBase2::paintEvent(event); } void EditorTab::indexWordsForCompletion() { CompletionStorage::instance()->scanCatalog(m_catalog); } void EditorTab::displayWordCount() { //TODO in trans and fuzzy separately int sourceCount=0; int targetCount=0; QRegExp rxClean(Project::instance()->markup()%'|'%Project::instance()->accel());//cleaning regexp; NOTE isEmpty()? QRegExp rxSplit(QStringLiteral("\\W|\\d"));//splitting regexp DocPosition pos(0); do { QString msg=m_catalog->source(pos); msg.remove(rxClean); QStringList words=msg.split(rxSplit,QString::SkipEmptyParts); sourceCount+=words.size(); msg=m_catalog->target(pos); msg.remove(rxClean); words=msg.split(rxSplit,QString::SkipEmptyParts); targetCount+=words.size(); } while (switchNext(m_catalog,pos)); KMessageBox::information(this, i18nc("@info words count", "Source text words: %1
Target text words: %2", sourceCount,targetCount),i18nc("@title","Word Count")); } bool EditorTab::findEntryBySourceContext(const QString& source, const QString& ctxt) { DocPosition pos(0); do { if (m_catalog->source(pos)==source && m_catalog->context(pos.entry)==QStringList(ctxt)) { gotoEntry(pos); return true; } } while (switchNext(m_catalog,pos)); return false; } //see also termlabel.h void EditorTab::defineNewTerm() { //TODO just a word under cursor? QString en(m_view->selectionInSource().toLower()); if (en.isEmpty()) en=m_catalog->msgid(m_currentPos).toLower(); QString target(m_view->selectionInTarget().toLower()); if (target.isEmpty()) target=m_catalog->msgstr(m_currentPos).toLower(); m_project->defineNewTerm(en,target); } void EditorTab::reloadFile() { QString mergeFilePath=m_syncView->filePath(); DocPosition p=m_currentPos; if (!fileOpen(currentFilePath())) return; gotoEntry(p); if (!mergeFilePath.isEmpty()) mergeOpen(mergeFilePath); } static void openLxrSearch(const QString& srcFileRelPath) { QDesktopServices::openUrl(QUrl(QStringLiteral("https://lxr.kde.org/search?_filestring=") + QString::fromLatin1(QUrl::toPercentEncoding(srcFileRelPath)))); } void EditorTab::dispatchSrcFileOpenRequest(const QString& srcFileRelPath, int line) { // Try project scripts first. m_srcFileOpenRequestAccepted = false; emit srcFileOpenRequested(srcFileRelPath, line); if (m_srcFileOpenRequestAccepted) return; // If project scripts do not handle opening the source file, check if the // path exists relative to the current translation file path. QDir relativePath(currentFilePath()); relativePath.cdUp(); QString srcAbsolutePath(relativePath.absoluteFilePath(srcFileRelPath)); if (QFile::exists(srcAbsolutePath)) { QDesktopServices::openUrl(QUrl::fromLocalFile(srcAbsolutePath)); return; } QString dir = Project::instance()->local()->sourceDir(); if (dir.isEmpty()) { switch (KMessageBox::questionYesNoCancel(SettingsController::instance()->mainWindowPtr(), i18nc("@info","Would you like to search for the source file locally or via lxr.kde.org?"),i18nc("@title:window","Source file lookup"), KGuiItem(i18n("Locally")), KGuiItem("lxr.kde.org") )) { case KMessageBox::Yes: break; case KMessageBox::No: openLxrSearch(srcFileRelPath); case KMessageBox::Cancel: default: return; } } //hard fallback if (dir.isEmpty()) { dir = QFileDialog::getExistingDirectory(this, i18n("Select project's base folder for source file lookup"), QDir::homePath()); if (dir.length()) Project::instance()->local()->setSourceDir(dir); } if (dir.length()) { auto doOpen = [srcFileRelPath]() { auto sourceFilePaths = Project::instance()->sourceFilePaths(); bool found = false; QByteArray fn = srcFileRelPath.midRef(srcFileRelPath.lastIndexOf('/')+1).toUtf8(); auto it=sourceFilePaths.constFind(fn); while (it!=sourceFilePaths.constEnd() && it.key() == fn) { const QString absFilePath = QString::fromUtf8(it.value()+'/'+fn); if (!absFilePath.endsWith(srcFileRelPath) || !QFileInfo::exists(absFilePath) ) //for the case when link contained also folders { it++; continue; } found = true; QDesktopServices::openUrl(QUrl::fromLocalFile(absFilePath)); it++; } if (!found) { switch (KMessageBox::warningYesNoCancel(SettingsController::instance()->mainWindowPtr(), i18nc("@info","Could not find source file in the folder specified.\n" "Do you want to change source files folder?"),i18nc("@title:window","Source file lookup"), KStandardGuiItem::yes(), KStandardGuiItem::no(), KGuiItem(i18n("lxr.kde.org")))) { case KMessageBox::Cancel: Project::instance()->local()->setSourceDir(QString()); Project::instance()->resetSourceFilePaths(); openLxrSearch(srcFileRelPath); case KMessageBox::No: return; default: ; //fall through to dir selection } QString dir = QFileDialog::getExistingDirectory(0, i18n("Select project's base folder for source file lookup"), Project::instance()->local()->sourceDir()); if (dir.length()) { Project::instance()->local()->setSourceDir(dir); Project::instance()->resetSourceFilePaths(); } } }; if (!Project::instance()->sourceFilePaths().isEmpty()) doOpen(); else connect(Project::instance(), &Project::sourceFilePathsAreReady, doOpen); return; } // Otherwise, let the user know how to create a project script to handle // opening source file paths that are not relative to translation files. KMessageBox::information(this, i18nc("@info", "Cannot open the target source file: The target source file is not " "relative to the current translation file, and there are currently no " "scripts loaded to handle opening source files in custom paths. Refer " "to the Lokalize handbook for script examples and how to plug them " "into your project.")); } void EditorTab::mergeIntoOpenDocument() { if (!m_catalog || m_catalog->type()!=Xliff) return; QString xliff2odf=QStringLiteral("xliff2odf"); if (QProcess::execute(xliff2odf, QStringList(QLatin1String("--version")))==-2) { KMessageBox::error(SettingsController::instance()->mainWindowPtr(), i18n("Install translate-toolkit package and retry.")); return; } QString xliffFolder=QFileInfo(m_catalog->url()).absolutePath(); QString originalOdfFilePath=m_catalog->originalOdfFilePath(); if (originalOdfFilePath.isEmpty() || !QFile::exists(originalOdfFilePath)) { originalOdfFilePath=QFileDialog::getOpenFileName( SettingsController::instance()->mainWindowPtr(), i18n("Select original OpenDocument on which current XLIFF file is based"), xliffFolder, i18n("OpenDocument files (*.odt *.ods)")/*"text/x-lokalize-project"*/); if (originalOdfFilePath.length()) m_catalog->setOriginalOdfFilePath(originalOdfFilePath); } if (originalOdfFilePath.isEmpty()) return; saveFile(); //TODO check if odt did update (merge with new template is needed) QFileInfo originalOdfFileInfo(originalOdfFilePath); QString targetLangCode=m_catalog->targetLangCode(); QStringList args(m_catalog->url()); args.append(xliffFolder%'/'%originalOdfFileInfo.baseName()%'-'%targetLangCode%'.'%originalOdfFileInfo.suffix()); args.append(QStringLiteral("-t")); args.append(originalOdfFilePath); qCDebug(LOKALIZE_LOG)<mainWindowPtr(), i18n("Install translate-toolkit package and retry")); return; } QProcess::startDetached(lowriter, QStringList(args.at(1))); QString reloaderScript=QStandardPaths::locate(QStandardPaths::DataLocation, QStringLiteral("scripts/odf/xliff2odf-standalone.py")); if (reloaderScript.length()) { QString python=QStringLiteral("python"); QStringList unoArgs(QStringLiteral("-c")); unoArgs.append(QStringLiteral("import uno")); if (QProcess::execute(python, unoArgs)!=0) { python=QStringLiteral("python3"); QStringList unoArgs(QStringLiteral("-c")); unoArgs.append(QStringLiteral("import uno")); if (QProcess::execute(python, unoArgs)!=0) { KMessageBox::information(SettingsController::instance()->mainWindowPtr(), i18n("Install python-uno package for additional functionality.")); return; } } QStringList reloaderArgs(reloaderScript); reloaderArgs.append(args.at(1)); reloaderArgs.append(currentEntryId()); QProcess::execute(python, reloaderArgs); } } } //BEGIN DBus interface #ifndef NOKDE #include "editoradaptor.h" QList EditorTab::ids; QString EditorTab::dbusObjectPath() { const QString EDITOR_PATH=QStringLiteral("/ThisIsWhatYouWant/Editor/"); if ( m_dbusId==-1 ) { m_adaptor=new EditorAdaptor(this); int i=0; while(iurl();} QByteArray EditorTab::currentFileContents(){return m_catalog->contents();} QString EditorTab::currentEntryId(){return m_catalog->id(m_currentPos);} QString EditorTab::selectionInTarget(){return m_view->selectionInTarget();} QString EditorTab::selectionInSource(){return m_view->selectionInSource();} void EditorTab::lookupSelectionInTranslationMemory(){emit tmLookupRequested(selectionInSource(),selectionInTarget());} void EditorTab::setEntryFilteredOut(int entry, bool filteredOut){m_transUnitsView->setEntryFilteredOut(entry, filteredOut);} void EditorTab::setEntriesFilteredOut(bool filteredOut){m_transUnitsView->setEntriesFilteredOut(filteredOut);} int EditorTab::entryCount(){return m_catalog->numberOfEntries();} QString EditorTab::entrySource(int entry, int form){return m_catalog->sourceWithTags(DocPosition(entry, form)).string;} QString EditorTab::entryTarget(int entry, int form){return m_catalog->targetWithTags(DocPosition(entry, form)).string;} int EditorTab::entryPluralFormCount(int entry){return m_catalog->isPlural(entry)?m_catalog->numberOfPluralForms():1;} bool EditorTab::entryReady(int entry){return m_catalog->isApproved(entry);} QString EditorTab::sourceLangCode(){return m_catalog->sourceLangCode();} QString EditorTab::targetLangCode(){return m_catalog->targetLangCode();} void EditorTab::addEntryNote(int entry, const QString& note){m_notesView->addNote(entry, note);} void EditorTab::addTemporaryEntryNote(int entry, const QString& note){m_notesView->addTemporaryEntryNote(entry, note);} void EditorTab::addAlternateTranslation(int entry, const QString& translation){m_altTransView->addAlternateTranslation(entry, translation);} void EditorTab::addTemporaryAlternateTranslation(int entry, const QString& translation){m_altTransView->addAlternateTranslation(entry, translation);} void EditorTab::attachAlternateTranslationFile(const QString& path){m_altTransView->attachAltTransFile(path);} void EditorTab::setEntryTarget(int entry, int form, const QString& content) { DocPosition pos(entry,form); m_catalog->beginMacro(i18nc("@item Undo action item","Set unit text")); removeTargetSubstring(m_catalog, pos); insertCatalogString(m_catalog, pos, CatalogString(content)); m_catalog->endMacro(); if (m_currentPos==pos) m_view->gotoEntry(); } //END DBus interface diff --git a/src/editortab_findreplace.cpp b/src/editortab_findreplace.cpp index 062fbc9..c7e4d20 100644 --- a/src/editortab_findreplace.cpp +++ b/src/editortab_findreplace.cpp @@ -1,664 +1,663 @@ /* **************************************************************************** This file is part of Lokalize Copyright (C) 2007-2009 by Nick Shaforostoff 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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, see . **************************************************************************** */ #include "lokalize_debug.h" #include "editortab.h" #include "editorview.h" #include "catalog.h" #include "pos.h" #include "cmd.h" #include "project.h" #include "prefs_lokalize.h" #include "ui_kaider_findextension.h" #include "stemming.h" #include #include #include #include -#include #include #include #include #include #define IGNOREACCELS KFind::MinimumUserOption #define INCLUDENOTES KFind::MinimumUserOption*2 static long makeOptions(long options, const Ui_findExtension* ui_findExtension) { return options +IGNOREACCELS*ui_findExtension->m_ignoreAccelMarks->isChecked() +INCLUDENOTES*ui_findExtension->m_notes->isChecked(); //bool skipMarkup(){return ui_findExtension->m_skipTags->isChecked();} } class EntryFindDialog: public KFindDialog { public: EntryFindDialog(QWidget* parent); ~EntryFindDialog(); long options() const{return makeOptions(KFindDialog::options(),ui_findExtension);} static EntryFindDialog* instance(QWidget* parent=0); private: static QPointer _instance; static void cleanup(){delete EntryFindDialog::_instance;} private: Ui_findExtension* ui_findExtension; }; QPointer EntryFindDialog::_instance=0; EntryFindDialog* EntryFindDialog::instance(QWidget* parent) { if (_instance==0 ) { _instance=new EntryFindDialog(parent); qAddPostRoutine(EntryFindDialog::cleanup); } return _instance; } EntryFindDialog::EntryFindDialog(QWidget* parent) : KFindDialog(parent) , ui_findExtension(new Ui_findExtension) { ui_findExtension->setupUi(findExtension()); setHasSelection(false); KConfig config; KConfigGroup stateGroup(&config,"FindReplace"); setOptions(stateGroup.readEntry("FindOptions",(qlonglong)0)); setFindHistory(stateGroup.readEntry("FindHistory",QStringList())); } EntryFindDialog::~EntryFindDialog() { KConfig config; KConfigGroup stateGroup(&config,"FindReplace"); stateGroup.writeEntry("FindOptions",(qlonglong)options()); stateGroup.writeEntry("FindHistory",findHistory()); delete ui_findExtension; } //BEGIN EntryReplaceDialog class EntryReplaceDialog: public KReplaceDialog { public: EntryReplaceDialog(QWidget* parent); ~EntryReplaceDialog(); long options() const{return makeOptions(KReplaceDialog::options(),ui_findExtension);} static EntryReplaceDialog* instance(QWidget* parent=0); private: static QPointer _instance; static void cleanup(){delete EntryReplaceDialog::_instance;} private: Ui_findExtension* ui_findExtension; }; QPointer EntryReplaceDialog::_instance=0; EntryReplaceDialog* EntryReplaceDialog::instance(QWidget* parent) { if (_instance==0 ) { _instance=new EntryReplaceDialog(parent); qAddPostRoutine(EntryReplaceDialog::cleanup); } return _instance; } EntryReplaceDialog::EntryReplaceDialog(QWidget* parent) : KReplaceDialog(parent) , ui_findExtension(new Ui_findExtension) { ui_findExtension->setupUi(findExtension()); //ui_findExtension->m_notes->hide(); setHasSelection(false); KConfig config; KConfigGroup stateGroup(&config,"FindReplace"); setOptions(stateGroup.readEntry("ReplaceOptions",(qlonglong)0)); setFindHistory(stateGroup.readEntry("ReplacePatternHistory",QStringList())); setReplacementHistory(stateGroup.readEntry("ReplacementHistory",QStringList())); } EntryReplaceDialog::~EntryReplaceDialog() { KConfig config; KConfigGroup stateGroup(&config,"FindReplace"); stateGroup.writeEntry("ReplaceOptions",(qlonglong)options()); stateGroup.writeEntry("ReplacePatternHistory",findHistory()); stateGroup.writeEntry("ReplacementHistory",replacementHistory()); delete ui_findExtension; } //END EntryReplaceDialog //TODO &,   static void calcOffsetWithAccels(const QString& data, int& offset, int& length) { int i=0; for (;ioptions() & KFind::FindBackwards) { pos.entry=m_catalog->numberOfEntries()-1; pos.form=(m_catalog->isPlural(pos.entry))? m_catalog->numberOfPluralForms()-1:0; } else { pos.entry=0; pos.form=0; } return true; } void EditorTab::find() { //QWidget* p=0; QWidget* next=qobject_cast(parent()); while(next) { p=next; next=qobject_cast(next->parent()); } EntryFindDialog::instance(nativeParentWidget()); QString sel=selectionInTarget(); if (!(sel.isEmpty()&&selectionInSource().isEmpty())) { if (sel.isEmpty()) sel=selectionInSource(); if (m_find&&m_find->options()&IGNOREACCELS) sel.remove('&'); EntryFindDialog::instance()->setPattern(sel); } if ( EntryFindDialog::instance()->exec() != QDialog::Accepted ) return; if (m_find) { m_find->resetCounts(); m_find->setPattern(EntryFindDialog::instance()->pattern()); m_find->setOptions(EntryFindDialog::instance()->options()); } else // This creates a find-next-prompt dialog if needed. { m_find = new KFind(EntryFindDialog::instance()->pattern(),EntryFindDialog::instance()->options(),this,EntryFindDialog::instance()); connect(m_find,SIGNAL(highlight(QString,int,int)),this, SLOT(highlightFound(QString,int,int)) ); connect(m_find,SIGNAL(findNext()),this,SLOT(findNext())); m_find->closeFindNextDialog(); } DocPosition pos; if (m_find->options() & KFind::FromCursor) pos=m_currentPos; else if (!determineStartingPos(m_catalog, m_find, pos)) return; findNext(pos); } void EditorTab::findNext(const DocPosition& startingPos) { Catalog& catalog=*m_catalog; KFind& find=*m_find; - if (KDE_ISUNLIKELY( catalog.numberOfEntries()<=startingPos.entry )) + if (Q_UNLIKELY( catalog.numberOfEntries()<=startingPos.entry )) return;//for the case when app wasn't able to process event before file close bool anotherEntry=_searchingPos.entry!=m_currentPos.entry; _searchingPos=startingPos; if (anotherEntry) _searchingPos.offset=0; QRegExp rx("[^(\\\\n)>]\n"); QTime a;a.start(); //_searchingPos.part=DocPosition::Source; bool ignoreaccels=m_find->options()&IGNOREACCELS; bool includenotes=m_find->options()&INCLUDENOTES; int switchOptions=DocPosition::Source|DocPosition::Target|(includenotes*DocPosition::Comment); int flag=1; while (flag) { flag=0; KFind::Result res = KFind::NoMatch; while (true) { if (find.needData()||anotherEntry||m_view->m_modifiedAfterFind) { anotherEntry=false; m_view->m_modifiedAfterFind=false; QString data; if (_searchingPos.part==DocPosition::Comment) data=catalog.notes(_searchingPos).at(_searchingPos.form).content; else data=catalog.catalogString(_searchingPos).string; if (ignoreaccels) data.remove('&'); find.setData(data); } res = find.find(); //offset=-1; if (res!=KFind::NoMatch) break; if (!( (find.options()&KFind::FindBackwards)? switchPrev(m_catalog,_searchingPos,switchOptions): switchNext(m_catalog,_searchingPos,switchOptions) )) break; } if (res==KFind::NoMatch) { //file-wide search if(find.shouldRestart(true,true)) { flag=1; determineStartingPos(m_catalog, m_find,_searchingPos); } find.resetCounts(); } } } void EditorTab::findNext() { if (m_find) { findNext((m_currentPos.entry==_searchingPos.entry&&_searchingPos.part==DocPosition::Comment)? _searchingPos:m_currentPos); } else find(); } void EditorTab::findPrev() { if (m_find) { m_find->setOptions(m_find->options() ^ KFind::FindBackwards); findNext(m_currentPos); } else { find(); } } void EditorTab::highlightFound(const QString &,int matchingIndex,int matchedLength) { if (m_find->options()&IGNOREACCELS && _searchingPos.part!=DocPosition::Comment) { QString data=m_catalog->catalogString(_searchingPos).string; calcOffsetWithAccels(data, matchingIndex, matchedLength); } _searchingPos.offset=matchingIndex; gotoEntry(_searchingPos,matchedLength); } void EditorTab::replace() { EntryReplaceDialog::instance(nativeParentWidget()); if (!m_view->selectionInTarget().isEmpty()) { if (m_replace&&m_replace->options()&IGNOREACCELS) { QString tmp(m_view->selectionInTarget()); tmp.remove('&'); EntryReplaceDialog::instance()->setPattern(tmp); } else EntryReplaceDialog::instance()->setPattern(m_view->selectionInTarget()); } if ( EntryReplaceDialog::instance()->exec() != QDialog::Accepted ) return; if (m_replace) m_replace->deleteLater();// _replace=0; // This creates a find-next-prompt dialog if needed. { m_replace = new KReplace(EntryReplaceDialog::instance()->pattern(),EntryReplaceDialog::instance()->replacement(),EntryReplaceDialog::instance()->options(),this,EntryReplaceDialog::instance()); connect(m_replace,SIGNAL(highlight(QString,int,int)), this,SLOT(highlightFound_(QString,int,int))); connect(m_replace,SIGNAL(findNext()), this,SLOT(replaceNext())); connect(m_replace,SIGNAL(replace(QString,int,int,int)), this,SLOT(doReplace(QString,int,int,int))); connect(m_replace,SIGNAL(dialogClosed()), this,SLOT(cleanupReplace())); // _replace->closeReplaceNextDialog(); } // else // { // _replace->resetCounts(); // _replace->setPattern(EntryReplaceDialog::instance()->pattern()); // _replace->setOptions(EntryReplaceDialog::instance()->options()); // } //m_catalog->beginMacro(i18nc("@item Undo action item","Replace")); m_doReplaceCalled=false; if (m_replace->options() & KFind::FromCursor) replaceNext(m_currentPos); else { DocPosition pos; if (!determineStartingPos(m_catalog, m_replace,pos)) return; replaceNext(pos); } } void EditorTab::replaceNext(const DocPosition& startingPos) { bool anotherEntry=m_currentPos.entry!=_replacingPos.entry; _replacingPos=startingPos; if (anotherEntry) _replacingPos.offset=0; int flag=1; bool ignoreaccels=m_replace->options()&IGNOREACCELS; bool includenotes=m_replace->options()&INCLUDENOTES; qCWarning(LOKALIZE_LOG)<<"includenotes"<needData()||anotherEntry/*||m_view->m_modifiedAfterFind*/) { anotherEntry=false; //m_view->m_modifiedAfterFind=false;//NOTE TEST THIS QString data; if (_replacingPos.part==DocPosition::Comment) data=m_catalog->notes(_replacingPos).at(_replacingPos.form).content; else { data=m_catalog->targetWithTags(_replacingPos).string; if (ignoreaccels) data.remove('&'); } m_replace->setData(data); } res = m_replace->replace(); if (res!=KFind::NoMatch) break; if (!( (m_replace->options()&KFind::FindBackwards)? switchPrev(m_catalog,_replacingPos,switchOptions): switchNext(m_catalog,_replacingPos,switchOptions) )) break; } if (res==KFind::NoMatch) { if((m_replace->options()&KFind::FromCursor) &&m_replace->shouldRestart(true)) { flag=1; determineStartingPos(m_catalog, m_replace,_replacingPos); } else { if(!(m_replace->options() & KFind::FromCursor)) m_replace->displayFinalDialog(); m_replace->closeReplaceNextDialog(); cleanupReplace(); } m_replace->resetCounts(); } } } void EditorTab::cleanupReplace() { if(m_doReplaceCalled) { m_doReplaceCalled=false; m_catalog->endMacro(); } } void EditorTab::replaceNext() { replaceNext(m_currentPos); } void EditorTab::highlightFound_(const QString &,int matchingIndex,int matchedLength) { if (m_replace->options()&IGNOREACCELS) { QString data=m_catalog->targetWithTags(_replacingPos).string; calcOffsetWithAccels(data,matchingIndex,matchedLength); } _replacingPos.offset=matchingIndex; gotoEntry(_replacingPos,matchedLength); } void EditorTab::doReplace(const QString &newStr,int offset,int newLen,int remLen) { if(!m_doReplaceCalled) { m_doReplaceCalled=true; m_catalog->beginMacro(i18nc("@item Undo action item","Replace")); } DocPosition pos=_replacingPos; if (_replacingPos.part==DocPosition::Comment) m_catalog->push(new SetNoteCmd(m_catalog,pos,newStr)); else { QString oldStr=m_catalog->target(_replacingPos); if (m_replace->options()&IGNOREACCELS) calcOffsetWithAccels(oldStr,offset,remLen); pos.offset=offset; m_catalog->push(new DelTextCmd(m_catalog,pos,oldStr.mid(offset,remLen))); if (newLen) m_catalog->push(new InsTextCmd(m_catalog,pos,newStr.mid(offset,newLen))); } if (pos.entry==m_currentPos.entry) { pos.offset+=newLen; m_view->gotoEntry(pos); } } void EditorTab::spellcheck() { if (!m_sonnetDialog) { m_sonnetChecker=new Sonnet::BackgroundChecker(this); m_sonnetChecker->changeLanguage(enhanceLangCode(Project::instance()->langCode())); m_sonnetDialog=new Sonnet::Dialog(m_sonnetChecker,this); connect(m_sonnetDialog,SIGNAL(done(QString)),this,SLOT(spellcheckNext())); connect(m_sonnetDialog,SIGNAL(replace(QString,int,QString)), this,SLOT(spellcheckReplace(QString,int,QString))); connect(m_sonnetDialog,SIGNAL(stop()),this,SLOT(spellcheckStop())); connect(m_sonnetDialog,SIGNAL(cancel()),this,SLOT(spellcheckCancel())); connect(m_sonnetDialog/*m_sonnetChecker*/,SIGNAL(misspelling(QString,int)), this,SLOT(spellcheckShow(QString,int))); // disconnect(/*m_sonnetDialog*/m_sonnetChecker,SIGNAL(misspelling(QString,int)), // m_sonnetDialog,SLOT(slotMisspelling(QString,int))); // // connect( d->checker, SIGNAL(misspelling(const QString&, int)), // SLOT(slotMisspelling(const QString&, int)) ); } QString text=m_catalog->msgstr(m_currentPos); if (!m_view->selectionInTarget().isEmpty()) text=m_view->selectionInTarget(); text.remove('&'); m_sonnetDialog->setBuffer(text); _spellcheckPos=m_currentPos; _spellcheckStartPos=m_currentPos; m_spellcheckStop=false; //m_catalog->beginMacro(i18n("Spellcheck")); m_spellcheckStartUndoIndex=m_catalog->index(); m_sonnetDialog->show(); } void EditorTab::spellcheckNext() { if (m_spellcheckStop) return; do { if (!switchNext(m_catalog,_spellcheckPos)) { qCWarning(LOKALIZE_LOG)<<_spellcheckStartPos.entry; qCWarning(LOKALIZE_LOG)<<_spellcheckStartPos.form; bool continueFromStart= !(_spellcheckStartPos.entry==0 && _spellcheckStartPos.form==0) && KMessageBox::questionYesNo(this,i18n("Lokalize has reached end of document. Do you want to continue from start?"), i18nc("@title", "Spellcheck"))==KMessageBox::Yes; if (continueFromStart) { _spellcheckStartPos.entry=0; _spellcheckStartPos.form=0; _spellcheckPos=_spellcheckStartPos; } else { KMessageBox::information(this,i18n("Lokalize has finished spellchecking"), i18nc("@title", "Spellcheck")); return; } } } while (m_catalog->msgstr(_spellcheckPos).isEmpty() || !m_catalog->isApproved(_spellcheckPos.entry)); m_sonnetDialog->setBuffer(m_catalog->msgstr(_spellcheckPos).remove(Project::instance()->accel())); } void EditorTab::spellcheckStop() { m_spellcheckStop=true; } void EditorTab::spellcheckCancel() { m_catalog->setIndex(m_spellcheckStartUndoIndex); gotoEntry(_spellcheckPos); } void EditorTab::spellcheckShow(const QString &word, int offset) { const Project& project = *Project::instance(); const QString accel = project.accel(); QString source=m_catalog->source(_spellcheckPos); source.remove(accel); if (source.contains(word) && project.targetLangCode().leftRef(2) != project.sourceLangCode().leftRef(2)) { m_sonnetDialog->setUpdatesEnabled(false); m_sonnetChecker->continueChecking(); return; } m_sonnetDialog->setUpdatesEnabled(true); show(); DocPosition pos=_spellcheckPos; int length=word.length(); calcOffsetWithAccels(m_catalog->target(pos),offset,length); pos.offset=offset; gotoEntry(pos,length); } void EditorTab::spellcheckReplace(QString oldWord, int offset, const QString &newWord) { DocPosition pos=_spellcheckPos; int length=oldWord.length(); calcOffsetWithAccels(m_catalog->target(pos),offset,length); pos.offset=offset; if (length>oldWord.length())//replaced word contains accel mark oldWord=m_catalog->target(pos).mid(offset,length); m_catalog->push(new DelTextCmd(m_catalog,pos,oldWord)); m_catalog->push(new InsTextCmd(m_catalog,pos,newWord)); gotoEntry(pos,newWord.length()); } diff --git a/src/editorview.cpp b/src/editorview.cpp index fac3dd9..075571f 100644 --- a/src/editorview.cpp +++ b/src/editorview.cpp @@ -1,361 +1,359 @@ /* **************************************************************************** This file is part of Lokalize (some bits of KBabel code were reused) Copyright (C) 2007-2014 by Nick Shaforostoff Copyright (C) 1999-2000 by Matthias Kiefer 2001-2004 by Stanislav Visnovsky 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 "editorview.h" #include "lokalize_debug.h" #include "xlifftextedit.h" #include "project.h" #include "catalog.h" #include "cmd.h" #include "prefs_lokalize.h" #include "prefs.h" -#include "kdemacros.h" - #include #include #include #include #include #include #include #include #include #ifndef NOKDE #include #include #include #endif //parent is set on qsplitter insertion #ifndef NOKDE LedsWidget::LedsWidget(QWidget* parent): QWidget(parent) { KColorScheme colorScheme(QPalette::Normal); QHBoxLayout* layout=new QHBoxLayout(this); layout->addStretch(); layout->addWidget(new QLabel(i18nc("@label whether entry is fuzzy","Not ready:"),this)); layout->addWidget(ledFuzzy=new KLed(colorScheme.foreground(KColorScheme::NeutralText).color()/*Qt::green*/,KLed::Off,KLed::Sunken,KLed::Rectangular)); layout->addWidget(new QLabel(i18nc("@label whether entry is untranslated","Untranslated:"),this)); layout->addWidget(ledUntr=new KLed(colorScheme.foreground(KColorScheme::NegativeText).color()/*Qt::red*/,KLed::Off,KLed::Sunken,KLed::Rectangular)); layout->addSpacing(1); layout->addWidget(lblColumn=new QLabel(this)); layout->addStretch(); setMaximumHeight(minimumSizeHint().height()); } void LedsWidget::contextMenuEvent(QContextMenuEvent* event) { QMenu menu; menu.addAction(i18nc("@action","Hide")); if (!menu.exec(event->globalPos())) return; //NOTE the config doesn't seem to work Settings::setLeds(false); SettingsController::instance()->dirty=true; hide(); } void LedsWidget::cursorPositionChanged(int column) { lblColumn->setText(i18nc("@info:label cursor position", "Column: %1", column)); } #endif EditorView::EditorView(QWidget *parent,Catalog* catalog/*,keyEventHandler* kh*/) : QSplitter(Qt::Vertical,parent) , m_catalog(catalog) , m_sourceTextEdit(new TranslationUnitTextEdit(catalog,DocPosition::Source,this)) , m_targetTextEdit(new TranslationUnitTextEdit(catalog,DocPosition::Target,this)) , m_pluralTabBar(new QTabBar(this)) #ifndef NOKDE , m_leds(0) #endif , m_modifiedAfterFind(false) { m_pluralTabBar->hide(); m_sourceTextEdit->setWhatsThis(i18n("

Original String

\n" "

This part of the window shows the original message\n" "of the currently displayed entry.

")); m_sourceTextEdit->viewport()->setBackgroundRole(QPalette::Background); connect (m_targetTextEdit, SIGNAL(contentsModified(DocPosition)), this, SLOT(resetFindForCurrent(DocPosition))); connect (m_targetTextEdit, SIGNAL(toggleApprovementRequested()), this, SLOT(toggleApprovement())); connect (this, SIGNAL(signalApprovedEntryDisplayed(bool)), m_targetTextEdit, SLOT(reflectApprovementState())); connect (m_sourceTextEdit, SIGNAL(tagInsertRequested(InlineTag)), m_targetTextEdit, SLOT(insertTag(InlineTag))); connect (m_sourceTextEdit, SIGNAL(binaryUnitSelectRequested(QString)), this, SIGNAL(binaryUnitSelectRequested(QString))); connect (m_targetTextEdit, SIGNAL(binaryUnitSelectRequested(QString)), this, SIGNAL(binaryUnitSelectRequested(QString))); connect (m_sourceTextEdit, SIGNAL(gotoEntryRequested(DocPosition)), this, SIGNAL(gotoEntryRequested(DocPosition))); connect (m_targetTextEdit, SIGNAL(gotoEntryRequested(DocPosition)), this, SIGNAL(gotoEntryRequested(DocPosition))); connect (m_sourceTextEdit, SIGNAL(tmLookupRequested(DocPosition::Part,QString)), this, SIGNAL(tmLookupRequested(DocPosition::Part,QString))); connect (m_targetTextEdit, SIGNAL(tmLookupRequested(DocPosition::Part,QString)), this, SIGNAL(tmLookupRequested(DocPosition::Part,QString))); connect (m_sourceTextEdit, SIGNAL(findRequested()), this, SIGNAL(findRequested())); connect (m_targetTextEdit, SIGNAL(findRequested()), this, SIGNAL(findRequested())); connect (m_sourceTextEdit, SIGNAL(findNextRequested()), this, SIGNAL(findNextRequested())); connect (m_targetTextEdit, SIGNAL(findNextRequested()), this, SIGNAL(findNextRequested())); connect (m_sourceTextEdit, SIGNAL(replaceRequested()), this, SIGNAL(replaceRequested())); connect (m_targetTextEdit, SIGNAL(replaceRequested()), this, SIGNAL(replaceRequested())); connect (this, SIGNAL(doExplicitCompletion()), m_targetTextEdit, SLOT(doExplicitCompletion())); addWidget(m_pluralTabBar); addWidget(m_sourceTextEdit); addWidget(m_targetTextEdit); QWidget::setTabOrder(m_targetTextEdit,m_sourceTextEdit); QWidget::setTabOrder(m_sourceTextEdit,m_targetTextEdit); setFocusProxy(m_targetTextEdit); // QTimer::singleShot(3000,this,SLOT(setupWhatsThis())); settingsChanged(); } EditorView::~EditorView() { } void EditorView::resetFindForCurrent(const DocPosition& pos) { m_modifiedAfterFind=true; emit signalChanged(pos.entry); } void EditorView::settingsChanged() { //Settings::self()->config()->setGroup("Editor"); m_sourceTextEdit->document()->setDefaultFont(Settings::msgFont()); m_targetTextEdit->document()->setDefaultFont(Settings::msgFont()); #ifndef NOKDE if (m_leds) m_leds->setVisible(Settings::leds()); else if (Settings::leds()) { m_leds=new LedsWidget(this); insertWidget(2,m_leds); connect (m_targetTextEdit, SIGNAL(cursorPositionChanged(int)), m_leds, SLOT(cursorPositionChanged(int))); connect (m_targetTextEdit, SIGNAL(nonApprovedEntryDisplayed()),m_leds->ledFuzzy, SLOT(on())); connect (m_targetTextEdit, SIGNAL(approvedEntryDisplayed()), m_leds->ledFuzzy, SLOT(off())); connect (m_targetTextEdit, SIGNAL(untranslatedEntryDisplayed()),m_leds->ledUntr, SLOT(on())); connect (m_targetTextEdit, SIGNAL(translatedEntryDisplayed()), m_leds->ledUntr, SLOT(off())); m_targetTextEdit->showPos(m_targetTextEdit->currentPos()); } #endif } //main function in this file :) void EditorView::gotoEntry(DocPosition pos, int selection) { setUpdatesEnabled(false); bool refresh=(pos.entry==-1); if (refresh) pos=m_targetTextEdit->currentPos(); //qCWarning(LOKALIZE_LOG)<<"refresh"<isPlural(pos.entry))) { if (Q_UNLIKELY( m_catalog->numberOfPluralForms()!=m_pluralTabBar->count() )) { int i=m_pluralTabBar->count(); if (m_catalog->numberOfPluralForms()>m_pluralTabBar->count()) while (inumberOfPluralForms()) m_pluralTabBar->addTab(i18nc("@title:tab","Plural Form %1",++i)); else while (i>m_catalog->numberOfPluralForms()) m_pluralTabBar->removeTab(i--); } m_pluralTabBar->show(); m_pluralTabBar->blockSignals(true); m_pluralTabBar->setCurrentIndex(pos.form); m_pluralTabBar->blockSignals(false); } else m_pluralTabBar->hide(); //bool keepCursor=DocPos(pos)==DocPos(_msgidEdit->currentPos()); bool keepCursor=false; CatalogString sourceWithTags=m_sourceTextEdit->showPos(pos,CatalogString(),keepCursor); //qCWarning(LOKALIZE_LOG)<<"calling showPos"; QString targetString=m_targetTextEdit->showPos(pos,sourceWithTags,keepCursor).string; //qCWarning(LOKALIZE_LOG)<<"ss"<<_msgstrEdit->textCursor().anchor()<<_msgstrEdit->textCursor().position(); m_sourceTextEdit->cursorToStart(); m_targetTextEdit->cursorToStart(); bool untrans=targetString.isEmpty(); //qCWarning(LOKALIZE_LOG)<<"ss1"<<_msgstrEdit->textCursor().anchor()<<_msgstrEdit->textCursor().position(); if (pos.offset || selection) { TranslationUnitTextEdit* msgEdit=(pos.part==DocPosition::Source?m_sourceTextEdit:m_targetTextEdit); QTextCursor t=msgEdit->textCursor(); t.movePosition(QTextCursor::NextCharacter,QTextCursor::MoveAnchor,pos.offset); //NOTE this was kinda bug due to on-the-fly msgid wordwrap if (selection) t.movePosition(QTextCursor::NextCharacter,QTextCursor::KeepAnchor,selection); msgEdit->setTextCursor(t); } else if (!untrans) { QTextCursor t=m_targetTextEdit->textCursor(); //what if msg starts with a tag? if (Q_UNLIKELY( targetString.startsWith('<') )) { int offset=targetString.indexOf(QRegExp(QStringLiteral(">[^<]"))); if ( offset!=-1 ) t.movePosition(QTextCursor::NextCharacter,QTextCursor::MoveAnchor,offset+1); } else if (Q_UNLIKELY( targetString.startsWith(TAGRANGE_IMAGE_SYMBOL) )) { int offset=targetString.indexOf(QRegExp(QStringLiteral("[^")%QChar(TAGRANGE_IMAGE_SYMBOL)%']')); if ( offset!=-1 ) t.movePosition(QTextCursor::NextCharacter,QTextCursor::MoveAnchor,offset+1); } m_targetTextEdit->setTextCursor(t); } //qCWarning(LOKALIZE_LOG)<<"set-->"<<_msgstrEdit->textCursor().anchor()<<_msgstrEdit->textCursor().position(); //qCWarning(LOKALIZE_LOG)<<"anchor"<setFocus(); setUpdatesEnabled(true); } //BEGIN edit actions that are easier to do in this class void EditorView::unwrap(TranslationUnitTextEdit* editor) { if (!editor) editor=m_targetTextEdit; QTextCursor t=editor->document()->find(QRegExp("[^(\\\\n)]$")); if (t.isNull()) return; if (editor==m_targetTextEdit) m_catalog->beginMacro(i18nc("@item Undo action item","Unwrap")); t.movePosition(QTextCursor::EndOfLine); if (!t.atEnd()) t.deleteChar(); QRegExp rx("[^(\\\\n)>]$"); //remove '\n's skipping "\\\\n" while (!(t=editor->document()->find(rx,t)).isNull()) { t.movePosition(QTextCursor::EndOfLine); if (!t.atEnd()) t.deleteChar(); } if (editor==m_targetTextEdit) m_catalog->endMacro(); } void EditorView::insertTerm(const QString& term) { m_targetTextEdit->insertPlainText(term); m_targetTextEdit->setFocus(); } QString EditorView::selectionInTarget() const { //TODO remove IMAGES return m_targetTextEdit->textCursor().selectedText(); } QString EditorView::selectionInSource() const { //TODO remove IMAGES return m_sourceTextEdit->textCursor().selectedText(); } void EditorView::setProperFocus() { m_targetTextEdit->setFocus(); } //END edit actions that are easier to do in this class QObject* EditorView::viewPort() { return m_targetTextEdit; } void EditorView::toggleBookmark(bool checked) { if (Q_UNLIKELY( m_targetTextEdit->currentPos().entry==-1 )) return; m_catalog->setBookmark(m_targetTextEdit->currentPos().entry,checked); } void EditorView::toggleApprovement() { //qCWarning(LOKALIZE_LOG)<<"called"; if (Q_UNLIKELY( m_targetTextEdit->currentPos().entry==-1 )) return; bool newState=!m_catalog->isApproved(m_targetTextEdit->currentPos().entry); SetStateCmd::push(m_catalog,m_targetTextEdit->currentPos(),newState); emit signalApprovedEntryDisplayed(newState); } void EditorView::setState(TargetState state) { if (Q_UNLIKELY( m_targetTextEdit->currentPos().entry==-1 || m_catalog->state(m_targetTextEdit->currentPos())==state)) return; SetStateCmd::instantiateAndPush(m_catalog,m_targetTextEdit->currentPos(),state); emit signalApprovedEntryDisplayed(m_catalog->isApproved(m_targetTextEdit->currentPos())); } void EditorView::setEquivTrans(bool equivTrans) { m_catalog->push(new SetEquivTransCmd(m_catalog, m_targetTextEdit->currentPos(), equivTrans)); } diff --git a/src/filesearch/filesearchtab.cpp b/src/filesearch/filesearchtab.cpp index bdb1c93..32776b4 100644 --- a/src/filesearch/filesearchtab.cpp +++ b/src/filesearch/filesearchtab.cpp @@ -1,958 +1,957 @@ /* **************************************************************************** This file is part of Lokalize Copyright (C) 2007-2014 by Nick Shaforostoff 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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, see . **************************************************************************** */ #include "filesearchtab.h" #include "lokalize_debug.h" #include "ui_filesearchoptions.h" #include "ui_massreplaceoptions.h" #include "project.h" #include "prefs.h" #include "tmscanapi.h" //TODO separate some decls into new header #include "state.h" #include "qaview.h" #include "catalog.h" #include "fastsizehintitemdelegate.h" -#include "kdemacros.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef NOKDE #include #include #include #endif static QStringList doScanRecursive(const QDir& dir); class FileListModel: public QStringListModel { public: FileListModel(QObject* parent): QStringListModel(parent){} QVariant data(const QModelIndex& item, int role=Qt::DisplayRole) const; Qt::ItemFlags flags(const QModelIndex&) const {return Qt::ItemIsEnabled|Qt::ItemIsSelectable;} }; QVariant FileListModel::data(const QModelIndex& item, int role) const { if (role==Qt::DisplayRole) return shorterFilePath(stringList().at(item.row())); if (role==Qt::UserRole) return stringList().at(item.row()); return QVariant(); } SearchFileListView::SearchFileListView(QWidget* parent) : QDockWidget( i18nc("@title:window","File List"), parent) , m_browser(new QTreeView(this)) , m_background(new QLabel(i18n("Drop translation files here..."), this)) , m_model(new FileListModel(this)) { setWidget(m_background); m_background->setMinimumWidth(QFontMetrics(font()).averageCharWidth()*30); m_background->setAlignment(Qt::AlignCenter); m_browser->hide(); m_browser->setModel(m_model); m_browser->setRootIsDecorated(false); m_browser->setHeaderHidden(true); m_browser->setUniformRowHeights(true); m_browser->setAlternatingRowColors(true); m_browser->setContextMenuPolicy(Qt::ActionsContextMenu); QAction* action=new QAction(i18nc("@action:inmenu", "Clear"), m_browser); connect(action, SIGNAL(triggered()), this, SLOT(clear())); m_browser->addAction(action); connect(m_browser, SIGNAL(activated(QModelIndex)), this, SLOT(requestFileOpen(QModelIndex))); } void SearchFileListView::requestFileOpen(const QModelIndex& item) { emit fileOpenRequested(item.data(Qt::UserRole).toString()); } void SearchFileListView::addFiles(const QStringList& files) { if (files.isEmpty()) return; m_background->hide(); setWidget(m_browser); m_browser->show(); //ensure unquiness, sorting the list along the way QMap map; foreach(const QString& filepath, m_model->stringList()) map[filepath]=true; foreach(const QString& filepath, files) map[filepath]=true; m_model->setStringList(map.keys()); } void SearchFileListView::addFilesFast(const QStringList& files) { if (files.size()) m_model->setStringList(m_model->stringList()+files); } void SearchFileListView::clear() { m_model->setStringList(QStringList()); } QStringList SearchFileListView::files() const { return m_model->stringList(); } void SearchFileListView::scrollTo(const QString& file) { if (file.isEmpty()) { m_browser->scrollToTop(); return; } int idx=m_model->stringList().indexOf(file); if (idx!=-1) m_browser->scrollTo(m_model->index(idx, 0), QAbstractItemView::PositionAtCenter); } bool SearchParams::isEmpty() const { return sourcePattern.pattern().isEmpty() && targetPattern.pattern().isEmpty(); } SearchJob::SearchJob(const QStringList& f, const SearchParams& sp, const QVector& r, int sn, QObject*) : QRunnable() , files(f) , searchParams(sp) , rules(r) , searchNumber(sn) , m_size(0) { setAutoDelete(false); } void SearchJob::run() { QTime a;a.start(); bool removeAmpFromSource = searchParams.sourcePattern.patternSyntax()==QRegExp::FixedString && !searchParams.sourcePattern.pattern().contains(QLatin1Char('&')); bool removeAmpFromTarget = searchParams.targetPattern.patternSyntax()==QRegExp::FixedString && !searchParams.targetPattern.pattern().contains(QLatin1Char('&')); foreach(const QString& filePath, files) { Catalog catalog(0); - if (KDE_ISUNLIKELY(catalog.loadFromUrl(filePath, QString(), &m_size, true)!=0)) + if (Q_UNLIKELY(catalog.loadFromUrl(filePath, QString(), &m_size, true)!=0)) continue; //QVector catalogResults; int numberOfEntries=catalog.numberOfEntries(); DocPosition pos(0); for (;pos.entry positions(2); int matchedQaRule=findMatchingRule(rules, r.source, r.target, positions); if (matchedQaRule==-1) continue; if (positions.at(0).len) r.sourcePositions< map; for (int i=0;is.string.length() || replaceWhat.pattern().startsWith('^')) break; pos=replaceWhat.indexIn(s.string, pos); } } catalog.save(); } } //BEGIN FileSearchModel FileSearchModel::FileSearchModel(QObject* parent) : QAbstractListModel(parent) { } QVariant FileSearchModel::headerData(int section, Qt::Orientation, int role) const { if (role!=Qt::DisplayRole) return QVariant(); switch (section) { case FileSearchModel::Source: return i18nc("@title:column Original text","Source"); case FileSearchModel::Target: return i18nc("@title:column Text in target language","Target"); //case FileSearchModel::Context: return i18nc("@title:column","Context"); case FileSearchModel::Filepath: return i18nc("@title:column","File"); case FileSearchModel::TranslationStatus: return i18nc("@title:column","Translation Status"); } return QVariant(); } void FileSearchModel::appendSearchResults(const SearchResults& results) { beginInsertRows(QModelIndex(), m_searchResults.size(), m_searchResults.size()+results.size()-1); m_searchResults+=results; endInsertRows(); } void FileSearchModel::clear() { beginResetModel(); m_searchResults.clear();; endResetModel(); } QVariant FileSearchModel::data(const QModelIndex& item, int role) const { bool doHtml=(role==FastSizeHintItemDelegate::HtmlDisplayRole); if (doHtml) role=Qt::DisplayRole; if (role==Qt::DisplayRole) { QString result; const SearchResult& sr=m_searchResults.at(item.row()); if (item.column()==Source) result=sr.source; if (item.column()==Target) result=sr.target; if (item.column()==Filepath) result=shorterFilePath(sr.filepath); if (doHtml && item.column()<=FileSearchModel::Target) { if (result.isEmpty()) return result; const QString startBld = QStringLiteral("_ST_"); const QString endBld = QStringLiteral("_END_"); const QString startBldTag = QStringLiteral(""); const QString endBldTag = QStringLiteral(""); if (item.column()==FileSearchModel::Target && !m_replaceWhat.isEmpty()) { result.replace(m_replaceWhat, m_replaceWith); QString escaped=convertToHtml(result, !sr.isApproved); escaped.replace(startBld, startBldTag); escaped.replace(endBld, endBldTag); return escaped; } const QVector& occurences=item.column()==FileSearchModel::Source?sr.sourcePositions:sr.targetPositions; int occ=occurences.count(); while (--occ>=0) { const StartLen& sl=occurences.at(occ); result.insert(sl.start+sl.len, endBld); result.insert(sl.start, startBld); } /* !isApproved(sr.state, Project::instance()->local()->role())*/ QString escaped=convertToHtml(result, item.column()==FileSearchModel::Target && !sr.isApproved); escaped.replace(startBld, startBldTag); escaped.replace(endBld, endBldTag); return escaped; } return result; } if (role==Qt::UserRole) { const SearchResult& sr=m_searchResults.at(item.row()); if (item.column()==Filepath) return sr.filepath; } return QVariant(); } void FileSearchModel::setReplacePreview(const QRegExp& s, const QString& r) { m_replaceWhat=s; m_replaceWith=QLatin1String("_ST_") % r % QLatin1String("_END_"); emit dataChanged(index(0, Target), index(rowCount()-1, Target)); } //END FileSearchModel //BEGIN FileSearchTab FileSearchTab::FileSearchTab(QWidget *parent) : LokalizeSubwindowBase2(parent) // , m_proxyModel(new TMResultsSortFilterProxyModel(this)) , m_model(new FileSearchModel(this)) , m_lastSearchNumber(0) , m_dbusId(-1) { setWindowTitle(i18nc("@title:window","Search and replace in files")); setAcceptDrops(true); QWidget* w=new QWidget(this); ui_fileSearchOptions=new Ui_FileSearchOptions; ui_fileSearchOptions->setupUi(w); setCentralWidget(w); QShortcut* sh=new QShortcut(Qt::CTRL+Qt::Key_L, this); connect(sh,SIGNAL(activated()),ui_fileSearchOptions->querySource,SLOT(setFocus())); setFocusProxy(ui_fileSearchOptions->querySource); sh=new QShortcut(Qt::Key_Escape,this,SLOT(stopSearch()),0,Qt::WidgetWithChildrenShortcut); QTreeView* view=ui_fileSearchOptions->treeView; QVector singleLineColumns(FileSearchModel::ColumnCount, false); singleLineColumns[FileSearchModel::Filepath]=true; singleLineColumns[FileSearchModel::TranslationStatus]=true; //singleLineColumns[TMDBModel::Context]=true; QVector richTextColumns(FileSearchModel::ColumnCount, false); richTextColumns[FileSearchModel::Source]=true; richTextColumns[FileSearchModel::Target]=true; view->setItemDelegate(new FastSizeHintItemDelegate(this,singleLineColumns,richTextColumns)); connect(m_model,SIGNAL(modelReset()),view->itemDelegate(),SLOT(reset())); connect(m_model,SIGNAL(dataChanged(QModelIndex,QModelIndex)),view->itemDelegate(),SLOT(reset())); //connect(m_model,SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)),view->itemDelegate(),SLOT(reset())); //connect(m_proxyModel,SIGNAL(layoutChanged()),view->itemDelegate(),SLOT(reset())); //connect(m_proxyModel,SIGNAL(layoutChanged()),this,SLOT(displayTotalResultCount())); view->setContextMenuPolicy(Qt::ActionsContextMenu); QAction* a=new QAction(i18n("Copy source to clipboard"),view); a->setShortcut(Qt::CTRL + Qt::Key_S); a->setShortcutContext(Qt::WidgetWithChildrenShortcut); connect(a,SIGNAL(triggered()), this, SLOT(copySourceToClipboard())); view->addAction(a); a=new QAction(i18n("Copy target to clipboard"),view); a->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Return)); a->setShortcutContext(Qt::WidgetWithChildrenShortcut); connect(a,SIGNAL(triggered()), this, SLOT(copyTargetToClipboard())); view->addAction(a); a=new QAction(i18n("Open file"),view); a->setShortcut(QKeySequence(Qt::Key_Return)); a->setShortcutContext(Qt::WidgetWithChildrenShortcut); connect(a,SIGNAL(triggered()), this, SLOT(openFile())); connect(view,SIGNAL(activated(QModelIndex)), this, SLOT(openFile())); view->addAction(a); connect(ui_fileSearchOptions->querySource,SIGNAL(returnPressed()),this,SLOT(performSearch())); connect(ui_fileSearchOptions->queryTarget,SIGNAL(returnPressed()),this,SLOT(performSearch())); connect(ui_fileSearchOptions->doFind, SIGNAL(clicked()), this,SLOT(performSearch())); // m_proxyModel->setDynamicSortFilter(true); // m_proxyModel->setSourceModel(m_model); view->setModel(m_model); // view->setModel(m_proxyModel); // view->sortByColumn(FileSearchModel::Filepath,Qt::AscendingOrder); // view->setSortingEnabled(true); // view->setItemDelegate(new FastSizeHintItemDelegate(this)); // connect(m_model,SIGNAL(resultsFetched()),view->itemDelegate(),SLOT(reset())); // connect(m_model,SIGNAL(modelReset()),view->itemDelegate(),SLOT(reset())); // connect(m_proxyModel,SIGNAL(layoutChanged()),view->itemDelegate(),SLOT(reset())); // connect(m_proxyModel,SIGNAL(layoutChanged()),this,SLOT(displayTotalResultCount())); //BEGIN resizeColumnToContents static const int maxInitialWidths[]={QApplication::desktop()->availableGeometry().width()/3,QApplication::desktop()->availableGeometry().width()/3}; int column=sizeof(maxInitialWidths)/sizeof(int); while (--column>=0) view->setColumnWidth(column, maxInitialWidths[column]); //END resizeColumnToContents int i=6; while (--i>ID_STATUS_PROGRESS) statusBarItems.insert(i,QString()); #ifndef NOKDE setXMLFile(QStringLiteral("filesearchtabui.rc"),true); dbusObjectPath(); #endif KActionCollection* ac=actionCollection(); KActionCategory* srf=new KActionCategory(i18nc("@title actions category","Search and replace in files"), ac); m_searchFileListView = new SearchFileListView(this); //m_searchFileListView->hide(); addDockWidget(Qt::RightDockWidgetArea, m_searchFileListView); srf->addAction( QStringLiteral("showfilelist_action"), m_searchFileListView->toggleViewAction() ); connect(m_searchFileListView, SIGNAL(fileOpenRequested(QString)), this, SIGNAL(fileOpenRequested(QString))); m_massReplaceView = new MassReplaceView(this); addDockWidget(Qt::RightDockWidgetArea, m_massReplaceView); srf->addAction( QStringLiteral("showmassreplace_action"), m_massReplaceView->toggleViewAction() ); connect(m_massReplaceView, SIGNAL(previewRequested(QRegExp,QString)), m_model, SLOT(setReplacePreview(QRegExp ,QString))); connect(m_massReplaceView, SIGNAL(replaceRequested(QRegExp,QString)), this, SLOT(massReplace(QRegExp,QString))); //m_massReplaceView->hide(); m_qaView = new QaView(this); m_qaView->hide(); addDockWidget(Qt::RightDockWidgetArea, m_qaView); srf->addAction( QStringLiteral("showqa_action"), m_qaView->toggleViewAction() ); connect(m_qaView, SIGNAL(rulesChanged()), this, SLOT(performSearch())); connect(m_qaView->toggleViewAction(), SIGNAL(toggled(bool)), this, SLOT(performSearch()), Qt::QueuedConnection); view->header()->restoreState(readUiState("FileSearchResultsHeaderState")); } FileSearchTab::~FileSearchTab() { stopSearch(); writeUiState("FileSearchResultsHeaderState", ui_fileSearchOptions->treeView->header()->saveState()); #ifndef NOKDE ids.removeAll(m_dbusId); #endif } void FileSearchTab::performSearch() { if (m_searchFileListView->files().isEmpty()) { addFilesToSearch(doScanRecursive(QDir(Project::instance()->poDir()))); if (m_searchFileListView->files().isEmpty()) return; } m_model->clear(); statusBarItems.insert(1,QString()); m_searchFileListView->scrollTo(); m_lastSearchNumber++; SearchParams sp; sp.sourcePattern.setPattern(ui_fileSearchOptions->querySource->text()); sp.targetPattern.setPattern(ui_fileSearchOptions->queryTarget->text()); sp.invertSource=ui_fileSearchOptions->invertSource->isChecked(); sp.invertTarget=ui_fileSearchOptions->invertTarget->isChecked(); QVector rules=m_qaView->isVisible()?m_qaView->rules():QVector(); if (sp.isEmpty() && rules.isEmpty()) return; if (!ui_fileSearchOptions->regEx->isChecked()) { sp.sourcePattern.setPatternSyntax(QRegExp::FixedString); sp.targetPattern.setPatternSyntax(QRegExp::FixedString); } /* else { sp.sourcePattern.setMinimal(true); sp.targetPattern.setMinimal(true); } */ stopSearch(); m_massReplaceView->deactivatePreview(); QStringList files=m_searchFileListView->files(); for(int i=0; istart(job); m_runningJobs.append(job); } } void FileSearchTab::stopSearch() { #if QT_VERSION >= 0x050500 int i=m_runningJobs.size(); while (--i>=0) QThreadPool::globalInstance()->cancel(m_runningJobs.at(i)); #endif m_runningJobs.clear(); } void FileSearchTab::massReplace(const QRegExp &what, const QString& with) { #define BATCH_SIZE 20 SearchResults searchResults=m_model->searchResults(); for (int i=0;istart(job); m_runningJobs.append(job); } } static void copy(QTreeView* view, int column) { QApplication::clipboard()->setText( view->currentIndex().sibling(view->currentIndex().row(),column).data().toString()); } void FileSearchTab::copySourceToClipboard() { copy(ui_fileSearchOptions->treeView, FileSearchModel::Source); } void FileSearchTab::copyTargetToClipboard() { copy(ui_fileSearchOptions->treeView, FileSearchModel::Target); } void FileSearchTab::openFile() { QModelIndex item=ui_fileSearchOptions->treeView->currentIndex(); SearchResult sr=m_model->searchResult(item); DocPosition docPos=sr.docPos.toDocPosition(); int selection=0; if (sr.targetPositions.size()) { docPos.offset=sr.targetPositions.first().start; selection =sr.targetPositions.first().len; } qCDebug(LOKALIZE_LOG)<<"fileOpenRequest"<treeView->currentIndex(); int row=item.row(); int rowCount=m_model->rowCount(); if (++row>=rowCount) //ok if row was -1 (no solection) return; ui_fileSearchOptions->treeView->setCurrentIndex(item.sibling(row, item.column())); openFile(); } QStringList scanRecursive(const QList& urls) { QStringList result; int i=urls.size(); while(--i>=0) { if (urls.at(i).isEmpty() || urls.at(i).path().isEmpty() ) //NOTE is this a Qt bug? continue; QString path=urls.at(i).toLocalFile(); if (Catalog::extIsSupported(path)) result.append(path); else result+=doScanRecursive(QDir(path)); } return result; } //returns gross number of jobs started static QStringList doScanRecursive(const QDir& dir) { QStringList result; QStringList subDirs(dir.entryList(QDir::Dirs|QDir::NoDotAndDotDot|QDir::Readable)); int i=subDirs.size(); while(--i>=0) result+=doScanRecursive(QDir(dir.filePath(subDirs.at(i)))); QStringList filters=Catalog::supportedExtensions(); i=filters.size(); while(--i>=0) filters[i].prepend('*'); QStringList files(dir.entryList(filters,QDir::Files|QDir::NoDotAndDotDot|QDir::Readable)); i=files.size(); while(--i>=0) result.append(dir.filePath(files.at(i))); return result; } void FileSearchTab::dragEnterEvent(QDragEnterEvent* event) { if(dragIsAcceptable(event->mimeData()->urls())) event->acceptProposedAction(); } void FileSearchTab::dropEvent(QDropEvent *event) { event->acceptProposedAction(); addFilesToSearch(scanRecursive(event->mimeData()->urls())); } void FileSearchTab::addFilesToSearch(const QStringList& files) { m_searchFileListView->addFiles(files); performSearch(); } void FileSearchTab::setSourceQuery(const QString& query) { ui_fileSearchOptions->querySource->setText(query); } void FileSearchTab::setTargetQuery(const QString& query) { ui_fileSearchOptions->queryTarget->setText(query); } void FileSearchTab::searchJobDone(SearchJob* j) { j->deleteLater(); if (j->searchNumber!=m_lastSearchNumber) return; /* SearchResults searchResults; FileSearchResults::const_iterator i = j->results.constBegin(); while (i != j->results.constEnd()) { foreach(const FileSearchResult& fsr, i.value()) { SearchResult sr(fsr); sr.filepath=i.key(); searchResults<appendSearchResults(searchResults); */ if (j->results.size()) { m_model->appendSearchResults(j->results); m_searchFileListView->scrollTo(j->results.last().filepath); } statusBarItems.insert(1,i18nc("@info:status message entries","Total: %1", m_model->rowCount())); //ui_fileSearchOptions->treeView->setFocus(); } void FileSearchTab::replaceJobDone(MassReplaceJob* j) { j->deleteLater(); ui_fileSearchOptions->treeView->scrollTo(m_model->index(j->globalPos+j->searchResults.count(), 0)); } //END FileSearchTab //BEGIN MASS REPLACE MassReplaceView::MassReplaceView(QWidget* parent) : QDockWidget(i18nc("@title:window","Mass replace"), parent) , ui(new Ui_MassReplaceOptions) { QWidget* base=new QWidget(this); setWidget(base); ui->setupUi(base); connect(ui->doPreview, SIGNAL(toggled(bool)), this, SLOT(requestPreview(bool))); connect(ui->doReplace, SIGNAL(clicked(bool)), this, SLOT(requestReplace())); /* QLabel* rl=new QLabel(i18n("Replace:"), base); QLineEdit* searchEdit=new QLineEdit(base); QHBoxLayout* searchL=new QHBoxLayout(); searchL->addWidget(rl); searchL->addWidget(searchEdit); QLabel* wl=new QLabel(i18n("With:"), base); wl->setAlignment(Qt::AlignRight); wl->setMinimumSize(rl->minimumSizeHint()); QLineEdit* replacementEdit=new QLineEdit(base); QHBoxLayout* replacementL=new QHBoxLayout(); replacementL->addWidget(wl); replacementL->addWidget(replacementEdit); FlowLayout* fl=new FlowLayout(); fl->addItem(searchL); fl->addItem(replacementL); base->setLayout(fl); */ } MassReplaceView::~MassReplaceView() { delete ui; } static QRegExp regExpFromUi(const QString& s, Ui_MassReplaceOptions* ui) { return QRegExp(s, ui->matchCase->isChecked()?Qt::CaseSensitive:Qt::CaseInsensitive, ui->useRegExps->isChecked()?QRegExp::FixedString:QRegExp::RegExp); } void MassReplaceView::requestPreviewUpdate() { QString s=ui->searchText->text(); QString r=ui->replaceText->text(); if (s.length()) ui->doReplace->setEnabled(true); emit previewRequested(regExpFromUi(s, ui), r); } void MassReplaceView::requestPreview(bool enable) { if (enable) { connect(ui->searchText, SIGNAL(textEdited(QString)), this, SLOT(requestPreviewUpdate())); connect(ui->replaceText,SIGNAL(textEdited(QString)), this, SLOT(requestPreviewUpdate())); connect(ui->useRegExps, SIGNAL(toggled(bool)), this, SLOT(requestPreviewUpdate())); connect(ui->matchCase, SIGNAL(toggled(bool)), this, SLOT(requestPreviewUpdate())); requestPreviewUpdate(); } else { disconnect(ui->searchText, SIGNAL(textEdited(QString)), this, SLOT(requestPreviewUpdate())); disconnect(ui->replaceText,SIGNAL(textEdited(QString)), this, SLOT(requestPreviewUpdate())); disconnect(ui->useRegExps, SIGNAL(toggled(bool)), this, SLOT(requestPreviewUpdate())); disconnect(ui->matchCase, SIGNAL(toggled(bool)), this, SLOT(requestPreviewUpdate())); emit previewRequested(QRegExp(), QString()); } } void MassReplaceView::requestReplace() { QString s=ui->searchText->text(); QString r=ui->replaceText->text(); if (s.isEmpty()) return; emit replaceRequested(regExpFromUi(s, ui), r); } void MassReplaceView::deactivatePreview() { ui->doPreview->setChecked(false); ui->doReplace->setEnabled(false); } #ifndef NOKDE #include "filesearchadaptor.h" #include QList FileSearchTab::ids; //BEGIN DBus interface QString FileSearchTab::dbusObjectPath() { QString FILESEARCH_PATH=QStringLiteral("/ThisIsWhatYouWant/FileSearch/"); if ( m_dbusId==-1 ) { new FileSearchAdaptor(this); int i=0; while(i 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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, see . **************************************************************************** */ #include "mergecatalog.h" #include "lokalize_debug.h" #include "catalog_private.h" #include "catalogstorage.h" #include "cmd.h" -#include #include #include #include MergeCatalog::MergeCatalog(QObject* parent, Catalog* baseCatalog, bool saveChanges) : Catalog(parent) , m_baseCatalog(baseCatalog) , m_unmatchedCount(0) , m_modified(false) { setActivePhase(baseCatalog->activePhase(),baseCatalog->activePhaseRole()); if (saveChanges) { connect (baseCatalog,SIGNAL(signalEntryModified(DocPosition)),this,SLOT(copyFromBaseCatalogIfInDiffIndex(DocPosition))); connect (baseCatalog,SIGNAL(signalFileSaved()),this,SLOT(save())); } } void MergeCatalog::copyFromBaseCatalog(const DocPosition& pos, int options) { bool a=m_mergeDiffIndex.contains(pos.entry); if (options&EvenIfNotInDiffIndex || !a) { //sync changes DocPosition ourPos=pos; if ( (ourPos.entry=m_map.at(ourPos.entry)) == -1) return; //note the explicit use of map... if (m_storage->isApproved(ourPos)!=m_baseCatalog->isApproved(pos)) //qCWarning(LOKALIZE_LOG)<setApproved(ourPos, m_baseCatalog->isApproved(pos)); DocPos p(pos); if (!m_originalHashes.contains(p)) m_originalHashes[p]=qHash(m_storage->target(ourPos)); m_storage->setTarget(ourPos,m_baseCatalog->target(pos)); setModified(ourPos, true); if (options&EvenIfNotInDiffIndex && a) m_mergeDiffIndex.removeAll(pos.entry); m_modified=true; emit signalEntryModified(pos); } } QString MergeCatalog::msgstr(const DocPosition& pos) const { DocPosition us=pos; us.entry=m_map.at(pos.entry); return (us.entry==-1)?QString():Catalog::msgstr(us); } bool MergeCatalog::isApproved(uint index) const { return (m_map.at(index)==-1)?false:Catalog::isApproved(m_map.at(index)); } TargetState MergeCatalog::state(const DocPosition& pos) const { DocPosition us=pos; us.entry=m_map.at(pos.entry); return (us.entry==-1)?New:Catalog::state(us); } bool MergeCatalog::isPlural(uint index) const { //sanity if (m_map.at(index) == -1) { qCWarning(LOKALIZE_LOG)<<"!!! index"<m_storage); CatalogStorage& mergeStorage=*(m_storage); MatchItem item(mergePos.entry, basePos.entry, true); //TODO make more robust, perhaps after XLIFF? QStringList baseMatchData=baseStorage.matchData(basePos); QStringList mergeMatchData=mergeStorage.matchData(mergePos); //compare ids item.score+=40*((baseMatchData.isEmpty()&&mergeMatchData.isEmpty())?baseStorage.id(basePos)==mergeStorage.id(mergePos) :baseMatchData==mergeMatchData); //TODO look also for changed/new s //translation isn't changed if (baseStorage.targetAllForms(basePos, true)==mergeStorage.targetAllForms(mergePos, true)) { item.translationIsDifferent=baseStorage.isApproved(basePos)!=mergeStorage.isApproved(mergePos); item.score+=29+1*item.translationIsDifferent; } #if 0 if (baseStorage.source(basePos)=="%1 (%2)") { qCDebug(LOKALIZE_LOG)<<"BASE"; qCDebug(LOKALIZE_LOG)<url(); qCDebug(LOKALIZE_LOG)<m_storage); CatalogStorage& mergeStorage=*(m_storage); DocPosition i(0); int size=baseStorage.size(); int mergeSize=mergeStorage.size(); m_map.fill(-1,size); QMultiMap backMap; //will be used to maintain one-to-one relation //precalc for fast lookup QMultiHash mergeMap; while (i.entry& entries=mergeMap.values(key); QList scores; int k=entries.size(); if (k) { while(--k>=0) scores<()); m_map[i.entry]=scores.first().mergeEntry; backMap.insert(scores.first().mergeEntry, i.entry); if (scores.first().translationIsDifferent) m_mergeDiffIndex.append(i.entry); } ++(i.entry); } //maintain one-to-one relation const QList& mergePositions=backMap.uniqueKeys(); foreach(int mergePosition, mergePositions) { const QList& basePositions=backMap.values(mergePosition); if (basePositions.size()==1) continue; //qCDebug(LOKALIZE_LOG)<<"kv"< scores; foreach(int value, basePositions) scores<()); int i=scores.size(); while(--i>0) { //qCDebug(LOKALIZE_LOG)<<"erasing"<::iterator it = mergeMap.begin(); while (it != mergeMap.end()) { //qCWarning(LOKALIZE_LOG)<msgstr(pos)!=msgstr(pos); m_baseCatalog->beginMacro(i18nc("@item Undo action item","Accept change in translation")); if ( m_baseCatalog->state(pos) != state(pos)) SetStateCmd::instantiateAndPush(m_baseCatalog,pos,state(pos)); if (changeContents) { pos.offset=0; if (!m_baseCatalog->msgstr(pos).isEmpty()) m_baseCatalog->push(new DelTextCmd(m_baseCatalog,pos,m_baseCatalog->msgstr(pos))); m_baseCatalog->push(new InsTextCmd(m_baseCatalog,pos,msgstr(pos))); } ////////this is NOT done automatically by BaseCatalogEntryChanged slot bool remove=true; if (isPlural(pos.entry)) { DocPosition p=pos; p.form=qMin(m_baseCatalog->numberOfPluralForms(),numberOfPluralForms());//just sanity check p.form=qMax((int)p.form,1);//just sanity check while ((--(p.form))>=0 && remove) remove=m_baseCatalog->msgstr(p)==msgstr(p); } if (remove) removeFromDiffIndex(pos.entry); m_baseCatalog->endMacro(); } void MergeCatalog::copyToBaseCatalog(int options) { DocPosition pos; pos.offset=0; bool insHappened=false; QLinkedList changed=differentEntries(); foreach(int entry, changed) { pos.entry=entry; if (options&EmptyOnly&&!m_baseCatalog->isEmpty(entry)) continue; if (options&HigherOnly&&!m_baseCatalog->isEmpty(entry)&&m_baseCatalog->state(pos)>=state(pos)) continue; int formsCount=(m_baseCatalog->isPlural(entry))?m_baseCatalog->numberOfPluralForms():1; pos.form=0; while (pos.formpush(new DelTextCmd(m_baseCatalog,pos,m_baseCatalog->msgstr(pos.entry,0))); ? //some forms may still contain translation... if (!(options&EmptyOnly && !m_baseCatalog->isEmpty(pos)) /*&& !(options&HigherOnly && !m_baseCatalog->isEmpty(pos) && m_baseCatalog->state(pos)>=state(pos))*/) { if (!insHappened) { //stop basecatalog from sending signalEntryModified to us //when we are the ones who does the modification disconnect (m_baseCatalog,SIGNAL(signalEntryModified(DocPosition)),this,SLOT(copyFromBaseCatalogIfInDiffIndex(DocPosition))); insHappened=true; m_baseCatalog->beginMacro(i18nc("@item Undo action item","Accept all new translations")); } copyToBaseCatalog(pos); /// /// /// m_baseCatalog->push(new InsTextCmd(m_baseCatalog,pos,mergeCatalog.msgstr(pos))); /// /// } ++(pos.form); } /// /// /// removeFromDiffIndex(m_pos.entry); /// /// } if (insHappened) { m_baseCatalog->endMacro(); //reconnect to catch all modifications coming from outside connect (m_baseCatalog,SIGNAL(signalEntryModified(DocPosition)),this,SLOT(copyFromBaseCatalogIfInDiffIndex(DocPosition))); } } diff --git a/src/mergemode/mergeview.cpp b/src/mergemode/mergeview.cpp index 6ab0095..d8f36ab 100644 --- a/src/mergemode/mergeview.cpp +++ b/src/mergemode/mergeview.cpp @@ -1,400 +1,399 @@ /* **************************************************************************** This file is part of Lokalize Copyright (C) 2007-2014 by Nick Shaforostoff 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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, see . **************************************************************************** */ #include "mergeview.h" #include "cmd.h" #include "mergecatalog.h" #include "project.h" #include "diff.h" -#include "kdemacros.h" #include #include #include #ifndef NOKDE #include #endif #include #include #include #include #include #include MergeView::MergeView(QWidget* parent, Catalog* catalog, bool primary) : QDockWidget ( primary?i18nc("@title:window that displays difference between current file and 'merge source'","Primary Sync"):i18nc("@title:window that displays difference between current file and 'merge source'","Secondary Sync"), parent) , m_browser(new QTextEdit(this)) , m_baseCatalog(catalog) , m_mergeCatalog(0) , m_normTitle(primary? i18nc("@title:window that displays difference between current file and 'merge source'","Primary Sync"): i18nc("@title:window that displays difference between current file and 'merge source'","Secondary Sync")) , m_hasInfoTitle(m_normTitle+" [*]") , m_hasInfo(false) , m_primary(primary) { setObjectName(primary?QStringLiteral("mergeView-primary"):QStringLiteral("mergeView-secondary")); setWidget(m_browser); setToolTip(i18nc("@info:tooltip","Drop file to be merged into / synced with the current one here, then see context menu options")); hide(); setAcceptDrops(true); m_browser->setReadOnly(true); m_browser->setContextMenuPolicy(Qt::NoContextMenu); m_browser->viewport()->setBackgroundRole(QPalette::Background); setContextMenuPolicy(Qt::ActionsContextMenu); } MergeView::~MergeView() { delete m_mergeCatalog; emit mergeCatalogPointerChanged(NULL); emit mergeCatalogAvailable(false); } QString MergeView::filePath() { if (m_mergeCatalog) return m_mergeCatalog->url(); return QString(); } void MergeView::dragEnterEvent(QDragEnterEvent* event) { if(event->mimeData()->hasUrls() && Catalog::extIsSupported(event->mimeData()->urls().first().path())) event->acceptProposedAction(); } void MergeView::dropEvent(QDropEvent *event) { mergeOpen(event->mimeData()->urls().first().toLocalFile()); event->acceptProposedAction(); } void MergeView::slotUpdate(const DocPosition& pos) { if (pos.entry==m_pos.entry) slotNewEntryDisplayed(pos); } void MergeView::slotNewEntryDisplayed(const DocPosition& pos) { m_pos=pos; if (!m_mergeCatalog) return; emit signalPriorChangedAvailable((pos.entry>m_mergeCatalog->firstChangedIndex()) ||(pluralFormsAvailableBackward()!=-1)); emit signalNextChangedAvailable((pos.entrylastChangedIndex()) ||(pluralFormsAvailableForward()!=-1)); if (!m_mergeCatalog->isPresent(pos.entry)) { //i.e. no corresponding entry, whether changed or not if (m_hasInfo) { m_hasInfo=false; setWindowTitle(m_normTitle); m_browser->clear(); // m_browser->viewport()->setBackgroundRole(QPalette::Base); } emit signalEntryWithMergeDisplayed(false); /// no editing at all! //////////// return; } if (!m_hasInfo) { m_hasInfo=true; setWindowTitle(m_hasInfoTitle); } emit signalEntryWithMergeDisplayed(m_mergeCatalog->isDifferent(pos.entry)); QString result=userVisibleWordDiff(m_baseCatalog->msgstr(pos), m_mergeCatalog->msgstr(pos), Project::instance()->accel(), Project::instance()->markup(), Html); #if 0 int i=-1; bool inTag=false; while(++i') inTag=false; } #endif if (!m_mergeCatalog->isApproved(pos.entry)) { result.prepend(""); result.append(""); } if (m_mergeCatalog->isModified(pos)) { result.prepend(""); result.append(""); } result.replace(' ', QChar::Nbsp); m_browser->setHtml(result); //qCDebug(LOKALIZE_LOG)<<"ELA "<clear(); } void MergeView::mergeOpen(QString mergeFilePath) { if (Q_UNLIKELY( !m_baseCatalog->numberOfEntries() )) return; if (mergeFilePath==m_baseCatalog->url()) { //(we are likely to be _mergeViewSecondary) //special handling: open corresponding file in the branch //for AutoSync QString path=QFileInfo(mergeFilePath).canonicalFilePath(); //bug 245546 regarding symlinks QString oldPath=path; path.replace(Project::instance()->poDir(),Project::instance()->branchDir()); if (oldPath==path) //if file doesn't exist both are empty { cleanup(); return; } mergeFilePath=path; } if (mergeFilePath.isEmpty()) { //Project::instance()->model()->weaver()->suspend(); //KDE5PORT use mutex if needed mergeFilePath=QFileDialog::getOpenFileName(this, i18nc("@title:window", "Select translation file"), QString(), Catalog::supportedFileTypes(false)); //Project::instance()->model()->weaver()->resume(); } if (mergeFilePath.isEmpty()) return; delete m_mergeCatalog; m_mergeCatalog=new MergeCatalog(this,m_baseCatalog); emit mergeCatalogPointerChanged(m_mergeCatalog); emit mergeCatalogAvailable(m_mergeCatalog); int errorLine=m_mergeCatalog->loadFromUrl(mergeFilePath); - if (KDE_ISLIKELY( errorLine==0 )) + if (Q_LIKELY( errorLine==0 )) { if (m_pos.entry>0) emit signalPriorChangedAvailable(m_pos.entry>m_mergeCatalog->firstChangedIndex()); emit signalNextChangedAvailable(m_pos.entrylastChangedIndex()); //a bit hacky :) connect (m_mergeCatalog,SIGNAL(signalEntryModified(DocPosition)),this,SLOT(slotUpdate(DocPosition))); if (m_pos.entry!=-1) slotNewEntryDisplayed(m_pos); show(); } else { //KMessageBox::error(this, KIO::NetAccess::lastErrorString() ); cleanup(); #ifndef NOKDE if (errorLine>0) KMessageBox::error(this, i18nc("@info","Error opening the file %1 for synchronization, error line: %2",mergeFilePath,errorLine) ); else { /* disable this as requested by bug 272587 KNotification* notification=new KNotification("MergeFilesOpenError", this); notification->setText( i18nc("@info %1 is full filename","Error opening the file %1 for synchronization",url.pathOrUrl()) ); notification->sendEvent(); */ } #endif //i18nc("@info %1 is w/o path","No branch counterpart for %1",url.fileName()), } } bool MergeView::isModified() { return m_mergeCatalog && m_mergeCatalog->isModified(); //not isClean because mergecatalog doesn't keep history } int MergeView::pluralFormsAvailableForward() { - if(KDE_ISLIKELY( m_pos.entry==-1 || !m_mergeCatalog->isPlural(m_pos.entry) )) + if(Q_LIKELY( m_pos.entry==-1 || !m_mergeCatalog->isPlural(m_pos.entry) )) return -1; int formLimit=qMin(m_baseCatalog->numberOfPluralForms(),m_mergeCatalog->numberOfPluralForms());//just sanity check DocPosition pos=m_pos; while (++(pos.form)msgstr(pos)!=m_mergeCatalog->msgstr(pos)) return pos.form; } return -1; } int MergeView::pluralFormsAvailableBackward() { - if(KDE_ISLIKELY( m_pos.entry==-1 || !m_mergeCatalog->isPlural(m_pos.entry) )) + if(Q_LIKELY( m_pos.entry==-1 || !m_mergeCatalog->isPlural(m_pos.entry) )) return -1; DocPosition pos=m_pos; while (--(pos.form)>=0) { if (m_baseCatalog->msgstr(pos)!=m_mergeCatalog->msgstr(pos)) return pos.form; } return -1; } void MergeView::gotoPrevChanged() { if (Q_UNLIKELY( !m_mergeCatalog )) return; DocPosition pos; //first, check if there any plural forms waiting to be synced int form=pluralFormsAvailableBackward(); if (Q_UNLIKELY( form!=-1 )) { pos=m_pos; pos.form=form; } else if(Q_UNLIKELY( (pos.entry=m_mergeCatalog->prevChangedIndex(m_pos.entry)) == -1 )) return; if (Q_UNLIKELY( m_mergeCatalog->isPlural(pos.entry)&&form==-1 )) pos.form=qMin(m_baseCatalog->numberOfPluralForms(),m_mergeCatalog->numberOfPluralForms())-1; emit gotoEntry(pos,0); } void MergeView::gotoNextChangedApproved() { gotoNextChanged(true); } void MergeView::gotoNextChanged(bool approvedOnly) { if (Q_UNLIKELY( !m_mergeCatalog )) return; DocPosition pos=m_pos; //first, check if there any plural forms waiting to be synced int form=pluralFormsAvailableForward(); if (Q_UNLIKELY( form!=-1 )) { pos=m_pos; pos.form=form; } else if(Q_UNLIKELY( (pos.entry=m_mergeCatalog->nextChangedIndex(m_pos.entry)) == -1 )) return; while (approvedOnly && !m_mergeCatalog->isApproved(pos.entry)) { if(Q_UNLIKELY( (pos.entry=m_mergeCatalog->nextChangedIndex(pos.entry)) == -1 )) return; } emit gotoEntry(pos,0); } void MergeView::mergeBack() { if(m_pos.entry==-1 ||!m_mergeCatalog ||m_baseCatalog->msgstr(m_pos).isEmpty()) return; m_mergeCatalog->copyFromBaseCatalog(m_pos); } void MergeView::mergeAccept() { if(m_pos.entry==-1 ||!m_mergeCatalog //||m_baseCatalog->msgstr(m_pos)==m_mergeCatalog->msgstr(m_pos) ||m_mergeCatalog->msgstr(m_pos).isEmpty()) return; m_mergeCatalog->copyToBaseCatalog(m_pos); emit gotoEntry(m_pos,0); } void MergeView::mergeAcceptAllForEmpty() { if(Q_UNLIKELY(!m_mergeCatalog)) return; bool update=m_mergeCatalog->differentEntries().contains(m_pos.entry); m_mergeCatalog->copyToBaseCatalog(/*MergeCatalog::EmptyOnly*/MergeCatalog::HigherOnly); if (update!=m_mergeCatalog->differentEntries().contains(m_pos.entry)) emit gotoEntry(m_pos,0); } bool MergeView::event(QEvent *event) { if (event->type()==QEvent::ToolTip && m_mergeCatalog) { QHelpEvent *helpEvent = static_cast(event); QString text=QStringLiteral("") % QDir::toNativeSeparators(filePath()) % QStringLiteral("\n") % i18nc("@info:tooltip","Different entries: %1\nUnmatched entries: %2", m_mergeCatalog->differentEntries().count(),m_mergeCatalog->unmatchedCount()); text.replace('\n',QStringLiteral("
")); QToolTip::showText(helpEvent->globalPos(),text); return true; } return QWidget::event(event); } diff --git a/src/project/project.cpp b/src/project/project.cpp index 8b2fb80..4ae4112 100644 --- a/src/project/project.cpp +++ b/src/project/project.cpp @@ -1,511 +1,509 @@ /* **************************************************************************** This file is part of Lokalize Copyright (C) 2007-2014 by Nick Shaforostoff 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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, see . **************************************************************************** */ #include "project.h" #include "lokalize_debug.h" #include "projectlocal.h" #include "prefs.h" #include "jobs.h" #include "glossary.h" #include "tmmanager.h" #include "glossarywindow.h" #include "editortab.h" #include "dbfilesmodel.h" #include "qamodel.h" -#include "kdemacros.h" - #include #include #include #include #include #include #include #include #ifndef NOKDE #include "projectmodel.h" #include "webquerycontroller.h" #include #include #include #include #include #include #include #include using namespace Kross; #endif QString getMailingList() { QString lang = QLocale::system().name(); if(lang.startsWith(QLatin1String("ca"))) return QLatin1String("kde-i18n-ca@kde.org"); if(lang.startsWith(QLatin1String("de"))) return QLatin1String("kde-i18n-de@kde.org"); if(lang.startsWith(QLatin1String("hu"))) return QLatin1String("kde-l10n-hu@kde.org"); if(lang.startsWith(QLatin1String("tr"))) return QLatin1String("kde-l10n-tr@kde.org"); if(lang.startsWith(QLatin1String("it"))) return QLatin1String("kde-i18n-it@kde.org"); if(lang.startsWith(QLatin1String("lt"))) return QLatin1String("kde-i18n-lt@kde.org"); if(lang.startsWith(QLatin1String("nb"))) return QLatin1String("i18n-nb@lister.ping.uio.no"); if(lang.startsWith(QLatin1String("nl"))) return QLatin1String("kde-i18n-nl@kde.org"); if(lang.startsWith(QLatin1String("nn"))) return QLatin1String("i18n-nn@lister.ping.uio.no"); if(lang.startsWith(QLatin1String("pt_BR"))) return QLatin1String("kde-i18n-pt_BR@kde.org"); if(lang.startsWith(QLatin1String("ru"))) return QLatin1String("kde-russian@lists.kde.ru"); if(lang.startsWith(QLatin1String("se"))) return QLatin1String("i18n-sme@lister.ping.uio.no"); if(lang.startsWith(QLatin1String("sl"))) return QLatin1String("lugos-slo@lugos.si"); return QLatin1String("kde-i18n-doc@kde.org"); } Project* Project::_instance=0; void Project::cleanupProject() { delete Project::_instance; Project::_instance = 0; } Project* Project::instance() { if (_instance==0 ) { _instance=new Project(); qAddPostRoutine(Project::cleanupProject); } return _instance; } Project::Project() : ProjectBase() , m_localConfig(new ProjectLocal()) , m_model(0) , m_glossary(new GlossaryNS::Glossary(this)) , m_glossaryWindow(0) , m_tmManagerWindow(0) { setDefaults(); /* qRegisterMetaType("DocPosition"); qDBusRegisterMetaType(); */ //QTimer::singleShot(66,this,SLOT(initLater())); } /* void Project::initLater() { if (isLoaded()) return; KConfig cfg; KConfigGroup gr(&cfg,"State"); QString file=gr.readEntry("Project"); if (!file.isEmpty()) load(file); } */ Project::~Project() { delete m_localConfig; //Project::save() } void Project::load(const QString &newProjectPath, const QString& forcedTargetLangCode, const QString& forcedProjectId) { QTime a;a.start(); TM::threadPool()->clear(); qCDebug(LOKALIZE_LOG)<<"loading"<setAutoDelete(true); TM::threadPool()->start(closeDBJob, CLOSEDB); } TM::threadPool()->waitForDone(500);//more safety #ifndef NOKDE setSharedConfig(KSharedConfig::openConfig(newProjectPath, KConfig::NoGlobals)); if (!QFileInfo::exists(newProjectPath)) Project::instance()->setDefaults(); ProjectBase::load(); #else #endif m_path=newProjectPath; m_desirablePath.clear(); //cache: m_projectDir=QFileInfo(m_path).absolutePath(); #ifndef NOKDE m_localConfig->setSharedConfig(KSharedConfig::openConfig(projectID()+QStringLiteral(".local"), KConfig::NoGlobals,QStandardPaths::DataLocation)); m_localConfig->load(); #endif if (forcedTargetLangCode.length()) setLangCode(forcedTargetLangCode); else if (langCode().isEmpty()) setLangCode(QLocale::system().name()); if (forcedProjectId.length()) setProjectID(forcedProjectId); //KConfig config; //delete m_localConfig; m_localConfig=new KConfigGroup(&config,"Project-"+path()); populateDirModel(); //put 'em into thread? //QTimer::singleShot(0,this,SLOT(populateGlossary())); populateGlossary();//we cant postpone it because project load can be called from define new term function m_sourceFilePaths.clear(); if (newProjectPath.isEmpty()) return; if (!isTmSupported()) qCWarning(LOKALIZE_LOG)<<"no sqlite module available"; //NOTE do we need to explicitly call it when project id changes? TM::DBFilesModel::instance()->openDB(projectID(), TM::Undefined, true); if (QaModel::isInstantiated()) { QaModel::instance()->saveRules(); QaModel::instance()->loadRules(qaPath()); } //qCDebug(LOKALIZE_LOG)<<"until emitting signal"<setAutoDelete(true); TM::threadPool()->start(closeDBJob, CLOSEDB); populateDirModel(); populateGlossary(); TM::threadPool()->waitForDone(500);//more safety TM::DBFilesModel::instance()->openDB(projectID(), TM::Undefined, true); } QString Project::absolutePath(const QString& possiblyRelPath) const { if (QFileInfo(possiblyRelPath).isRelative()) return QDir::cleanPath(m_projectDir % QLatin1Char('/') % possiblyRelPath); return possiblyRelPath; } void Project::populateDirModel() { #ifndef NOKDE - if (KDE_ISUNLIKELY( m_path.isEmpty() || !QFileInfo::exists(poDir()) )) + if (Q_UNLIKELY( m_path.isEmpty() || !QFileInfo::exists(poDir()) )) return; QUrl potUrl; if (QFileInfo::exists(potDir())) potUrl=QUrl::fromLocalFile(potDir()); model()->setUrl(QUrl::fromLocalFile(poDir()), potUrl); #endif } void Project::populateGlossary() { m_glossary->load(glossaryPath()); } GlossaryNS::GlossaryWindow* Project::showGlossary() { return defineNewTerm(); } GlossaryNS::GlossaryWindow* Project::defineNewTerm(QString en, QString target) { if (!SettingsController::instance()->ensureProjectIsLoaded()) return 0; if (!m_glossaryWindow) m_glossaryWindow=new GlossaryNS::GlossaryWindow(SettingsController::instance()->mainWindowPtr()); m_glossaryWindow->show(); m_glossaryWindow->activateWindow(); if (!en.isEmpty()||!target.isEmpty()) m_glossaryWindow->newTermEntry(en,target); return m_glossaryWindow; } bool Project::queryCloseForAuxiliaryWindows() { if (m_glossaryWindow && m_glossaryWindow->isVisible()) return m_glossaryWindow->queryClose(); return true; } bool Project::isTmSupported() const { QStringList drivers=QSqlDatabase::drivers(); return drivers.contains(QLatin1String("QSQLITE")); } void Project::showTMManager() { if (!m_tmManagerWindow) { if (!isTmSupported()) { KMessageBox::information(0, i18n("TM facility requires SQLite Qt module."), i18n("No SQLite module available")); return; } m_tmManagerWindow=new TM::TMManagerWin(SettingsController::instance()->mainWindowPtr()); } m_tmManagerWindow->show(); m_tmManagerWindow->activateWindow(); } void Project::save() { m_localConfig->setFirstRun(false); ProjectBase::setTargetLangCode(langCode()); ProjectBase::save(); m_localConfig->save(); } ProjectModel* Project::model() { #ifndef NOKDE - if (KDE_ISUNLIKELY(!m_model)) + if (Q_UNLIKELY(!m_model)) m_model=new ProjectModel(this); return m_model; #else return 0; #endif } void Project::setDefaults() { ProjectBase::setDefaults(); setLangCode(QLocale::system().name()); } void Project::init(const QString& path, const QString& kind, const QString& id, const QString& sourceLang, const QString& targetLang) { setDefaults(); bool stop=false; while(true) { setKind(kind);setSourceLangCode(sourceLang);setLangCode(targetLang);setProjectID(id); if (stop) break; else {load(path);stop=true;} } save(); } static void fillFilePathsRecursive(const QDir& dir, QMultiMap& sourceFilePaths) { QStringList subDirs(dir.entryList(QDir::Dirs|QDir::NoDotAndDotDot|QDir::Readable)); int i=subDirs.size(); while(--i>=0) fillFilePathsRecursive(QDir(dir.filePath(subDirs.at(i))),sourceFilePaths); static QStringList filters = QStringList(QStringLiteral("*.cpp")) <=0) { //qCDebug(LOKALIZE_LOG)<sourceFilePathsAreReady();} protected: bool doKill(); private: QString m_folderName; }; SourceFilesSearchJob::SourceFilesSearchJob(const QString& folderName, QObject* parent) : KJob(parent) , m_folderName(folderName) { setCapabilities(KJob::Killable); } bool SourceFilesSearchJob::doKill() { //TODO return true; } class FillSourceFilePathsJob: public QRunnable { public: explicit FillSourceFilePathsJob(const QDir& dir, SourceFilesSearchJob* j):startingDir(dir),kj(j){} protected: void run() { QMultiMap sourceFilePaths; fillFilePathsRecursive(startingDir, sourceFilePaths); Project::instance()->m_sourceFilePaths = sourceFilePaths; QTimer::singleShot(0, kj, &SourceFilesSearchJob::finish); } public: QDir startingDir; SourceFilesSearchJob* kj; }; void SourceFilesSearchJob::start() { QThreadPool::globalInstance()->start(new FillSourceFilePathsJob(QDir(m_folderName), this)); emit description(this, i18n("Scanning folders with source files"), qMakePair(i18n("Editor"), m_folderName)); } #endif const QMultiMap& Project::sourceFilePaths() { if (m_sourceFilePaths.isEmpty()) { QDir dir(local()->sourceDir()); if (dir.exists()) { #ifndef NOKDE SourceFilesSearchJob* metaJob = new SourceFilesSearchJob(local()->sourceDir()); KIO::getJobTracker()->registerJob(metaJob); metaJob->start(); //KNotification* notification=new KNotification("SourceFileScan", 0); //notification->setText( i18nc("@info","Please wait while %1 is being scanned for source files.", local()->sourceDir()) ); //notification->sendEvent(); #else QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); fillFilePathsRecursive(dir, m_sourceFilePaths); QApplication::restoreOverrideCursor(); #endif } } return m_sourceFilePaths; } #include #include #include "languagelistmodel.h" void Project::projectOdfCreate() { QString odf2xliff=QStringLiteral("odf2xliff"); if (QProcess::execute(odf2xliff, QStringList(QLatin1String("--version")))==-2) { KMessageBox::error(SettingsController::instance()->mainWindowPtr(), i18n("Install translate-toolkit package and retry")); return; } QString odfPath=QFileDialog::getOpenFileName(SettingsController::instance()->mainWindowPtr(), QString(), QDir::homePath()/*_catalog->url().directory()*/, i18n("OpenDocument files (*.odt *.ods)")/*"text/x-lokalize-project"*/); if (odfPath.isEmpty()) return; QString targetLangCode=getTargetLangCode(QString(), true); QFileInfo fi(odfPath); QString trFolderName=i18nc("project folder name. %2 is targetLangCode", "%1 %2 Translation", fi.baseName(), targetLangCode); fi.absoluteDir().mkdir(trFolderName); QStringList args(odfPath); args.append(fi.absoluteDir().absoluteFilePath(trFolderName) % '/' % fi.baseName() % QLatin1String(".xlf")); qCDebug(LOKALIZE_LOG)<load(fi.absoluteDir().absoluteFilePath(trFolderName)+QLatin1String("/index.lokalize"), targetLangCode, fi.baseName()%'-'%targetLangCode); emit fileOpenRequested(args.at(1)); } diff --git a/src/project/projectmodel.cpp b/src/project/projectmodel.cpp index 4c6b5c7..0b1c5f0 100644 --- a/src/project/projectmodel.cpp +++ b/src/project/projectmodel.cpp @@ -1,1457 +1,1455 @@ /* **************************************************************************** This file is part of Lokalize Copyright (C) 2007-2015 by Nick Shaforostoff Copyright (C) 2009 by Viesturs Zarins 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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, see . **************************************************************************** */ #include "projectmodel.h" #include "lokalize_debug.h" #include "project.h" #include "poextractor.h" #include "xliffextractor.h" -#include "kdemacros.h" - #include #include #include #include #include #include #include #include #include #include #include #include static int nodeCounter=0; //TODO: figure out how to handle file and folder renames... //TODO: fix behavior on folder removing, adding. ProjectModel::ProjectModel(QObject *parent) : QAbstractItemModel(parent) , m_poModel(this) , m_potModel(this) , m_rootNode(ProjectNode(NULL, -1, -1, -1)) , m_dirIcon(QIcon::fromTheme(QStringLiteral("inode-directory"))) , m_poIcon(QIcon::fromTheme(QStringLiteral("flag-blue"))) , m_poComplIcon(QIcon::fromTheme(QStringLiteral("flag-green"))) , m_potIcon(QIcon::fromTheme(QStringLiteral("flag-black"))) , m_activeJob(NULL) , m_activeNode(NULL) , m_doneTimer(new QTimer(this)) , m_delayedReloadTimer(new QTimer(this)) , m_threadPool(new QThreadPool(this)) , m_completeScan(true) { m_threadPool->setMaxThreadCount(1); m_threadPool->setExpiryTimeout(-1); m_poModel.dirLister()->setAutoErrorHandlingEnabled(false, NULL); m_poModel.dirLister()->setNameFilter(QStringLiteral("*.po *.pot *.xlf *.xliff *.ts")); m_potModel.dirLister()->setAutoErrorHandlingEnabled(false, NULL); m_potModel.dirLister()->setNameFilter(QStringLiteral("*.pot")); connect(&m_poModel, &KDirModel::dataChanged, this, &ProjectModel::po_dataChanged); connect(&m_poModel, &KDirModel::rowsInserted, this, &ProjectModel::po_rowsInserted); connect(&m_poModel, &KDirModel::rowsRemoved, this, &ProjectModel::po_rowsRemoved); connect(&m_potModel, &KDirModel::dataChanged, this, &ProjectModel::pot_dataChanged); connect(&m_potModel, &KDirModel::rowsInserted, this, &ProjectModel::pot_rowsInserted); connect(&m_potModel, &KDirModel::rowsRemoved, this, &ProjectModel::pot_rowsRemoved); m_delayedReloadTimer->setSingleShot(true); m_doneTimer->setSingleShot(true); connect(m_doneTimer, &QTimer::timeout, this, &ProjectModel::updateTotalsChanged); connect(m_delayedReloadTimer, &QTimer::timeout, this, &ProjectModel::reload); setUrl(QUrl(), QUrl()); } ProjectModel::~ProjectModel() { m_dirsWaitingForMetadata.clear(); if (m_activeJob != NULL) m_activeJob->setStatus(-2); m_activeJob = NULL; for (int pos = 0; pos < m_rootNode.rows.count(); pos ++) deleteSubtree(m_rootNode.rows.at(pos)); } void ProjectModel::setUrl(const QUrl &poUrl, const QUrl &potUrl) { //qCDebug(LOKALIZE_LOG) << "ProjectModel::openUrl("<< poUrl.pathOrUrl() << +", " << potUrl.pathOrUrl() << ")"; emit loadingAboutToStart(); //cleanup old data m_dirsWaitingForMetadata.clear(); if (m_activeJob != NULL) m_activeJob->setStatus(-1); m_activeJob = NULL; if (m_rootNode.rows.count()) { beginRemoveRows(QModelIndex(), 0, m_rootNode.rows.count()); for (int pos = 0; pos < m_rootNode.rows.count(); pos ++) deleteSubtree(m_rootNode.rows.at(pos)); m_rootNode.rows.clear(); m_rootNode.poCount = 0; m_rootNode.translated = -1; m_rootNode.translated_reviewer = -1; m_rootNode.translated_approver = -1; m_rootNode.untranslated = -1; m_rootNode.fuzzy = -1; m_rootNode.fuzzy_reviewer = -1; m_rootNode.fuzzy_approver = -1; endRemoveRows(); } //add trailing slashes to base URLs, needed for potToPo and poToPot m_poUrl = poUrl.adjusted(QUrl::StripTrailingSlash); m_potUrl = potUrl.adjusted(QUrl::StripTrailingSlash); if (!poUrl.isEmpty()) m_poModel.dirLister()->openUrl(m_poUrl, KDirLister::Reload); if (!potUrl.isEmpty()) m_potModel.dirLister()->openUrl(m_potUrl, KDirLister::Reload); } QUrl ProjectModel::beginEditing(const QModelIndex& index) { Q_ASSERT(index.isValid()); QModelIndex poIndex = poIndexForOuter(index); QModelIndex potIndex = potIndexForOuter(index); if (poIndex.isValid()) { KFileItem item = m_poModel.itemForIndex(poIndex); return item.url(); } else if (potIndex.isValid()) { //copy over the file QUrl potFile = m_potModel.itemForIndex(potIndex).url(); QUrl poFile = potToPo(potFile); //EditorTab::fileOpen takes care of this //be careful, copy only if file does not exist already. // if (!KIO::NetAccess::exists(poFile, KIO::NetAccess::DestinationSide, NULL)) // KIO::NetAccess::file_copy(potFile, poFile); return poFile; } else { Q_ASSERT(false); return QUrl(); } } void ProjectModel::reload() { setUrl(m_poUrl, m_potUrl); } //Theese methds update the combined model from POT and PO model changes. //Quite complex stuff here, better do not change anything. //TODO A comment from Viesturs Zarins 2009-05-17 20:53:11 UTC: //This is a design issue in projectview.cpp. The same issue happens when creating/deleting any folder in project. //When a node PO item is added, the existing POT node is deleted and new one created to represent both. //When view asks if there is more data in the new node, the POT model answers no, as all the data was already stored in POT node witch is now deleted. //To fix this either reuse the existing POT node or manually repopulate data form POT model. void ProjectModel::po_dataChanged(const QModelIndex& po_topLeft, const QModelIndex& po_bottomRight) { //nothing special here //map from source and propagate QModelIndex topLeft = indexForPoIndex(po_topLeft); QModelIndex bottomRight = indexForPoIndex(po_bottomRight); if (topLeft.row()==bottomRight.row() && itemForIndex(topLeft).isFile()) { //this code works fine only for lonely files //and fails for more complex changes //see bug 342959 emit dataChanged(topLeft, bottomRight); enqueueNodeForMetadataUpdate(nodeForIndex(topLeft.parent())); } else m_delayedReloadTimer->start(1000); } void ProjectModel::pot_dataChanged(const QModelIndex& pot_topLeft, const QModelIndex& pot_bottomRight) { #if 0 //tricky here - some of the pot items may be represented by po items //let's propagate that all subitems changed QModelIndex pot_parent = pot_topLeft.parent(); QModelIndex parent = indexForPotIndex(pot_parent); ProjectNode* node = nodeForIndex(parent); int count = node->rows.count(); QModelIndex topLeft = index(0, pot_topLeft.column(), parent); QModelIndex bottomRight = index(count-1, pot_bottomRight.column(), parent); emit dataChanged(topLeft, bottomRight); enqueueNodeForMetadataUpdate(nodeForIndex(topLeft.parent())); #else Q_UNUSED(pot_topLeft) Q_UNUSED(pot_bottomRight) m_delayedReloadTimer->start(1000); #endif } void ProjectModel::po_rowsInserted(const QModelIndex& po_parent, int first, int last) { QModelIndex parent = indexForPoIndex(po_parent); QModelIndex pot_parent = potIndexForOuter(parent); ProjectNode* node = nodeForIndex(parent); //insert po rows beginInsertRows(parent, first, last); for (int pos = first; pos <= last; pos ++) { ProjectNode * childNode = new ProjectNode(node, pos, pos, -1); node->rows.insert(pos, childNode); } node->poCount += last - first + 1; //update rowNumber for (int pos = last + 1; pos < node->rows.count(); pos++) node->rows[pos]->rowNumber = pos; endInsertRows(); //remove unneeded pot rows, update PO rows if (pot_parent.isValid() || !parent.isValid()) { QVector pot2PoMapping; generatePOTMapping(pot2PoMapping, po_parent, pot_parent); for (int pos = node->poCount; pos < node->rows.count(); pos ++) { ProjectNode* potNode = node->rows.at(pos); int potIndex = potNode->potRowNumber; int poIndex = pot2PoMapping[potIndex]; if (poIndex != -1) { //found pot node, that now has a PO index. //remove the pot node and change the corresponding PO node beginRemoveRows(parent, pos, pos); node->rows.remove(pos); deleteSubtree(potNode); endRemoveRows(); node->rows[poIndex]->potRowNumber = potIndex; //This change does not need notification //dataChanged(index(poIndex, 0, parent), index(poIndex, ProjectModelColumnCount, parent)); pos--; } } } enqueueNodeForMetadataUpdate(node); } void ProjectModel::pot_rowsInserted(const QModelIndex& pot_parent, int start, int end) { QModelIndex parent = indexForPotIndex(pot_parent); QModelIndex po_parent = poIndexForOuter(parent); ProjectNode* node = nodeForIndex(parent); int insertedCount = end + 1 - start; QVector newPotNodes; if (po_parent.isValid() || !parent.isValid()) { //this node containts mixed items - add and merge the stuff QVector pot2PoMapping; generatePOTMapping(pot2PoMapping, po_parent, pot_parent); //reassign affected PO row POT indices for (int pos = 0; pos < node->poCount;pos ++) { ProjectNode* n=node->rows[pos]; if (n->potRowNumber >= start) n->potRowNumber += insertedCount; } //assign new POT indices for (int potIndex = start; potIndex <= end; potIndex ++) { int poIndex = pot2PoMapping[potIndex]; if (poIndex != -1) { //found pot node, that has a PO index. //change the corresponding PO node node->rows[poIndex]->potRowNumber = potIndex; //This change does not need notification //dataChanged(index(poIndex, 0, parent), index(poIndex, ProjectModelColumnCount, parent)); } else newPotNodes.append(potIndex); } } else { for (int pos = start; pos < end; pos ++) newPotNodes.append(pos); } //insert standalone POT rows, preserving POT order int newNodesCount = newPotNodes.count(); if (newNodesCount) { int insertionPoint = node->poCount; while ((insertionPoint < node->rows.count()) && (node->rows[insertionPoint]->potRowNumber < start)) insertionPoint++; beginInsertRows(parent, insertionPoint, insertionPoint + newNodesCount - 1); for (int pos = 0; pos < newNodesCount; pos ++) { int potIndex = newPotNodes.at(pos); ProjectNode * childNode = new ProjectNode(node, insertionPoint, -1, potIndex); node->rows.insert(insertionPoint, childNode); insertionPoint++; } //renumber remaining POT rows for (int pos = insertionPoint; pos < node->rows.count(); pos ++) { node->rows[pos]->rowNumber = pos; node->rows[pos]->potRowNumber += insertedCount; } endInsertRows(); } enqueueNodeForMetadataUpdate(node); //FIXME if templates folder doesn't contain an equivalent of po folder then it's stats will be broken: // one way to fix this is to explicitly force scan of the files of the child folders of the 'node' } void ProjectModel::po_rowsRemoved(const QModelIndex& po_parent, int start, int end) { QModelIndex parent = indexForPoIndex(po_parent); //QModelIndex pot_parent = potIndexForOuter(parent); ProjectNode* node = nodeForIndex(parent); int removedCount = end + 1 - start; if ((!parent.isValid()) && (node->rows.count() == 0)) { qCDebug(LOKALIZE_LOG)<<"po_rowsRemoved fail"; //events after removing entire contents return; } //remove PO rows QList potRowsToInsert; beginRemoveRows(parent, start, end); //renumber all rows after removed. for (int pos = end + 1; pos < node->rows.count(); pos ++) { ProjectNode* childNode = node->rows.at(pos); childNode->rowNumber -= removedCount; if (childNode->poRowNumber > end) node->rows[pos]->poRowNumber -= removedCount; } //remove for (int pos = end; pos >= start; pos --) { int potIndex = node->rows.at(pos)->potRowNumber; deleteSubtree(node->rows.at(pos)); node->rows.remove(pos); if (potIndex != -1) potRowsToInsert.append(potIndex); } node->poCount -= removedCount; endRemoveRows(); //< fires removed event - the list has to be consistent now //add back rows that have POT files and fix row order qSort(potRowsToInsert.begin(), potRowsToInsert.end()); int insertionPoint = node->poCount; for (int pos = 0; pos < potRowsToInsert.count(); pos ++) { int potIndex = potRowsToInsert.at(pos); while (insertionPoint < node->rows.count() && node->rows[insertionPoint]->potRowNumber < potIndex) { node->rows[insertionPoint]->rowNumber = insertionPoint; insertionPoint ++; } beginInsertRows(parent, insertionPoint, insertionPoint); ProjectNode * childNode = new ProjectNode(node, insertionPoint, -1, potIndex); node->rows.insert(insertionPoint, childNode); insertionPoint++; endInsertRows(); } //renumber remaining rows while (insertionPoint < node->rows.count()) { node->rows[insertionPoint]->rowNumber = insertionPoint; insertionPoint++; } enqueueNodeForMetadataUpdate(node); } void ProjectModel::pot_rowsRemoved(const QModelIndex& pot_parent, int start, int end) { QModelIndex parent = indexForPotIndex(pot_parent); QModelIndex po_parent = poIndexForOuter(parent); ProjectNode * node = nodeForIndex(parent); int removedCount = end + 1 - start; if ((!parent.isValid()) && (node->rows.count() == 0)) { //events after removing entire contents return; } //First remove POT nodes int firstPOTToRemove = node->poCount; int lastPOTToRemove = node->rows.count() - 1; while (firstPOTToRemove <= lastPOTToRemove && node->rows[firstPOTToRemove]->potRowNumber < start) firstPOTToRemove ++; while (lastPOTToRemove >= firstPOTToRemove && node->rows[lastPOTToRemove]->potRowNumber > end) lastPOTToRemove --; if (firstPOTToRemove <= lastPOTToRemove) { beginRemoveRows(parent, firstPOTToRemove, lastPOTToRemove); for (int pos = lastPOTToRemove; pos >= firstPOTToRemove; pos --) { ProjectNode* childNode = node->rows.at(pos); Q_ASSERT(childNode->potRowNumber >= start); Q_ASSERT(childNode->potRowNumber <= end); deleteSubtree(childNode); node->rows.remove(pos); } //renumber remaining rows for (int pos = firstPOTToRemove; pos < node->rows.count(); pos ++) { node->rows[pos]->rowNumber = pos; node->rows[pos]->potRowNumber -= removedCount; } endRemoveRows(); } //now remove POT indices form PO rows if (po_parent.isValid() || !parent.isValid()) { for (int poIndex = 0; poIndex < node->poCount; poIndex ++) { ProjectNode * childNode = node->rows[poIndex]; int potIndex = childNode->potRowNumber; if (potIndex >= start && potIndex <= end) { //found PO node, that has a POT index in range. //change the corresponding PO node node->rows[poIndex]->potRowNumber = -1; //this change does not affect the model //dataChanged(index(poIndex, 0, parent), index(poIndex, ProjectModelColumnCount, parent)); } else if (childNode->potRowNumber > end) { //reassign POT indices childNode->potRowNumber -= removedCount; } } } enqueueNodeForMetadataUpdate(node); } int ProjectModel::columnCount(const QModelIndex& /*parent*/)const { return ProjectModelColumnCount; } QVariant ProjectModel::headerData(int section, Qt::Orientation, int role) const { if (role!=Qt::DisplayRole) return QVariant(); switch (section) { case FileName: return i18nc("@title:column File name","Name"); case Graph: return i18nc("@title:column Graphical representation of Translated/Fuzzy/Untranslated counts","Graph"); case TotalCount: return i18nc("@title:column Number of entries","Total"); case TranslatedCount: return i18nc("@title:column Number of entries","Translated"); case FuzzyCount: return i18nc("@title:column Number of entries","Not ready"); case UntranslatedCount: return i18nc("@title:column Number of entries","Untranslated"); case TranslationDate: return i18nc("@title:column","Last Translation"); case SourceDate: return i18nc("@title:column","Template Revision"); case LastTranslator: return i18nc("@title:column","Last Translator"); default: return QVariant(); } } Qt::ItemFlags ProjectModel::flags( const QModelIndex & index ) const { if (index.column() == FileName) return Qt::ItemIsSelectable|Qt::ItemIsEnabled; else return Qt::ItemIsSelectable; } int ProjectModel::rowCount ( const QModelIndex & parent /*= QModelIndex()*/ ) const { return nodeForIndex(parent)->rows.size(); } bool ProjectModel::hasChildren ( const QModelIndex & parent /*= QModelIndex()*/ ) const { if (!parent.isValid()) return true; QModelIndex poIndex = poIndexForOuter(parent); QModelIndex potIndex = potIndexForOuter(parent); return ((poIndex.isValid() && m_poModel.hasChildren(poIndex)) || (potIndex.isValid() && m_potModel.hasChildren(potIndex))); } bool ProjectModel::canFetchMore ( const QModelIndex & parent ) const { if (!parent.isValid()) return m_poModel.canFetchMore(QModelIndex()) || m_potModel.canFetchMore(QModelIndex()); QModelIndex poIndex = poIndexForOuter(parent); QModelIndex potIndex = potIndexForOuter(parent); return ((poIndex.isValid() && m_poModel.canFetchMore(poIndex)) || (potIndex.isValid() && m_potModel.canFetchMore(potIndex))); } void ProjectModel::fetchMore ( const QModelIndex & parent ) { if (!parent.isValid()) { if (m_poModel.canFetchMore(QModelIndex())) m_poModel.fetchMore(QModelIndex()); if (m_potModel.canFetchMore(QModelIndex())) m_potModel.fetchMore(QModelIndex()); } else { QModelIndex poIndex = poIndexForOuter(parent); QModelIndex potIndex = potIndexForOuter(parent); if (poIndex.isValid() && (m_poModel.canFetchMore(poIndex))) m_poModel.fetchMore(poIndex); if (potIndex.isValid() && (m_potModel.canFetchMore(potIndex))) m_potModel.fetchMore(potIndex); } } /** * we use QRect to pass data through QVariant tunnel * * order is tran, untr, fuzzy * left() top() width() * */ QVariant ProjectModel::data(const QModelIndex& index, int role) const { if (!index.isValid()) return QVariant(); const ProjectModelColumns& column=(ProjectModelColumns)index.column(); ProjectNode* node = nodeForIndex(index); QModelIndex internalIndex = poOrPotIndexForOuter(index); if (!internalIndex.isValid()) return QVariant(); KFileItem item=itemForIndex(index); bool isDir = item.isDir(); int translated = node->translatedAsPerRole(); int fuzzy = node->fuzzyAsPerRole(); int untranslated = node->untranslated; bool hasStats = translated != -1; switch(role) { case Qt::DisplayRole: switch (column) { case FileName: return item.text(); case Graph: return hasStats?QRect(translated, untranslated, fuzzy, 0):QVariant(); case TotalCount: return hasStats?(translated + untranslated + fuzzy):QVariant(); case TranslatedCount:return hasStats?translated:QVariant(); case FuzzyCount: return hasStats?fuzzy:QVariant(); case UntranslatedCount:return hasStats?untranslated:QVariant(); case SourceDate: return node->sourceDate; case TranslationDate:return node->translationDate; case LastTranslator:return node->lastTranslator; default: return QVariant(); } case Qt::ToolTipRole: switch (column) { case FileName: return item.text(); default: return QVariant(); } case KDirModel::FileItemRole: return QVariant::fromValue(item); case Qt::DecorationRole: switch (column) { case FileName: if (isDir) return m_dirIcon; if (hasStats && fuzzy == 0 && untranslated == 0) return m_poComplIcon; else if (node->poRowNumber != -1) return m_poIcon; else if (node->potRowNumber != -1) return m_potIcon; default: return QVariant(); } case FuzzyUntrCountRole: return item.isFile()?(fuzzy + untranslated):0; case FuzzyCountRole: return item.isFile()?fuzzy:0; case UntransCountRole: return item.isFile()?untranslated:0; case TemplateOnlyRole: return item.isFile()?(node->poRowNumber == -1):0; case TransOnlyRole: return item.isFile()?(node->potRowNumber == -1):0; case TotalRole: return hasStats?(fuzzy + untranslated + translated):0; default: return QVariant(); } } QModelIndex ProjectModel::index(int row, int column, const QModelIndex& parent) const { ProjectNode* parentNode = nodeForIndex(parent); //qCWarning(LOKALIZE_LOG)<<(sizeof(ProjectNode))<=parentNode->rows.size()) { qCWarning(LOKALIZE_LOG)<<"Issues with indexes"<rows.size()<rows.at(row)); } KFileItem ProjectModel::itemForIndex(const QModelIndex& index) const { if (!index.isValid()) { //file item for root node. return m_poModel.itemForIndex(index); } QModelIndex poIndex = poIndexForOuter(index); if (poIndex.isValid()) return m_poModel.itemForIndex(poIndex); else { QModelIndex potIndex = potIndexForOuter(index); if (potIndex.isValid()) return m_potModel.itemForIndex(potIndex); } qCInfo(LOKALIZE_LOG)<<"returning empty KFileItem()"<(index.internalPointer())->untranslated<(index.internalPointer())->sourceDate; return KFileItem(); } ProjectModel::ProjectNode* ProjectModel::nodeForIndex(const QModelIndex& index) const { if (index.isValid()) { ProjectNode * node = static_cast(index.internalPointer()); Q_ASSERT(node != NULL); return node; } else { ProjectNode * node = const_cast(&m_rootNode); Q_ASSERT(node != NULL); return node; } } QModelIndex ProjectModel::indexForNode(const ProjectNode* node) { if (node == &m_rootNode) return QModelIndex(); int row = node->rowNumber; QModelIndex index = createIndex(row, 0, (void*)node); return index; } QModelIndex ProjectModel::indexForUrl(const QUrl& url) { if (m_poUrl.isParentOf(url)) { QModelIndex poIndex = m_poModel.indexForUrl(url); return indexForPoIndex(poIndex); } else if (m_potUrl.isParentOf(url)) { QModelIndex potIndex = m_potModel.indexForUrl(url); return indexForPotIndex(potIndex); } return QModelIndex(); } QModelIndex ProjectModel::parent(const QModelIndex& childIndex) const { if (!childIndex.isValid()) return QModelIndex(); ProjectNode* childNode = nodeForIndex(childIndex); ProjectNode* parentNode = childNode->parent; if (!parentNode || (childNode == &m_rootNode) || (parentNode == &m_rootNode)) return QModelIndex(); return createIndex(parentNode->rowNumber, 0, parentNode); } /** * Theese methods map from project model indices to PO and POT model indices. * In each folder files form PO model comes first, and files from POT that do not exist in PO model come after. */ QModelIndex ProjectModel::indexForOuter(const QModelIndex& outerIndex, IndexType type) const { if (!outerIndex.isValid()) return QModelIndex(); QModelIndex parent = outerIndex.parent(); QModelIndex internalParent; if (parent.isValid()) { internalParent = indexForOuter(parent, type); if (!internalParent.isValid()) return QModelIndex(); } ProjectNode* node = nodeForIndex(outerIndex); short rowNumber=(type==PoIndex?node->poRowNumber:node->potRowNumber); if (rowNumber == -1) return QModelIndex(); return (type==PoIndex?m_poModel:m_potModel).index(rowNumber, outerIndex.column(), internalParent); } QModelIndex ProjectModel::poIndexForOuter(const QModelIndex& outerIndex) const { return indexForOuter(outerIndex, PoIndex); } QModelIndex ProjectModel::potIndexForOuter(const QModelIndex& outerIndex) const { return indexForOuter(outerIndex, PotIndex); } QModelIndex ProjectModel::poOrPotIndexForOuter(const QModelIndex& outerIndex) const { if (!outerIndex.isValid()) return QModelIndex(); QModelIndex poIndex = poIndexForOuter(outerIndex); if (poIndex.isValid()) return poIndex; QModelIndex potIndex = potIndexForOuter(outerIndex); if (!potIndex.isValid()) qCWarning(LOKALIZE_LOG)<<"error mapping index to PO or POT"; return potIndex; } QModelIndex ProjectModel::indexForPoIndex(const QModelIndex& poIndex) const { if (!poIndex.isValid()) return QModelIndex(); QModelIndex outerParent = indexForPoIndex(poIndex.parent()); int row = poIndex.row(); //keep the same row, no changes return index(row, poIndex.column(), outerParent); } QModelIndex ProjectModel::indexForPotIndex(const QModelIndex& potIndex) const { if (!potIndex.isValid()) return QModelIndex(); QModelIndex outerParent = indexForPotIndex(potIndex.parent()); ProjectNode* node = nodeForIndex(outerParent); int potRow = potIndex.row(); int row = 0; while(rowrows.count() && node->rows.at(row)->potRowNumber!=potRow) row++; if (row != node->rows.count()) return index(row, potIndex.column(), outerParent); qCWarning(LOKALIZE_LOG)<<"error mapping index from POT to outer, searched for potRow:"< & result, const QModelIndex& poParent, const QModelIndex& potParent) const { result.clear(); int poRows = m_poModel.rowCount(poParent); int potRows = m_potModel.rowCount(potParent); if (potRows == 0) return; QList poOccupiedUrls; for (int poPos = 0; poPos < poRows; poPos ++) { KFileItem file = m_poModel.itemForIndex(m_poModel.index(poPos, 0, poParent)); QUrl potUrl = poToPot(file.url()); poOccupiedUrls.append(potUrl); } for (int potPos = 0; potPos < potRows; potPos ++) { QUrl potUrl = m_potModel.itemForIndex(m_potModel.index(potPos, 0, potParent)).url(); int occupiedPos = -1; //TODO: this is slow for (int poPos = 0; occupiedPos == -1 && poPos < poOccupiedUrls.count(); poPos ++) { QUrl& occupiedUrl = poOccupiedUrls[poPos]; if (potUrl.matches(occupiedUrl, QUrl::StripTrailingSlash)) occupiedPos = poPos; } result.append(occupiedPos); } } QUrl ProjectModel::poToPot(const QUrl& poPath) const { if (!(m_poUrl.isParentOf(poPath)||m_poUrl.matches(poPath, QUrl::StripTrailingSlash))) { qCWarning(LOKALIZE_LOG)<<"PO path not in project: " << poPath.url(); return QUrl(); } QString pathToAdd = QDir(m_poUrl.path()).relativeFilePath(poPath.path()); //change ".po" into ".pot" if (pathToAdd.endsWith(QLatin1String(".po"))) //TODO: what about folders ?? pathToAdd+='t'; QUrl potPath = m_potUrl; potPath.setPath(potPath.path()%'/'%pathToAdd); //qCDebug(LOKALIZE_LOG) << "ProjectModel::poToPot("<< poPath.pathOrUrl() << +") = " << potPath.pathOrUrl(); return potPath; } QUrl ProjectModel::potToPo(const QUrl& potPath) const { if (!(m_potUrl.isParentOf(potPath)||m_potUrl.matches(potPath, QUrl::StripTrailingSlash))) { qCWarning(LOKALIZE_LOG)<<"POT path not in project: " << potPath.url(); return QUrl(); } QString pathToAdd = QDir(m_potUrl.path()).relativeFilePath(potPath.path()); //change ".pot" into ".po" if (pathToAdd.endsWith(QLatin1String(".pot"))) //TODO: what about folders ?? pathToAdd = pathToAdd.left(pathToAdd.length() - 1); QUrl poPath = m_poUrl; poPath.setPath(poPath.path()%'/'%pathToAdd); //qCDebug(LOKALIZE_LOG) << "ProjectModel::potToPo("<< potPath.pathOrUrl() << +") = " << poPath.pathOrUrl(); return poPath; } //Metadata stuff //For updating translation stats void ProjectModel::enqueueNodeForMetadataUpdate(ProjectNode* node) { m_doneTimer->stop(); if (m_dirsWaitingForMetadata.contains(node)) { if ((m_activeJob != NULL) && (m_activeNode == node)) m_activeJob->setStatus(-1); return; } m_dirsWaitingForMetadata.insert(node); if (m_activeJob == NULL) startNewMetadataJob(); } void ProjectModel::deleteSubtree(ProjectNode* node) { for (int row = 0; row < node->rows.count(); row ++) deleteSubtree(node->rows.at(row)); m_dirsWaitingForMetadata.remove(node); if ((m_activeJob != NULL) && (m_activeNode == node)) m_activeJob->setStatus(-1); delete node; } void ProjectModel::startNewMetadataJob() { if (!m_completeScan) //hack for debugging return; m_activeJob = NULL; m_activeNode = NULL; if (m_dirsWaitingForMetadata.isEmpty()) return; ProjectNode* node = *m_dirsWaitingForMetadata.constBegin(); //prepare new work m_activeNode = node; QList files; QModelIndex item = indexForNode(node); for (int row=0; row < node->rows.count(); row ++) files.append(itemForIndex(index(row, 0, item))); m_activeJob = new UpdateStatsJob(files, this); connect(m_activeJob, &UpdateStatsJob::done, this, &ProjectModel::finishMetadataUpdate); m_threadPool->start(m_activeJob); } void ProjectModel::finishMetadataUpdate(UpdateStatsJob* job) { if (job->m_status == -2) { delete job; return; } if ((m_dirsWaitingForMetadata.contains(m_activeNode)) && (job->m_status == 0)) { m_dirsWaitingForMetadata.remove(m_activeNode); //store the results setMetadataForDir(m_activeNode, m_activeJob->m_info); QModelIndex item = indexForNode(m_activeNode); //scan dubdirs - initiate data loading into the model. for (int row=0; row < m_activeNode->rows.count(); row++) { QModelIndex child = index(row, 0, item); if (canFetchMore(child)) fetchMore(child); //QCoreApplication::processEvents(); } } delete m_activeJob; m_activeJob = 0; startNewMetadataJob(); } void ProjectModel::slotFileSaved(const QString& filePath) { QModelIndex index = indexForUrl(QUrl::fromLocalFile(filePath)); if (!index.isValid()) return; QList files; files.append(itemForIndex(index)); UpdateStatsJob* j = new UpdateStatsJob(files); connect(j, &UpdateStatsJob::done, this, &ProjectModel::finishSingleMetadataUpdate); m_threadPool->start(j); } void ProjectModel::finishSingleMetadataUpdate(UpdateStatsJob* job) { if (job->m_status != 0) { delete job; return; } const FileMetaData& info=job->m_info.first(); QModelIndex index = indexForUrl(QUrl::fromLocalFile(info.filePath)); if (!index.isValid()) return; ProjectNode* node = nodeForIndex(index); node->setFileStats(job->m_info.first()); updateDirStats(nodeForIndex(index.parent())); QModelIndex topLeft = index.sibling(index.row(), Graph); QModelIndex bottomRight = index.sibling(index.row(), ProjectModelColumnCount - 1); emit dataChanged(topLeft, bottomRight); delete job; } void ProjectModel::setMetadataForDir(ProjectNode* node, const QList& data) { int dataCount = data.count(); int rowsCount = node->rows.count(); //Q_ASSERT(dataCount == rowsCount); if (dataCount != rowsCount) { m_delayedReloadTimer->start(2000); qCWarning(LOKALIZE_LOG)<<"dataCount != rowsCount, scheduling full refresh"; return; } for (int row = 0; row < rowsCount; row++) node->rows[row]->setFileStats(data.at(row)); if (!dataCount) return; updateDirStats(node); QModelIndex item = indexForNode(node); QModelIndex topLeft = index(0, Graph, item); QModelIndex bottomRight = index(rowsCount - 1, ProjectModelColumnCount - 1, item); emit dataChanged(topLeft, bottomRight); } void ProjectModel::updateDirStats(ProjectNode* node) { node->calculateDirStats(); if (node == &m_rootNode) { updateTotalsChanged(); return; } updateDirStats(node->parent); if (node->parent->rows.count()==0 || node->parent->rows.count()>=node->rowNumber) return; QModelIndex index = indexForNode(node); qCDebug(LOKALIZE_LOG)<parent->rows.count(); if (index.row()>=node->parent->rows.count()) return; QModelIndex topLeft = index.sibling(index.row(), Graph); QModelIndex bottomRight = index.sibling(index.row(), ProjectModelColumnCount - 1); emit dataChanged(topLeft, bottomRight); } bool ProjectModel::updateDone(const QModelIndex& index, const KDirModel& model) { if (model.canFetchMore(index)) return false; int row=model.rowCount(index); while (--row>=0) { if (!updateDone(model.index(row, 0, index), model)) return false; } return true; } void ProjectModel::updateTotalsChanged() { bool done = m_dirsWaitingForMetadata.isEmpty(); if (done) { done = updateDone(m_poModel.indexForUrl(m_poUrl), m_poModel) && updateDone(m_potModel.indexForUrl(m_potUrl), m_potModel); if (m_rootNode.fuzzyAsPerRole() + m_rootNode.translatedAsPerRole() + m_rootNode.untranslated > 0 && !done) m_doneTimer->start(2000); emit loadingFinished(); } emit totalsChanged(m_rootNode.fuzzyAsPerRole(), m_rootNode.translatedAsPerRole(), m_rootNode.untranslated, done); } //ProjectNode class ProjectModel::ProjectNode::ProjectNode(ProjectNode* _parent, int _rowNum, int _poIndex, int _potIndex) : parent(_parent) , rowNumber(_rowNum) , poRowNumber(_poIndex) , potRowNumber(_potIndex) , poCount(0) , translated(-1) , translated_reviewer(-1) , translated_approver(-1) , untranslated(-10) , fuzzy(-1) , fuzzy_reviewer(-10) , fuzzy_approver(-10) { ++nodeCounter; } ProjectModel::ProjectNode::~ProjectNode() { --nodeCounter; } void ProjectModel::ProjectNode::calculateDirStats() { fuzzy = 0; fuzzy_reviewer = 0; fuzzy_approver = 0; translated = 0; translated_reviewer = 0; translated_approver = 0; untranslated = 0; for (int pos = 0; pos < rows.count(); pos++) { ProjectNode* child = rows.at(pos); if (child->translated != -1) { fuzzy += child->fuzzy; fuzzy_reviewer += child->fuzzy_reviewer; fuzzy_approver += child->fuzzy_approver; translated += child->translated; translated_reviewer += child->translated_reviewer; translated_approver += child->translated_approver; untranslated += child->untranslated; } } } void ProjectModel::ProjectNode::setFileStats(const FileMetaData& info) { translated = info.translated; translated_reviewer = info.translated_reviewer; translated_approver = info.translated_approver; untranslated = info.untranslated; fuzzy = info.fuzzy; fuzzy_reviewer = info.fuzzy_reviewer; fuzzy_approver = info.fuzzy_approver; lastTranslator = info.lastTranslator; sourceDate = info.sourceDate; translationDate = info.translationDate; } //BEGIN UpdateStatsJob //these are run in separate thread UpdateStatsJob::UpdateStatsJob(QList files, QObject*) : QRunnable() , m_files(files) , m_status(0) { setAutoDelete(false); } UpdateStatsJob::~UpdateStatsJob() { } static FileMetaData metaData(QString filePath) { FileMetaData m; if (filePath.endsWith(QLatin1String(".po"))||filePath.endsWith(QLatin1String(".pot"))) { POExtractor extractor; extractor.extract(filePath, m); } else if (filePath.endsWith(QLatin1String(".xlf"))||filePath.endsWith(QLatin1String(".xliff"))) { XliffExtractor extractor; extractor.extract(filePath, m); } else if (filePath.endsWith(QLatin1String(".ts"))) { //POExtractor extractor; //extractor.extract(filePath, m); } return m; } //#define NOMETAINFOCACHE #ifndef NOMETAINFOCACHE static void initDataBase(QSqlDatabase& db) { QSqlQuery queryMain(db); queryMain.exec(QStringLiteral("PRAGMA encoding = \"UTF-8\"")); queryMain.exec(QStringLiteral( "CREATE TABLE IF NOT EXISTS metadata (" "filepath INTEGER PRIMARY KEY ON CONFLICT REPLACE, "// AUTOINCREMENT," //"filepath TEXT UNIQUE ON CONFLICT REPLACE, " "metadata BLOB, "//XLIFF markup info, see catalog/catalogstring.h catalog/xliff/* "changedate INTEGER" ")")); //queryMain.exec("CREATE INDEX IF NOT EXISTS filepath_index ON metainfo ("filepath)"); } QDataStream &operator<<(QDataStream &s, const FileMetaData &d) { s << d.translated; s << d.translated_approver; s << d.translated_reviewer; s << d.fuzzy; s << d.fuzzy_approver; s << d.fuzzy_reviewer; s << d.untranslated; s << d.lastTranslator; s << d.translationDate; s << d.sourceDate; return s; } QDataStream &operator>>(QDataStream &s, FileMetaData &d) { s >> d.translated; s >> d.translated_approver; s >> d.translated_reviewer; s >> d.fuzzy; s >> d.fuzzy_approver; s >> d.fuzzy_reviewer; s >> d.untranslated; s >> d.lastTranslator; s >> d.translationDate; s >> d.sourceDate; return s; } #endif static FileMetaData cachedMetaData(const KFileItem& file) { if (file.isNull() || file.isDir()) return FileMetaData(); #ifdef NOMETAINFOCACHE return metaData(file.localPath()); #else QString dbName=QStringLiteral("metainfocache"); if (!QSqlDatabase::contains(dbName)) { QSqlDatabase db=QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"),dbName); db.setDatabaseName(QStandardPaths::writableLocation(QStandardPaths::DataLocation) % QLatin1Char('/') % dbName % QLatin1String(".sqlite")); - if (KDE_ISUNLIKELY( !db.open() )) + if (Q_UNLIKELY( !db.open() )) return metaData(file.localPath()); initDataBase(db); } QSqlDatabase db=QSqlDatabase::database(dbName); if (!db.isOpen()) return metaData(file.localPath()); QByteArray result; QSqlQuery queryCache(db); queryCache.prepare(QStringLiteral("SELECT * from metadata where filepath=?")); queryCache.bindValue(0, qHash(file.localPath())); queryCache.exec(); //not using file.time(KFileItem::ModificationTime) because it gives wrong result for files that have just been saved in editor if (queryCache.next() && QFileInfo(file.localPath()).lastModified()==queryCache.value(2).toDateTime()) { result=queryCache.value(1).toByteArray(); QDataStream stream(&result,QIODevice::ReadOnly); FileMetaData info; stream>>info; Q_ASSERT(info.translated==metaData(file.localPath()).translated); return info; } FileMetaData m = metaData(file.localPath()); QDataStream stream(&result,QIODevice::WriteOnly); //this is synced with ProjectModel::ProjectNode::setFileStats stream< 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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, see . **************************************************************************** */ #include "projectwidget.h" #include "lokalize_debug.h" #include "project.h" #include "catalog.h" #include "headerviewmenu.h" #include #include #include #include -#include #include #include #include #include #include #include #include #include #include #include class PoItemDelegate: public QStyledItemDelegate { public: PoItemDelegate(QObject *parent=0); ~PoItemDelegate(){} void paint (QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const; private: KColorScheme m_colorScheme; }; PoItemDelegate::PoItemDelegate(QObject *parent) : QStyledItemDelegate(parent) , m_colorScheme(QPalette::Normal) {} QSize PoItemDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const { QString text=index.data().toString(); int lineCount=1; int nPos=text.indexOf('\n'); if (nPos==-1) nPos=text.size(); else lineCount+=text.count('\n'); static QFontMetrics metrics(option.font); return QSize(metrics.averageCharWidth()*nPos, metrics.height()*lineCount); } void PoItemDelegate::paint (QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { if (index.column() != ProjectModel::Graph) return QStyledItemDelegate::paint(painter,option,index); QVariant graphData = index.data(Qt::DisplayRole); - if (KDE_ISUNLIKELY( !graphData.isValid())) + if (Q_UNLIKELY( !graphData.isValid())) { painter->fillRect(option.rect,Qt::transparent); return; } QRect rect = graphData.toRect(); int translated = rect.left(); int untranslated = rect.top(); int fuzzy = rect.width(); int total = translated + untranslated + fuzzy; if (total > 0) { QBrush brush; painter->setPen(Qt::white); QRect myRect(option.rect); myRect.setWidth(option.rect.width() * translated / total); if (translated) { brush=m_colorScheme.foreground(KColorScheme::PositiveText); painter->fillRect(myRect, brush); } myRect.setLeft(myRect.left() + myRect.width()); myRect.setWidth(option.rect.width() * fuzzy / total); if (fuzzy) { brush=m_colorScheme.foreground(KColorScheme::NeutralText); painter->fillRect(myRect, brush); // painter->drawText(myRect,Qt::AlignRight,QString("%1").arg(data.width())); } myRect.setLeft(myRect.left() + myRect.width()); myRect.setWidth(option.rect.width() - myRect.left() + option.rect.left()); if (untranslated) brush=m_colorScheme.foreground(KColorScheme::NegativeText); //esle: paint what is left with the last brush used - blank, positive or neutral painter->fillRect(myRect, brush); // painter->drawText(myRect,Qt::AlignRight,QString("%1").arg(data.top())); } else if (total == -1) painter->fillRect(option.rect,Qt::transparent); else if (total == 0) painter->fillRect(option.rect,QBrush(Qt::gray)); } class SortFilterProxyModel : public KDirSortFilterProxyModel { public: SortFilterProxyModel(QObject* parent=0) : KDirSortFilterProxyModel(parent) { connect(Project::instance()->model(),SIGNAL(totalsChanged(int,int,int,bool)),this,SLOT(invalidate())); } ~SortFilterProxyModel(){} protected: bool lessThan(const QModelIndex& left, const QModelIndex& right) const; bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const; }; bool SortFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const { bool result=false; const QAbstractItemModel* model=sourceModel(); QModelIndex item=model->index(source_row,0,source_parent); /* if (model->hasChildren(item)) model->fetchMore(item); */ if (item.data(ProjectModel::TotalRole) == 0) return false; // Hide rows with no translations int i=model->rowCount(item); while(--i>=0 && !result) result=filterAcceptsRow(i,item); return result || QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent); } bool SortFilterProxyModel::lessThan(const QModelIndex& left, const QModelIndex& right) const { static QCollator collator; // qCWarning(LOKALIZE_LOG)<(sourceModel()); const KFileItem leftFileItem = projectModel->itemForIndex(left); const KFileItem rightFileItem = projectModel->itemForIndex(right); //Code taken from KDirSortFilterProxyModel, as it is not compatible with our model. //TODO: make KDirSortFilterProxyModel::subSortLessThan not cast model to KDirModel, but use data() with FileItemRole instead. // Directories and hidden files should always be on the top, independent // from the sort order. const bool isLessThan = (sortOrder() == Qt::AscendingOrder); if (leftFileItem.isNull() || rightFileItem.isNull()) { qCWarning(LOKALIZE_LOG)<<".isNull()"; return false; } // On our priority, folders go above regular files. if (leftFileItem.isDir() && !rightFileItem.isDir()) { return isLessThan; } else if (!leftFileItem.isDir() && rightFileItem.isDir()) { return !isLessThan; } // Hidden elements go before visible ones, if they both are // folders or files. if (leftFileItem.isHidden() && !rightFileItem.isHidden()) { return isLessThan; } else if (!leftFileItem.isHidden() && rightFileItem.isHidden()) { return !isLessThan; } // Hidden elements go before visible ones, if they both are // folders or files. if (leftFileItem.isHidden() && !rightFileItem.isHidden()) { return true; } else if (!leftFileItem.isHidden() && rightFileItem.isHidden()) { return false; } switch(left.column()) { case ProjectModel::FileName: return collator.compare(leftFileItem.name(), rightFileItem.name()) < 0; case ProjectModel::Graph:{ QRect leftRect(left.data(Qt::DisplayRole).toRect()); QRect rightRect(right.data(Qt::DisplayRole).toRect()); int leftAll=leftRect.left()+leftRect.top()+leftRect.width(); int rightAll=rightRect.left()+rightRect.top()+rightRect.width(); if (!leftAll || !rightAll) return false; float leftVal=(float)leftRect.left()/leftAll; float rightVal=(float)rightRect.left()/rightAll; if (leftValrightVal) return false; leftVal=(float)leftRect.top()/leftAll; rightVal=(float)rightRect.top()/rightAll; if (leftValrightVal) return false; leftVal=(float)leftRect.width()/leftAll; rightVal=(float)rightRect.width()/rightAll; if (leftValdata(left).toString(), projectModel->data(right).toString()) < 0; case ProjectModel::TotalCount: case ProjectModel::TranslatedCount: case ProjectModel::UntranslatedCount: case ProjectModel::FuzzyCount: return projectModel->data(left).toInt() < projectModel->data(right).toInt(); default: return false; } } ProjectWidget::ProjectWidget(/*Catalog* catalog, */QWidget* parent) : QTreeView(parent) , m_proxyModel(new SortFilterProxyModel(this)) // , m_catalog(catalog) { PoItemDelegate* delegate=new PoItemDelegate(this); setItemDelegate(delegate); connect(this,SIGNAL(activated(QModelIndex)),this,SLOT(slotItemActivated(QModelIndex))); m_proxyModel->setSourceModel(Project::instance()->model()); //m_proxyModel->setDynamicSortFilter(true); setModel(m_proxyModel); connect(Project::instance()->model(), SIGNAL(loadingAboutToStart()), this, SLOT(modelAboutToReload())); connect(Project::instance()->model(), SIGNAL(loadingFinished()), this, SLOT(modelReloaded()), Qt::QueuedConnection); setUniformRowHeights(true); setAllColumnsShowFocus(true); int widthDefaults[]={6,1,1,1,1,1,4,4}; int i=sizeof(widthDefaults)/sizeof(int); int baseWidth=columnWidth(0); while(--i>=0) setColumnWidth(i, baseWidth*widthDefaults[i]/2); setSortingEnabled(true); sortByColumn(0, Qt::AscendingOrder); setSelectionMode(QAbstractItemView::ExtendedSelection); setSelectionBehavior(QAbstractItemView::SelectRows); // QTimer::singleShot(0,this,SLOT(initLater())); new HeaderViewMenuHandler(header()); KConfig config; KConfigGroup stateGroup(&config,"ProjectWindow"); header()->restoreState(QByteArray::fromBase64( stateGroup.readEntry("ListHeaderState", QByteArray()) )); } ProjectWidget::~ProjectWidget() { KConfig config; KConfigGroup stateGroup(&config,"ProjectWindow"); stateGroup.writeEntry("ListHeaderState",header()->saveState().toBase64()); } void ProjectWidget::modelAboutToReload() { m_currentItemPathBeforeReload=currentItem(); } void ProjectWidget::modelReloaded() { int i=10; while(--i>=0) { QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents|QEventLoop::ExcludeSocketNotifiers|QEventLoop::WaitForMoreEvents, 100); if (setCurrentItem(m_currentItemPathBeforeReload)) break; } if (proxyModel()->filterRegExp().pattern().size()>2) expandItems(); } bool ProjectWidget::setCurrentItem(const QString& u) { if (u.isEmpty()) return true; QModelIndex index = m_proxyModel->mapFromSource(Project::instance()->model()->indexForUrl(QUrl::fromLocalFile(u))); if (index.isValid()) setCurrentIndex(index); return index.isValid(); } QString ProjectWidget::currentItem() const { if (!currentIndex().isValid()) return QString(); return Project::instance()->model()->itemForIndex( m_proxyModel->mapToSource(currentIndex()) ).localPath(); } bool ProjectWidget::currentIsTranslationFile() const { //remember 'bout empty state return Catalog::extIsSupported(currentItem()); } void ProjectWidget::slotItemActivated(const QModelIndex& index) { if (currentIsTranslationFile()) { ProjectModel * srcModel = static_cast(static_cast(m_proxyModel)->sourceModel()); QModelIndex srcIndex = static_cast(m_proxyModel)->mapToSource(index); QUrl fileUrl = srcModel->beginEditing(srcIndex); emit fileOpenRequested(fileUrl.toLocalFile()); } } static void recursiveAdd(QStringList& list, const QModelIndex& idx) { ProjectModel& model=*(Project::instance()->model()); const KFileItem& item(model.itemForIndex(idx)); if (item.isDir()) { int j=model.rowCount(idx); while (--j>=0) { const KFileItem& childItem(model.itemForIndex(idx.child(j,0))); if (childItem.isDir()) recursiveAdd(list,idx.child(j,0)); else list.prepend(childItem.localPath()); } } else //if (!list.contains(u)) list.prepend(item.localPath()); } QStringList ProjectWidget::selectedItems() const { QStringList list; foreach(const QModelIndex& item, selectedIndexes()) { if (item.column()==0) recursiveAdd(list,m_proxyModel->mapToSource(item)); } return list; } void ProjectWidget::expandItems(const QModelIndex& parent) { const QAbstractItemModel* m=model(); expand(parent); int i=m->rowCount(parent); while(--i>=0) expandItems(m->index(i,0,parent)); } bool ProjectWidget::gotoIndexCheck(const QModelIndex& currentIndex, ProjectModel::AdditionalRoles role) { // Check if role is found for this index if (currentIndex.isValid()) { ProjectModel *srcModel = static_cast(static_cast(m_proxyModel)->sourceModel()); QModelIndex srcIndex = static_cast(m_proxyModel)->mapToSource(currentIndex); QVariant result = srcModel->data(srcIndex, role); return result.isValid() && result.toInt() > 0; } return false; } QModelIndex ProjectWidget::gotoIndexPrevNext(const QModelIndex& currentIndex, int direction) const { QModelIndex index = currentIndex; QModelIndex sibling; // Unless first or last sibling reached, continue with previous or next // sibling, otherwise continue with previous or next parent while (index.isValid()) { sibling = index.sibling(index.row() + direction, index.column()); if (sibling.isValid()) return sibling; index = index.parent(); } return index; } ProjectWidget::gotoIndexResult ProjectWidget::gotoIndexFind( const QModelIndex& currentIndex, ProjectModel::AdditionalRoles role, int direction) { QModelIndex index = currentIndex; while (index.isValid()) { // Set current index and show it if role is found for this index if (gotoIndexCheck(index, role)) { clearSelection(); setCurrentIndex(index); scrollTo(index); return gotoIndex_found; } // Handle child recursively if index is not a leaf QModelIndex child = index.child((direction == 1) ? 0 : (m_proxyModel->rowCount(index) - 1), index.column()); if (child.isValid()) { ProjectWidget::gotoIndexResult result = gotoIndexFind(child, role, direction); if (result != gotoIndex_notfound) return result; } // Go to previous or next item index = gotoIndexPrevNext(index, direction); } if (index.parent().isValid()) return gotoIndex_notfound; else return gotoIndex_end; } ProjectWidget::gotoIndexResult ProjectWidget::gotoIndex( const QModelIndex& currentIndex, ProjectModel::AdditionalRoles role, int direction) { QModelIndex index = currentIndex; // Check if current index already found, and if so go to previous or next item if (gotoIndexCheck(index, role)) index = gotoIndexPrevNext(index, direction); return gotoIndexFind(index, role, direction); } void ProjectWidget::gotoPrevFuzzyUntr() {gotoIndex(currentIndex(), ProjectModel::FuzzyUntrCountRole, -1);} void ProjectWidget::gotoNextFuzzyUntr() {gotoIndex(currentIndex(), ProjectModel::FuzzyUntrCountRole, +1);} void ProjectWidget::gotoPrevFuzzy() {gotoIndex(currentIndex(), ProjectModel::FuzzyCountRole, -1);} void ProjectWidget::gotoNextFuzzy() {gotoIndex(currentIndex(), ProjectModel::FuzzyCountRole, +1);} void ProjectWidget::gotoPrevUntranslated() {gotoIndex(currentIndex(), ProjectModel::UntransCountRole, -1);} void ProjectWidget::gotoNextUntranslated() {gotoIndex(currentIndex(), ProjectModel::UntransCountRole, +1);} void ProjectWidget::gotoPrevTemplateOnly() {gotoIndex(currentIndex(), ProjectModel::TemplateOnlyRole, -1);} void ProjectWidget::gotoNextTemplateOnly() {gotoIndex(currentIndex(), ProjectModel::TemplateOnlyRole, +1);} void ProjectWidget::gotoPrevTransOnly() {gotoIndex(currentIndex(), ProjectModel::TransOnlyRole, -1);} void ProjectWidget::gotoNextTransOnly() {gotoIndex(currentIndex(), ProjectModel::TransOnlyRole, +1);} QSortFilterProxyModel* ProjectWidget::proxyModel(){return m_proxyModel;} diff --git a/src/tm/dbfilesmodel.cpp b/src/tm/dbfilesmodel.cpp index b0c8f93..fc6194b 100644 --- a/src/tm/dbfilesmodel.cpp +++ b/src/tm/dbfilesmodel.cpp @@ -1,244 +1,243 @@ /* **************************************************************************** This file is part of Lokalize Copyright (C) 2007-2014 by Nick Shaforostoff 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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, see . **************************************************************************** */ #include "dbfilesmodel.h" #include "lokalize_debug.h" #include "jobs.h" #include "project.h" -#include "kdemacros.h" #include #include #include #include #include #if defined(Q_OS_WIN) && defined(QStringLiteral) #undef QStringLiteral #define QStringLiteral QLatin1String #endif using namespace TM; static QString tmFileExtension = QStringLiteral(TM_DATABASE_EXTENSION); static QString remoteTmExtension = QStringLiteral(REMOTETM_DATABASE_EXTENSION); DBFilesModel* DBFilesModel::_instance=0; void DBFilesModel::cleanupDBFilesModel() { delete DBFilesModel::_instance; DBFilesModel::_instance = 0; } DBFilesModel* DBFilesModel::instance() { - if (KDE_ISUNLIKELY( _instance==0 )) { + if (Q_UNLIKELY( _instance==0 )) { _instance=new DBFilesModel; qAddPostRoutine(DBFilesModel::cleanupDBFilesModel); } return _instance; } DBFilesModel::DBFilesModel() : QSortFilterProxyModel() , projectDB(0) , m_fileSystemModel(new QFileSystemModel(this)) , m_tmRootPath(QStandardPaths::writableLocation(QStandardPaths::DataLocation)) { m_fileSystemModel->setNameFilters(QStringList(QStringLiteral("*." TM_DATABASE_EXTENSION))); m_fileSystemModel->setFilter(QDir::Files); m_fileSystemModel->setRootPath(QStandardPaths::writableLocation(QStandardPaths::DataLocation)); setSourceModel(m_fileSystemModel); connect (this,SIGNAL(rowsInserted(QModelIndex,int,int)), this,SLOT(calcStats(QModelIndex,int,int))/*,Qt::QueuedConnection*/); connect (this,SIGNAL(dataChanged(QModelIndex,QModelIndex)), this,SLOT(updateStats(QModelIndex,QModelIndex)),Qt::QueuedConnection); m_timeSinceLastUpdate.start(); int count=rowCount(rootIndex()); if (count) calcStats(rootIndex(),0,count-1); openDB(QStringLiteral("default")); //behave when no project is loaded } DBFilesModel::~DBFilesModel() { delete projectDB; } bool DBFilesModel::filterAcceptsRow ( int source_row, const QModelIndex& source_parent ) const { if (source_parent!=m_fileSystemModel->index(m_tmRootPath)) return true; const QString& fileName=m_fileSystemModel->index(source_row, 0, source_parent).data().toString(); return (fileName.endsWith(tmFileExtension) && !fileName.endsWith(QLatin1String("-journal.db"))) || fileName.endsWith(remoteTmExtension); } QModelIndex DBFilesModel::rootIndex() const { return mapFromSource(m_fileSystemModel->index(m_tmRootPath)); } QVariant DBFilesModel::headerData(int section, Qt::Orientation orientation, int role) const { Q_UNUSED(orientation); if (role!=Qt::DisplayRole) return QVariant(); const char* const columns[]={ I18N_NOOP2("@title:column","Name"), I18N_NOOP2("@title:column","Source language"), I18N_NOOP2("@title:column","Target language"), I18N_NOOP2("@title:column","Pairs"), I18N_NOOP2("@title:column","Unique original entries"), I18N_NOOP2("@title:column","Unique translations") }; return i18nc("@title:column",columns[section]); } void DBFilesModel::openDB(const QString& name, DbType type, bool forceCurrentProjectConfig) { if (type==TM::Undefined) type=QFileInfo( QStandardPaths::writableLocation(QStandardPaths::DataLocation)%QLatin1Char('/')%name%QStringLiteral(REMOTETM_DATABASE_EXTENSION)).exists()?TM::Remote:TM::Local; OpenDBJob* openDBJob=new OpenDBJob(name, type); if (forceCurrentProjectConfig) { openDBJob->m_setParams=true; openDBJob->m_tmConfig.markup=Project::instance()->markup(); openDBJob->m_tmConfig.accel=Project::instance()->accel(); openDBJob->m_tmConfig.sourceLangCode=Project::instance()->sourceLangCode(); openDBJob->m_tmConfig.targetLangCode=Project::instance()->targetLangCode(); } openDB(openDBJob); } void DBFilesModel::openDB(OpenDBJob* openDBJob) { connect(openDBJob,SIGNAL(done(OpenDBJob*)),this,SLOT(openJobDone(OpenDBJob*))); threadPool()->start(openDBJob, OPENDB); } void DBFilesModel::updateStats(const QModelIndex& topLeft, const QModelIndex& bottomRight) { if (m_timeSinceLastUpdate.elapsed()<60000 || !topLeft.isValid() || !bottomRight.isValid()) return; qCDebug(LOKALIZE_LOG)<<"DBFilesModel::updateStats() called"; calcStats(topLeft.parent(), topLeft.row(), bottomRight.row()); m_timeSinceLastUpdate.start(); } void DBFilesModel::calcStats(const QModelIndex& parent, int start, int end) { if (parent!=rootIndex()) return; const QString& projectID=Project::instance()->projectID(); while (start<=end) { QModelIndex index=QSortFilterProxyModel::index(start++, 0, parent); QString res=index.data().toString(); - if (KDE_ISUNLIKELY(res==projectID && (!projectDB || data(*projectDB).toString()!=projectID))) + if (Q_UNLIKELY(res==projectID && (!projectDB || data(*projectDB).toString()!=projectID))) projectDB=new QPersistentModelIndex(index);//TODO if user switches the project -// if (KDE_ISLIKELY( QSqlDatabase::contains(res) )) +// if (Q_LIKELY( QSqlDatabase::contains(res) )) // continue; openDB(res, DbType(index.data(FileNameRole).toString().endsWith(remoteTmExtension))); } } void DBFilesModel::openJobDone(OpenDBJob* j) { j->deleteLater(); m_stats[j->m_dbName]=j->m_stat; m_configurations[j->m_dbName]=j->m_tmConfig; qCDebug(LOKALIZE_LOG)<m_dbName<m_tmConfig.targetLangCode; } void DBFilesModel::removeTM ( QModelIndex index ) { index=index.sibling(index.row(),0); CloseDBJob* closeDBJob=new CloseDBJob(index.data().toString()); connect(closeDBJob,SIGNAL(done(CloseDBJob*)),this,SLOT(closeJobDone(CloseDBJob*))); threadPool()->start(closeDBJob, CLOSEDB); } void DBFilesModel::closeJobDone(CloseDBJob* j) { j->deleteLater(); QFile::remove(m_fileSystemModel->rootPath() % '/' % j->dbName() % tmFileExtension); } void DBFilesModel::updateProjectTmIndex() { if (projectDB && data(*projectDB).toString()!=Project::instance()->projectID()) { delete projectDB; projectDB=0; } } int DBFilesModel::columnCount (const QModelIndex&) const { return 4; //FIXME the lat two columns are not displayed even if 6 is returned } QVariant DBFilesModel::data (const QModelIndex& index, int role) const { if (role==Qt::DecorationRole) return QVariant(); if (role!=Qt::DisplayRole && role!=FileNameRole && role!=NameRole && index.column()<4) return QSortFilterProxyModel::data(index, role); QString res=QSortFilterProxyModel::data(index.sibling(index.row(), 0), QFileSystemModel::FileNameRole).toString(); if (role==FileNameRole) return res; if (res.endsWith(remoteTmExtension)) res.chop(remoteTmExtension.size()); else res.chop(tmFileExtension.size()); if (role==NameRole) return res; //qCDebug(LOKALIZE_LOG)< 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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, see . **************************************************************************** */ #include "jobs.h" #include "lokalize_debug.h" #include "catalog.h" #include "project.h" #include "diff.h" #include "prefs_lokalize.h" #include "version.h" #include "stemming.h" -#include "kdemacros.h" - #include #include #include #include #include #include #include #include #include #include #include using namespace TM; QThreadPool* TM::threadPool() { static QThreadPool* inst=new QThreadPool; return inst; } #ifdef Q_OS_WIN #define U QLatin1String #else #define U QStringLiteral #endif #define TM_DELIMITER '\v' #define TM_SEPARATOR '\b' #define TM_NOTAPPROVED 0x04 static bool stop=false; void TM::cancelAllJobs(){stop=true;} static qlonglong newTMSourceEntryCount = 0; static qlonglong reusedTMSourceEntryCount = 0; /** * splits string into words, removing any markup * * TODO segmentation by sentences... **/ static void doSplit(QString& cleanEn, QStringList& words, QRegExp& rxClean1, const QString& accel ) { static QRegExp rxSplit(QStringLiteral("\\W+|\\d+")); if (!rxClean1.pattern().isEmpty()) cleanEn.replace(rxClean1,QStringLiteral(" ")); cleanEn.remove(accel); words=cleanEn.toLower().split(rxSplit,QString::SkipEmptyParts); if (words.size()>4) { int i=0; for(;i=0) { // insert word (if we do not have it) if (Q_UNLIKELY(!query1.exec(U("SELECT word, ids_short, ids_long FROM words WHERE " "word='")%words.at(j)%'\''))) qCWarning(LOKALIZE_LOG) <<"select error 3: " <0); query1.clear(); if (exit) return; //END check bool isShort=words.size()<20; int j=words.size(); while (--j>=0) { // remove from record for the word (if we do not have it) if (Q_UNLIKELY(!query1.exec(U("SELECT word, ids_short, ids_long FROM words WHERE " "word='")%words.at(j)%'\''))) { qCWarning(LOKALIZE_LOG) <<"select error 3: " < tmConfigCache; static void setConfig(QSqlDatabase& db, const TMConfig& c) { QSqlQuery query(db); query.prepare(QStringLiteral("INSERT INTO tm_config (key, value) VALUES (?, ?)")); query.addBindValue(0); query.addBindValue(c.markup); //qCDebug(LOKALIZE_LOG)<<"setting tm db config:"<setPriority(QThread::IdlePriority); if (m_type==TM::Local) { QSqlDatabase db=QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"),m_dbName); QString dbFolder=QStandardPaths::writableLocation(QStandardPaths::DataLocation); QFileInfo fileInfo(dbFolder); if (!fileInfo.exists(dbFolder)) fileInfo.absoluteDir().mkpath(fileInfo.fileName()); db.setDatabaseName(dbFolder % QLatin1Char('/') % m_dbName % TM_DATABASE_EXTENSION); m_connectionSuccessful=db.open(); if (Q_UNLIKELY( !m_connectionSuccessful )) { qCDebug(LOKALIZE_LOG)<<"failed to open db"<sourceWithTags(pos), catalog->context(pos.entry).first(), catalog->url(), pos, db.isEmpty()?Project::instance()->projectID():db); if (opt&Enqueue) { //deletion should be done by receiver, e.g. slotSuggestionsCame() threadPool()->start(job, SELECT); } return job; } SelectJob::SelectJob(const CatalogString& source, const QString& ctxt, const QString& file, const DocPosition& pos, const QString& dbName) : QObject(), QRunnable() , m_source(source) , m_ctxt(ctxt) , m_file(file) , m_dequeued(false) , m_pos(pos) , m_dbName(dbName) { setAutoDelete(false); //qCDebug(LOKALIZE_LOG)<<"selectjob"< invertMap(const QMap& source) { //uses the fact that map has its keys always sorted QMap sortingMap; for(QMap::const_iterator i=source.constBegin(); i!=source.constEnd(); ++i) { sortingMap.insertMulti(i.value(), i.key()); } return sortingMap; } //returns true if seen translation with >85% bool SelectJob::doSelect(QSqlDatabase& db, QStringList& words, //QList& entries, bool isShort) { bool qpsql = (db.driverName()==QLatin1String("QPSQL")); QMap occurencies; QVector idsForWord; QSqlQuery queryWords(db); //TODO ??? not sure. make another loop before to create QList< QList > then reorder it by size static const QString queryC[]={U("SELECT ids_long FROM words WHERE word='%1'"), U("SELECT ids_short FROM words WHERE word='%1'")}; QString queryString=queryC[isShort]; //for each word... int o=words.size(); while (--o>=0) { //if this is not the first word occurrence, just readd ids for it if (!( !idsForWord.isEmpty() && words.at(o)==words.at(o+1) )) { idsForWord.clear(); queryWords.exec(queryString.arg(words.at(o))); if (Q_UNLIKELY(!queryWords.exec(queryString.arg(words.at(o))))) qCWarning(LOKALIZE_LOG) <<"select error: " < ids(arr.split(' ')); int p=ids.size(); idsForWord.reserve(p); while (--p>=0) idsForWord.append(ids.at(p).toLongLong(/*bool ok*/0,36)); } else { queryWords.clear(); continue; } } //qCWarning(LOKALIZE_LOG) <<"SelectJob: idsForWord.size() "<::const_iterator i=idsForWord.constBegin(); i!=idsForWord.constEnd(); i++) occurencies[*i]++; //0 is default value } //accels are removed TMConfig c=getConfig(db); QString tmp=c.markup; if (!c.markup.isEmpty()) tmp+='|'; QRegExp rxSplit(QLatin1Char('(') % tmp % QStringLiteral("\\W+|\\d+)+")); QString sourceClean(m_source.string); sourceClean.remove(c.accel); //split m_english for use in wordDiff later--all words are needed so we cant use list we already have QStringList englishList(sourceClean.toLower().split(rxSplit,QString::SkipEmptyParts)); static QRegExp delPart(QStringLiteral("*"), Qt::CaseSensitive, QRegExp::Wildcard); static QRegExp addPart(QStringLiteral("*"), Qt::CaseSensitive, QRegExp::Wildcard); delPart.setMinimal(true); addPart.setMinimal(true); //QList concordanceLevels=sortedUniqueValues(occurencies); //we start from entries with higher word-concordance level QMap concordanceLevelToIds=invertMap(occurencies); if (concordanceLevelToIds.isEmpty()) return false; bool seen85=false; int limit=200; auto clit=concordanceLevelToIds.constEnd(); if (concordanceLevelToIds.size()) --clit; if (concordanceLevelToIds.size()) while (--limit>=0) { if (Q_UNLIKELY( m_dequeued )) break; //for every concordance level qlonglong level=clit.key(); QString joined; while(level==clit.key()) { joined+=QString::number(clit.value())+','; if (clit==concordanceLevelToIds.constBegin() || --limit<0) break; --clit; } joined.chop(1); //get records containing current word QSqlQuery queryFetch(U( "SELECT id, source, source_accel, source_markup FROM source_strings WHERE " "source_strings.id IN (")%joined%')',db); TMEntry e; while (queryFetch.next()) { e.id=queryFetch.value(0).toLongLong(); if (queryFetch.value(3).toByteArray().size()) qCDebug(LOKALIZE_LOG)<<"BA"<10*englishList.size()) continue; //sugg is 'old' --translator has to adapt its translation to 'new'--current QString result=wordDiff(englishSuggList,englishList); //qCWarning(LOKALIZE_LOG) <<"SelectJob: doin "< 1 so we have decreased it, and increased result: / exp(0.014*float(addLen)*log10(3.0f+addSubStrCount)); if (delLen) { //qCWarning(LOKALIZE_LOG) <<"SelectJob: delLen:"<8500; if (seen85 && e.score<6000) continue; //BEGIN fetch rest of the data QString change_author_str; QString authors_table_str; if (qpsql) { //change_author_str=", main.change_author "; change_author_str=QStringLiteral(", pg_user.usename "); authors_table_str=QStringLiteral(" JOIN pg_user ON (pg_user.usesysid=main.change_author) "); } QSqlQuery queryRest(U( "SELECT main.id, main.date, main.ctxt, main.bits, " "target_strings.target, target_strings.target_accel, target_strings.target_markup, " "files.path, main.change_date ") % change_author_str % U( "FROM main JOIN target_strings ON (target_strings.id=main.target) JOIN files ON (files.id=main.file) ") % authors_table_str % U("WHERE " "main.source=")%QString::number(e.id)%U(" AND " "(main.bits&4)!=4 AND " "target_strings.target NOTNULL") ,db); //ORDER BY tm_main.id ? queryRest.exec(); //qCDebug(LOKALIZE_LOG)<<"main select error"< sortedEntryList;//to eliminate same targets from different files while (queryRest.next()) { e.id=queryRest.value(0).toLongLong(); e.date=queryRest.value(1).toDate(); e.ctxt=queryRest.value(2).toString(); e.target=CatalogString( makeAcceledString(queryRest.value(4).toString(), c.accel, queryRest.value(5)), queryRest.value(6).toByteArray() ); QStringList matchData=queryRest.value(2).toString().split(TM_DELIMITER,QString::KeepEmptyParts);//context|plural e.file=queryRest.value(7).toString(); if (e.target.isEmpty()) continue; e.obsolete=queryRest.value(3).toInt()&1; e.changeDate=queryRest.value(8).toDate(); if (qpsql) e.changeAuthor=queryRest.value(9).toString(); //BEGIN exact match score++ if (possibleExactMatch) //"exact" match (case insensitive+w/o non-word characters!) { if (m_source.string==e.source.string) e.score=10000; else e.score=9900; } if (!m_ctxt.isEmpty()&&matchData.size()>0)//check not needed? { if (matchData.at(0)==m_ctxt) e.score+=33; } //qCWarning(LOKALIZE_LOG)<<"m_pos"<1) { int form=matchData.at(1).toInt(); //pluralMatches=(form&&form==m_pos.form); if (form&&form==(int)m_pos.form) { //qCWarning(LOKALIZE_LOG)<<"this"< hash; int oldCount=m_entries.size(); QMap::const_iterator it=sortedEntryList.constEnd(); if (sortedEntryList.size()) while(true) { --it; const TMEntry& e=it.key(); int& hits=hash[e.target.string]; if (!hits) //0 was default value m_entries.append(e); hits++; if (it==sortedEntryList.constBegin()) break; } for (int i=oldCount;isetPriority(QThread::IdlePriority); QTime a;a.start(); if (Q_UNLIKELY( !QSqlDatabase::contains(m_dbName) )) { emit done(this); return; } QSqlDatabase db=QSqlDatabase::database(m_dbName); if (Q_UNLIKELY(!db.isValid() || !db.isOpen() )) { emit done(this); return; } //qCDebug(LOKALIZE_LOG)<<"select started 2"<()); int limit=qMin(Settings::suggCount(),m_entries.size()); int i=m_entries.size(); while(--i>=limit) m_entries.removeLast(); if (Q_UNLIKELY( m_dequeued )) { emit done(this); return; } ++i; while(--i>=0) { m_entries[i].accelExpr=c.accel; m_entries[i].markupExpr=c.markup; m_entries[i].diff=userVisibleWordDiff(m_entries.at(i).source.string, m_source.string, m_entries.at(i).accelExpr, m_entries.at(i).markupExpr); } emit done(this); } ScanJob::ScanJob(const QString& filePath, const QString& dbName) : QRunnable() , m_filePath(filePath) , m_time(0) , m_added(0) , m_newVersions(0) , m_size(0) , m_dbName(dbName) { qCDebug(LOKALIZE_LOG)<setPriority(QThread::IdlePriority); QTime a;a.start(); QSqlDatabase db=QSqlDatabase::database(m_dbName); if (!db.isOpen()) return; //initSqliteDb(db); TMConfig c=getConfig(db,true); QRegExp rxClean1(c.markup);rxClean1.setMinimal(true); Catalog catalog(0); if (Q_LIKELY(catalog.loadFromUrl(m_filePath, QString(), &m_size, /*no auto save*/true)==0)) { if (c.targetLangCode!=catalog.targetLangCode()) { qCWarning(LOKALIZE_LOG)<<"not indexing file because target languages don't match:"< #include /** @author Nick Shaforostoff */ class TmxParser : public QXmlDefaultHandler { enum State //localstate for getting chars into right place { null=0, seg, propContext, propFile, propPluralForm, propApproved }; enum Lang { Source, Target, Null }; public: TmxParser(const QString& dbName); ~TmxParser(); private: bool startDocument(); bool startElement(const QString&,const QString&,const QString&,const QXmlAttributes&); bool endElement(const QString&,const QString&,const QString&); bool characters(const QString&); private: QSqlDatabase db; QRegExp rxClean1; QString accel; int m_hits; CatalogString m_segment[3]; //Lang enum QList m_inlineTags; QString m_context; QString m_pluralForm; QString m_filePath; QString m_approvedString; State m_state:8; Lang m_lang:8; ushort m_added; QMap m_fileIds; QString m_dbLangCode; }; TmxParser::TmxParser(const QString& dbName) : m_hits(0) , m_state(null) , m_lang(Null) , m_added(0) , m_dbLangCode(Project::instance()->langCode().toLower()) { db=QSqlDatabase::database(dbName); TMConfig c=getConfig(db); rxClean1.setPattern(c.markup);rxClean1.setMinimal(true); accel=c.accel; } bool TmxParser::startDocument() { //initSqliteDb(db); m_fileIds.clear(); QSqlQuery queryBegin(QLatin1String("BEGIN"),db); m_state=null; m_lang=Null; return true; } TmxParser::~TmxParser() { QSqlQuery queryEnd(QLatin1String("END"),db); } bool TmxParser::startElement( const QString&, const QString&, const QString& qName, const QXmlAttributes& attr) { if (qName==QLatin1String("tu")) { bool ok; m_hits=attr.value(QLatin1String("usagecount")).toInt(&ok); if (!ok) m_hits=-1; m_segment[Source].clear(); m_segment[Target].clear(); m_context.clear(); m_pluralForm.clear(); m_filePath.clear(); m_approvedString.clear(); } else if (qName==QLatin1String("tuv")) { QString attrLang=attr.value(QStringLiteral("xml:lang")).toLower(); if (attrLang==QLatin1String("en")) //TODO startsWith? m_lang=Source; else if (attrLang==m_dbLangCode) m_lang=Target; else { qCWarning(LOKALIZE_LOG)<<"skipping lang"< ExportTmxJob::ExportTmxJob(const QString& filename, const QString& dbName) : QRunnable() , m_filename(filename) , m_time(0) , m_dbName(dbName) { } ExportTmxJob::~ExportTmxJob() { qCDebug(LOKALIZE_LOG)<<"ExportTmxJob dtor"; } void ExportTmxJob::run() { QTime a;a.start(); QFile out(m_filename); if (!out.open(QFile::WriteOnly|QFile::Text)) return; QXmlStreamWriter xmlOut(&out); xmlOut.setAutoFormatting(true); xmlOut.writeStartDocument(QStringLiteral("1.0")); xmlOut.writeStartElement(QStringLiteral("tmx")); xmlOut.writeAttribute(QStringLiteral("version"),QStringLiteral("2.0")); xmlOut.writeStartElement(QStringLiteral("header")); xmlOut.writeAttribute(QStringLiteral("creationtool"),QStringLiteral("lokalize")); xmlOut.writeAttribute(QStringLiteral("creationtoolversion"),QStringLiteral(LOKALIZE_VERSION)); xmlOut.writeAttribute(QStringLiteral("segtype"),QStringLiteral("paragraph")); xmlOut.writeAttribute(QStringLiteral("o-encoding"),QStringLiteral("UTF-8")); xmlOut.writeEndElement(); xmlOut.writeStartElement(QStringLiteral("body")); QString dbLangCode=Project::instance()->langCode(); QSqlDatabase db=QSqlDatabase::database(m_dbName); QSqlQuery query1(db); if (Q_UNLIKELY(!query1.exec(U( "SELECT main.id, main.ctxt, main.date, main.bits, " "source_strings.source, source_strings.source_accel, " "target_strings.target, target_strings.target_accel, " "files.path, main.change_date " "FROM main, source_strings, target_strings, files " "WHERE source_strings.id=main.source AND " "target_strings.id=main.target AND " "files.id=main.file")))) qCWarning(LOKALIZE_LOG) <<"select error: " <lastError().text(); emit done(this); } diff --git a/src/tm/qamodel.cpp b/src/tm/qamodel.cpp index cf9dad1..7afa56f 100644 --- a/src/tm/qamodel.cpp +++ b/src/tm/qamodel.cpp @@ -1,241 +1,240 @@ /* This file is part of Lokalize Copyright (C) 2011-2012 Nick Shaforostoff 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, see . */ #include "qamodel.h" #include "domroutines.h" #include #include #include #include #include -#include static QString ruleTagNames[]={QString("source"), QString("falseFriend"), QString("target")}; static QStringList domListToStringList(const QDomNodeList& nodes) { QStringList result; result.reserve(nodes.size()); for (int i=0;i domListToRegExpVector(const QDomNodeList& nodes) { QVector result; result.reserve(nodes.size()); for (int i=0;i QaModel::toVector() const { QVector rules; QDomNodeList m_categories=m_doc.elementsByTagName("category"); for (int i=0;i\n" "\n" " \n" " \n" "\n")); } m_entries=m_doc.elementsByTagName("rule"); m_filename=filename; return true; } bool QaModel::saveRules(QString filename) { if (filename.isEmpty()) filename=m_filename; if (filename.isEmpty()) return false; QFile device(filename); if (!device.open(QFile::WriteOnly | QFile::Truncate)) return false; QTextStream stream(&device); m_doc.save(stream,2); //setClean(true); return true; } QModelIndex QaModel::appendRow() { beginInsertRows(QModelIndex(),rowCount(),rowCount()); QDomElement category=m_doc.elementsByTagName("qa").at(0).toElement().elementsByTagName("category").at(0).toElement(); QDomElement rule=category.appendChild(m_doc.createElement("rule")).toElement(); rule.appendChild(m_doc.createElement(ruleTagNames[Source])); rule.appendChild(m_doc.createElement(ruleTagNames[FalseFriend])); endInsertRows(); return index(m_entries.count()-1); } void QaModel::removeRow(const QModelIndex& rowIndex) { //TODO optimize for contiguous selections beginRemoveRows(QModelIndex(),rowIndex.row(),rowIndex.row()); QDomElement category=m_doc.elementsByTagName("qa").at(0).toElement().elementsByTagName("category").at(0).toElement(); category.removeChild(m_entries.at(rowIndex.row())); endRemoveRows(); } Qt::ItemFlags QaModel::flags(const QModelIndex& ) const { return Qt::ItemIsSelectable|Qt::ItemIsEnabled|Qt::ItemIsEditable; } bool QaModel::setData(const QModelIndex& item, const QVariant& value, int role) { if (role!=Qt::DisplayRole && role!=Qt::EditRole) return false; QDomElement entry=m_entries.at(item.row()).toElement(); QDomNodeList sources=entry.elementsByTagName(ruleTagNames[item.column()]); QStringList newSources=value.toString().split('\n'); while(sources.size()newSources.size()) entry.removeChild(sources.at(sources.size()-1)); for (int i=0;i 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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, see . **************************************************************************** */ #include "tmview.h" #include "lokalize_debug.h" #include "jobs.h" #include "tmscanapi.h" #include "catalog.h" #include "cmd.h" #include "project.h" #include "prefs_lokalize.h" #include "dbfilesmodel.h" #include "diff.h" #include "xlifftextedit.h" -#include "kdemacros.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef NDEBUG #undef NDEBUG #endif #define DEBUG using namespace TM; struct DiffInfo { DiffInfo(int reserveSize); QString diffClean; QString old; //Formatting info: QByteArray diffIndex; //Map old string-->d.diffClean QVector old2DiffClean; }; DiffInfo::DiffInfo(int reserveSize) { diffClean.reserve(reserveSize); old.reserve(reserveSize); diffIndex.reserve(reserveSize); old2DiffClean.reserve(reserveSize); } /** * 0 - common + - add - - del M - modified so the string is like 00000MM00+++---000 (M appears afterwards) */ static DiffInfo getDiffInfo(const QString& diff) { DiffInfo d(diff.size()); QChar sep('{'); char state='0'; //walk through diff string char-by-char //calculate old and others int pos=-1; while (++pos& actions) : QDockWidget ( i18nc("@title:window","Translation Memory"), parent) , m_browser(new TextBrowser(this)) , m_catalog(catalog) , m_currentSelectJob(0) , m_actions(actions) , m_normTitle(i18nc("@title:window","Translation Memory")) , m_hasInfoTitle(m_normTitle+QStringLiteral(" [*]")) , m_hasInfo(false) , m_isBatching(false) , m_markAsFuzzy(false) { setObjectName(QStringLiteral("TMView")); setWidget(m_browser); m_browser->document()->setDefaultStyleSheet(QStringLiteral("p.close_match { font-weight:bold; }")); m_browser->viewport()->setBackgroundRole(QPalette::Background); QTimer::singleShot(0,this,SLOT(initLater())); connect(m_catalog,SIGNAL(signalFileLoaded(QString)), this,SLOT(slotFileLoaded(QString))); } TMView::~TMView() { #if QT_VERSION >= 0x050500 int i=m_jobs.size(); while (--i>=0) TM::threadPool()->cancel(m_jobs.at(i)); #endif } void TMView::initLater() { setAcceptDrops(true); QSignalMapper* signalMapper=new QSignalMapper(this); int i=m_actions.size(); while(--i>=0) { connect(m_actions.at(i),SIGNAL(triggered()),signalMapper,SLOT(map())); signalMapper->setMapping(m_actions.at(i), i); } connect(signalMapper, SIGNAL(mapped(int)), this, SLOT(slotUseSuggestion(int))); setToolTip(i18nc("@info:tooltip","Double-click any word to insert it into translation")); DBFilesModel::instance(); connect(m_browser,SIGNAL(textInsertRequested(QString)),this,SIGNAL(textInsertRequested(QString))); connect(m_browser,SIGNAL(customContextMenuRequested(QPoint)),this,SLOT(contextMenu(QPoint))); //TODO ? kdisplayPaletteChanged // connect(KGlobalSettings::self(),,SIGNAL(kdisplayPaletteChanged()),this,SLOT(slotPaletteChanged())); } void TMView::dragEnterEvent(QDragEnterEvent* event) { if (dragIsAcceptable(event->mimeData()->urls())) event->acceptProposedAction(); } void TMView::dropEvent(QDropEvent *event) { QStringList files; foreach(const QUrl& url, event->mimeData()->urls()) files.append(url.toLocalFile()); if (scanRecursive(files,Project::instance()->projectID())) event->acceptProposedAction(); } void TMView::slotFileLoaded(const QString& filePath) { const QString& pID=Project::instance()->projectID(); if (Settings::scanToTMOnOpen()) TM::threadPool()->start(new ScanJob(filePath,pID), SCAN); if (!Settings::prefetchTM() &&!m_isBatching) return; m_cache.clear(); #if QT_VERSION >= 0x050500 int i=m_jobs.size(); while (--i>=0) TM::threadPool()->cancel(m_jobs.at(i)); #endif m_jobs.clear(); DocPosition pos; while(switchNext(m_catalog,pos)) { if (!m_catalog->isEmpty(pos.entry) &&m_catalog->isApproved(pos.entry)) continue; SelectJob* j=initSelectJob(m_catalog, pos, pID); connect(j,SIGNAL(done(SelectJob*)),this,SLOT(slotCacheSuggestions(SelectJob*))); m_jobs.append(j); } //dummy job for the finish indication BatchSelectFinishedJob* m_seq=new BatchSelectFinishedJob(this); connect(m_seq,SIGNAL(done()),this,SLOT(slotBatchSelectDone())); TM::threadPool()->start(m_seq, BATCHSELECTFINISHED); m_jobs.append(m_seq); } void TMView::slotCacheSuggestions(SelectJob* job) { m_jobs.removeAll(job); qCDebug(LOKALIZE_LOG)<m_pos.entry; if (job->m_pos.entry==m_pos.entry) slotSuggestionsCame(job); m_cache[DocPos(job->m_pos)]=job->m_entries.toVector(); } void TMView::slotBatchSelectDone() { m_jobs.clear(); if (!m_isBatching) return; bool insHappened=false; DocPosition pos; while(switchNext(m_catalog,pos)) { if (!(m_catalog->isEmpty(pos.entry) ||!m_catalog->isApproved(pos.entry)) ) continue; const QVector& suggList=m_cache.value(DocPos(pos)); if (suggList.isEmpty()) continue; const TMEntry& entry=suggList.first(); if (entry.score<9900)//hacky continue; { bool forceFuzzy=(suggList.size()>1&&suggList.at(1).score>=10000) ||entry.score<10000; bool ctxtMatches=entry.score==1001; if (!m_catalog->isApproved(pos.entry)) { ///m_catalog->push(new DelTextCmd(m_catalog,pos,m_catalog->msgstr(pos))); removeTargetSubstring(m_catalog, pos, 0, m_catalog->targetWithTags(pos).string.size()); if ( ctxtMatches || !(m_markAsFuzzy||forceFuzzy) ) SetStateCmd::push(m_catalog,pos,true); } else if ((m_markAsFuzzy&&!ctxtMatches)||forceFuzzy) { SetStateCmd::push(m_catalog,pos,false); } ///m_catalog->push(new InsTextCmd(m_catalog,pos,entry.target)); insertCatalogString(m_catalog, pos, entry.target, 0); if (Q_UNLIKELY( m_pos.entry==pos.entry&&pos.form==m_pos.form )) emit refreshRequested(); } if (!insHappened) { insHappened=true; m_catalog->beginMacro(i18nc("@item Undo action","Batch translation memory filling")); } } QString msg=i18nc("@info","Batch translation has been completed."); if (insHappened) m_catalog->endMacro(); else { // xgettext: no-c-format msg+=' '; msg+=i18nc("@info","No suggestions with exact matches were found."); } KPassivePopup::message(KPassivePopup::Balloon, i18nc("@title","Batch translation complete"), msg, this); } void TMView::slotBatchTranslate() { m_isBatching=true; m_markAsFuzzy=false; if (!Settings::prefetchTM()) slotFileLoaded(m_catalog->url()); else if (m_jobs.isEmpty()) return slotBatchSelectDone(); KPassivePopup::message(KPassivePopup::Balloon, i18nc("@title","Batch translation"), i18nc("@info","Batch translation has been scheduled."), this); } void TMView::slotBatchTranslateFuzzy() { m_isBatching=true; m_markAsFuzzy=true; if (!Settings::prefetchTM()) slotFileLoaded(m_catalog->url()); else if (m_jobs.isEmpty()) slotBatchSelectDone(); KPassivePopup::message(KPassivePopup::Balloon, i18nc("@title","Batch translation"), i18nc("@info","Batch translation has been scheduled."), this); } void TMView::slotNewEntryDisplayed(const DocPosition& pos) { if (m_catalog->numberOfEntries()<=pos.entry) return;//because of Qt::QueuedConnection #if QT_VERSION >= 0x050500 int i=m_jobs.size(); while (--i>=0) TM::threadPool()->cancel(m_currentSelectJob); #endif //update DB //m_catalog->flushUpdateDBBuffer(); //this is called via subscribtion if (pos.entry!=-1) m_pos=pos; m_browser->clear(); if (Settings::prefetchTM() &&m_cache.contains(DocPos(m_pos))) { QTimer::singleShot(0,this,SLOT(displayFromCache())); } m_currentSelectJob=initSelectJob(m_catalog, m_pos); connect(m_currentSelectJob,SIGNAL(done(SelectJob*)),this,SLOT(slotSuggestionsCame(SelectJob*))); } void TMView::displayFromCache() { if (m_prevCachePos.entry==m_pos.entry &&m_prevCachePos.form==m_pos.form) return; SelectJob* temp=initSelectJob(m_catalog, m_pos, QString(), 0); temp->m_entries=m_cache.value(DocPos(m_pos)).toList(); slotSuggestionsCame(temp); temp->deleteLater(); m_prevCachePos=m_pos; } void TMView::slotSuggestionsCame(SelectJob* j) { QTime time;time.start(); SelectJob& job=*j; job.deleteLater(); if (job.m_pos.entry!=m_pos.entry) return; Catalog& catalog=*m_catalog; if (catalog.numberOfEntries()<=m_pos.entry) return;//because of Qt::QueuedConnection //BEGIN query other DBs handling Project* project=Project::instance(); const QString& projectID=project->projectID(); //check if this is an additional query, from secondary DBs if (job.m_dbName!=projectID) { job.m_entries+=m_entries; qSort(job.m_entries.begin(), job.m_entries.end(), qGreater()); int limit=qMin(Settings::suggCount(),job.m_entries.size()); int i=job.m_entries.size(); while(--i>=limit) job.m_entries.removeLast(); } else if (job.m_entries.isEmpty()||job.m_entries.first().score<8500) { //be careful, as we switched to QDirModel! const DBFilesModel& dbFilesModel=*(DBFilesModel::instance()); QModelIndex root=dbFilesModel.rootIndex(); int i=dbFilesModel.rowCount(root); //qCWarning(LOKALIZE_LOG)<<"query other DBs,"<=0) { const QString& dbName=dbFilesModel.data(dbFilesModel.index(i,0,root), DBFilesModel::NameRole).toString(); if (projectID!=dbName && dbFilesModel.m_configurations.value(dbName).targetLangCode==catalog.targetLangCode()) { SelectJob* j=initSelectJob(m_catalog, m_pos, dbName); connect(j,SIGNAL(done(SelectJob*)),this,SLOT(slotSuggestionsCame(SelectJob*))); m_jobs.append(j); } } } //END query other DBs handling m_entries=job.m_entries; int limit=job.m_entries.size(); if (!limit) { if (m_hasInfo) { m_hasInfo=false; setWindowTitle(m_normTitle); } return; } if (!m_hasInfo) { m_hasInfo=true; setWindowTitle(m_hasInfoTitle); } setUpdatesEnabled(false); m_browser->clear(); m_entryPositions.clear(); //m_entries=job.m_entries; //m_browser->insertHtml(""); int i=0; QTextBlockFormat blockFormatBase; QTextBlockFormat blockFormatAlternate; blockFormatAlternate.setBackground(QPalette().alternateBase()); QTextCharFormat noncloseMatchCharFormat; QTextCharFormat closeMatchCharFormat; closeMatchCharFormat.setFontWeight(QFont::Bold); forever { QTextCursor cur=m_browser->textCursor(); QString html; html.reserve(1024); const TMEntry& entry=job.m_entries.at(i); html+=(entry.score>9500)?QStringLiteral("

"):QStringLiteral("

"); //qCDebug(LOKALIZE_LOG)< 10000 ? 100: float(entry.score)/100); //int sourceStartPos=cur.position(); QString result=entry.diff.toHtmlEscaped(); //result.replace("&","&"); //result.replace("<","<"); //result.replace(">",">"); result.replace(QLatin1String("{KBABELADD}"),QStringLiteral("")); result.replace(QLatin1String("{/KBABELADD}"),QLatin1String("")); result.replace(QLatin1String("{KBABELDEL}"),QStringLiteral("")); result.replace(QLatin1String("{/KBABELDEL}"),QLatin1String("")); result.replace(QLatin1String("\\n"),QLatin1String("\\n
")); result.replace(QLatin1String("\\n"),QLatin1String("\\n
")); html+=result; #if 0 cur.insertHtml(result); cur.movePosition(QTextCursor::PreviousCharacter,QTextCursor::MoveAnchor,cur.position()-sourceStartPos); CatalogString catStr(entry.diff); catStr.string.remove("{KBABELDEL}"); catStr.string.remove("{/KBABELDEL}"); catStr.string.remove("{KBABELADD}"); catStr.string.remove("{/KBABELADD}"); catStr.tags=entry.source.tags; DiffInfo d=getDiffInfo(entry.diff); int j=catStr.tags.size(); while(--j>=0) { catStr.tags[j].start=d.old2DiffClean.at(catStr.tags.at(j).start); catStr.tags[j].end =d.old2DiffClean.at(catStr.tags.at(j).end); } insertContent(cur,catStr,job.m_source,false); #endif //str.replace('&',"&"); TODO check html+=QLatin1String("
"); - if (KDE_ISLIKELY( isetStatusTip(entry.target.string); html+=QStringLiteral("[%1] ").arg(m_actions.at(i)->shortcut().toString(QKeySequence::NativeText)); } else html+=QLatin1String("[ - ] "); /* QString str(entry.target.string); str.replace('<',"<"); str.replace('>',">"); html+=str; */ cur.insertHtml(html); html.clear(); cur.setCharFormat((entry.score>9500)?closeMatchCharFormat:noncloseMatchCharFormat); insertContent(cur,entry.target); m_entryPositions.insert(cur.anchor(),i); html+=i?QStringLiteral("

"):QStringLiteral("

"); cur.insertHtml(html); if (Q_UNLIKELY( ++i>=limit )) break; cur.insertBlock(i%2?blockFormatAlternate:blockFormatBase); } m_browser->insertHtml(QStringLiteral("")); setUpdatesEnabled(true); // qCWarning(LOKALIZE_LOG)<<"ELA "<document()->blockCount(); } /* void TMView::slotPaletteChanged() { }*/ bool TMView::event(QEvent *event) { if (event->type()==QEvent::ToolTip) { QHelpEvent *helpEvent = static_cast(event); //int block1=m_browser->cursorForPosition(m_browser->viewport()->mapFromGlobal(helpEvent->globalPos())).blockNumber(); QMap::iterator block =m_entryPositions.lowerBound(m_browser->cursorForPosition(m_browser->viewport()->mapFromGlobal(helpEvent->globalPos())).anchor()); if (block!=m_entryPositions.end() && *blockurl()) file=i18nc("File argument in tooltip, when file is current file", "this"); QString tooltip=i18nc("@info:tooltip","File: %1
Addition date: %2",file, tmEntry.date.toString(Qt::ISODate)); if (!tmEntry.changeDate.isNull() && tmEntry.changeDate!=tmEntry.date) tooltip+=i18nc("@info:tooltip on TM entry continues","
Last change date: %1", tmEntry.changeDate.toString(Qt::ISODate)); if (!tmEntry.changeAuthor.isEmpty()) tooltip+=i18nc("@info:tooltip on TM entry continues","
Last change author: %1", tmEntry.changeAuthor); tooltip+=i18nc("@info:tooltip on TM entry continues","
TM: %1", tmEntry.dbName); if (tmEntry.obsolete) tooltip+=i18nc("@info:tooltip on TM entry continues","
Is not present in the file anymore"); QToolTip::showText(helpEvent->globalPos(),tooltip); return true; } } return QWidget::event(event); } void TMView::contextMenu(const QPoint& pos) { int block=*m_entryPositions.lowerBound(m_browser->cursorForPosition(pos).anchor()); qCWarning(LOKALIZE_LOG)<=m_entries.size()) return; const TMEntry& e=m_entries.at(block); enum {Remove, Open}; QMenu popup; popup.addAction(i18nc("@action:inmenu", "Remove this entry"))->setData(Remove); if (e.file!= m_catalog->url() && QFile::exists(e.file)) popup.addAction(i18nc("@action:inmenu", "Open file containing this entry"))->setData(Open); QAction* r=popup.exec(m_browser->mapToGlobal(pos)); if (!r) return; if ((r->data().toInt()==Remove) && KMessageBox::Yes==KMessageBox::questionYesNo(this, i18n("Do you really want to remove this entry:
%1
from translation memory %2?", e.target.string.toHtmlEscaped(), e.dbName), i18nc("@title:window","Translation Memory Entry Removal"))) { RemoveJob* job=new RemoveJob(e); connect(job,SIGNAL(done()),this,SLOT(slotNewEntryDisplayed())); TM::threadPool()->start(job, REMOVE); } else if (r->data().toInt()==Open) emit fileOpenRequested(e.file, e.source.string, e.ctxt); } /** * helper function: * searches to th nearest rxNum or ABBR * clears rxNum if ABBR is found before rxNum */ static int nextPlacableIn(const QString& old, int start, QString& cap) { static QRegExp rxNum(QStringLiteral("[\\d\\.\\%]+")); static QRegExp rxAbbr(QStringLiteral("\\w+")); int numPos=rxNum.indexIn(old,start); // int abbrPos=rxAbbr.indexIn(old,start); int abbrPos=start; //qCWarning(LOKALIZE_LOG)<<"seeing"<=0) { if ((c++)->isUpper()) break; } abbrPos+=rxAbbr.matchedLength(); } int pos=qMin(numPos,abbrPos); if (pos==-1) pos=qMax(numPos,abbrPos); // if (pos==numPos) // cap=rxNum.cap(0); // else // cap=rxAbbr.cap(0); cap=(pos==numPos?rxNum:rxAbbr).cap(0); //qCWarning(LOKALIZE_LOG)<]*") % Settings::addColor().name() % QLatin1String("[^>]*\">([^>]*)")); QRegExp rxDel(QLatin1String("]*") % Settings::delColor().name() % QLatin1String("[^>]*\">([^>]*)")); //rxAdd.setMinimal(true); //rxDel.setMinimal(true); //first things first int pos=0; while ((pos=rxDel.indexIn(diff,pos))!=-1) diff.replace(pos,rxDel.matchedLength(),"\tKBABELDEL\t" % rxDel.cap(1) % "\t/KBABELDEL\t"); pos=0; while ((pos=rxAdd.indexIn(diff,pos))!=-1) diff.replace(pos,rxAdd.matchedLength(),"\tKBABELADD\t" % rxAdd.cap(1) % "\t/KBABELADD\t"); diff.replace(QStringLiteral("<"),QStringLiteral("<")); diff.replace(QStringLiteral(">"),QStringLiteral(">")); //possible enhancement: search for non-translated words in removedSubstrings... //QStringList removedSubstrings; //QStringList addedSubstrings; /* 0 - common + - add - - del M - modified so the string is like 00000MM00+++---000 */ DiffInfo d=getDiffInfo(diff); bool sameMarkup=Project::instance()->markup()==entry.markupExpr&&!entry.markupExpr.isEmpty(); bool tryMarkup=!entry.target.tags.size() && sameMarkup; //search for changed markup if (tryMarkup) { QRegExp rxMarkup(entry.markupExpr); rxMarkup.setMinimal(true); pos=0; int replacingPos=0; while ((pos=rxMarkup.indexIn(d.old,pos))!=-1) { //qCWarning(LOKALIZE_LOG)<<"size"<=d.old2DiffClean.at(pos)) d.diffIndex[tmp]='M'; //qCWarning(LOKALIZE_LOG)<<"M"<0) { QByteArray diffMPart(d.diffIndex.left(len)); int m=diffMPart.indexOf('M'); if (m!=-1) diffMPart.truncate(m); #if 0 nono //first goes del, then add. so stop on second del sequence bool seenAdd=false; int j=-1; while(++j=0) d.diffIndex[j]='M'; //qCWarning(LOKALIZE_LOG)<<"M"<=0) d.diffIndex[len+j]='M'; //qCWarning(LOKALIZE_LOG)<<"M"< 500 cases while ((++endPos=0) && (d.old2DiffClean.at(pos)>=0)) // { // qCWarning(LOKALIZE_LOG)<<"d.diffIndex"<=0) &&(d.diffIndex.at(startPos)=='+') //&&(-1!=nextPlacableIn(QString(d.diffClean.at(d.old2DiffClean.at(pos))),0,_)) ) diffMPart.prepend('+'); ++startPos; qCWarning(LOKALIZE_LOG)<<"diffMPart extended 2"<=d.old2DiffClean.at(pos)) d.diffIndex[tmp]='M'; //qCWarning(LOKALIZE_LOG)<<"M"<=m_entries.size() )) return; CatalogString target=targetAdapted(m_entries.at(i), m_catalog->sourceWithTags(m_pos)); #if 0 QString tmp=target.string; tmp.replace(TAGRANGE_IMAGE_SYMBOL, '*'); qCWarning(LOKALIZE_LOG)<<"targetAdapted"<beginMacro(i18nc("@item Undo action","Use translation memory suggestion")); QString old=m_catalog->targetWithTags(m_pos).string; if (!old.isEmpty()) { m_pos.offset=0; //FIXME test! removeTargetSubstring(m_catalog, m_pos, 0, old.size()); //m_catalog->push(new DelTextCmd(m_catalog,m_pos,m_catalog->msgstr(m_pos))); } qCWarning(LOKALIZE_LOG)<<"1"<push(new InsTextCmd(m_catalog,m_pos,target)/*,true*/); insertCatalogString(m_catalog, m_pos, target, 0); if (m_entries.at(i).score>9900 && !m_catalog->isApproved(m_pos.entry)) SetStateCmd::push(m_catalog,m_pos,true); m_catalog->endMacro(); emit refreshRequested(); } diff --git a/src/xlifftextedit.cpp b/src/xlifftextedit.cpp index 2424000..4d14355 100644 --- a/src/xlifftextedit.cpp +++ b/src/xlifftextedit.cpp @@ -1,1395 +1,1394 @@ /* **************************************************************************** This file is part of Lokalize Copyright (C) 2007-2014 by Nick Shaforostoff 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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, see . **************************************************************************** */ #include "xlifftextedit.h" #include "lokalize_debug.h" #include "catalog.h" #include "cmd.h" #include "syntaxhighlighter.h" #include "prefs_lokalize.h" #include "prefs.h" #include "project.h" #include "completionstorage.h" -#include "kdemacros.h" #include #ifndef NOKDE #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include inline static QImage generateImage(const QString& str, const QFont& font) { // im_count++; // QTime a;a.start(); QStyleOptionButton opt; opt.fontMetrics=QFontMetrics(font); opt.text=' '+str+' '; opt.rect=opt.fontMetrics.boundingRect(opt.text).adjusted(0,0,5,5); opt.rect.moveTo(0,0); QImage result(opt.rect.size(),QImage::Format_ARGB32); result.fill(0);//0xAARRGGBB QPainter painter(&result); QApplication::style()->drawControl(QStyle::CE_PushButton,&opt,&painter); // im_time+=a.elapsed(); // qCWarning(LOKALIZE_LOG)<width() + 2*frameWidth(); return QSize(w, h); } bool MyCompletionBox::eventFilter(QObject* object, QEvent* event) { if (event->type()==QEvent::KeyPress) { QKeyEvent* e = static_cast(event); if (e->key()==Qt::Key_PageDown || e->key()==Qt::Key_PageUp) { hide(); return false; } } return KCompletionBox::eventFilter(object, event); } #endif TranslationUnitTextEdit::TranslationUnitTextEdit(Catalog* catalog, DocPosition::Part part, QWidget* parent) : KTextEdit(parent) , m_currentUnicodeNumber(0) , m_langUsesSpaces(true) , m_catalog(catalog) , m_part(part) , m_highlighter(new SyntaxHighlighter(this)) , m_enabled(Settings::autoSpellcheck()) , m_completionBox(0) { setReadOnly(part==DocPosition::Source); setUndoRedoEnabled(false); setAcceptRichText(false); #if !defined(NOKDE) || defined(SONNET_STATIC) m_highlighter->setActive(m_enabled); setHighlighter(m_highlighter); #endif if (part==DocPosition::Target) { connect (document(), SIGNAL(contentsChange(int,int,int)), this, SLOT(contentsChanged(int,int,int))); connect (this,SIGNAL(cursorPositionChanged()), this, SLOT(emitCursorPositionChanged())); } connect (catalog,SIGNAL(signalFileLoaded()), this, SLOT(fileLoaded())); //connect (Project::instance(),SIGNAL(configChanged()), this, SLOT(projectConfigChanged())); } void TranslationUnitTextEdit::setSpellCheckingEnabled(bool enable) { Settings::setAutoSpellcheck(enable); m_enabled=enable; #if !defined(NOKDE) || defined(SONNET_STATIC) m_highlighter->setActive(enable); #endif SettingsController::instance()->dirty=true; } void TranslationUnitTextEdit::fileLoaded() { QString langCode=m_part==DocPosition::Source? m_catalog->sourceLangCode():m_catalog->targetLangCode(); QLocale langLocale(langCode); #if !defined(NOKDE) || defined(SONNET_STATIC) // First try to use a locale name derived from the language code m_highlighter->setCurrentLanguage(langLocale.name()); // If that fails, try to use the language code directly if (m_highlighter->currentLanguage()!=langLocale.name() || m_highlighter->currentLanguage().isEmpty()) { m_highlighter->setCurrentLanguage(langCode); if (m_highlighter->currentLanguage()!=langCode && langCode.length()>2) m_highlighter->setCurrentLanguage(langCode.left(2)); } #endif //"i use an english locale while translating kde pot files from english to hebrew" Bug #181989 Qt::LayoutDirection targetLanguageDirection=Qt::LeftToRight; static QLocale::Language rtlLanguages[]={QLocale::Arabic, QLocale::Hebrew, QLocale::Urdu, QLocale::Persian, QLocale::Pashto}; int i=sizeof(rtlLanguages)/sizeof(QLocale::Arabic); while (--i>=0 && langLocale.language()!=rtlLanguages[i]) ; if (i!=-1) targetLanguageDirection=Qt::RightToLeft; setLayoutDirection(targetLanguageDirection); if (m_part==DocPosition::Source) return; //"Some language do not need space between words. For example Chinese." static QLocale::Language noSpaceLanguages[]={QLocale::Chinese}; i=sizeof(noSpaceLanguages)/sizeof(QLocale::Chinese); while (--i>=0 && langLocale.language()!=noSpaceLanguages[i]) ; m_langUsesSpaces=(i==-1); } void TranslationUnitTextEdit::reflectApprovementState() { if (m_part==DocPosition::Source || m_currentPos.entry==-1) return; bool approved=m_catalog->isApproved(m_currentPos.entry); disconnect (document(), SIGNAL(contentsChange(int,int,int)), this, SLOT(contentsChanged(int,int,int))); m_highlighter->setApprovementState(approved); m_highlighter->rehighlight(); connect (document(), SIGNAL(contentsChange(int,int,int)), this, SLOT(contentsChanged(int,int,int))); viewport()->setBackgroundRole(approved?QPalette::Base:QPalette::AlternateBase); if (approved) emit approvedEntryDisplayed(); else emit nonApprovedEntryDisplayed(); bool untr=m_catalog->isEmpty(m_currentPos); if (untr) emit untranslatedEntryDisplayed(); else emit translatedEntryDisplayed(); } void TranslationUnitTextEdit::reflectUntranslatedState() { if (m_part==DocPosition::Source || m_currentPos.entry==-1) return; bool untr=m_catalog->isEmpty(m_currentPos); if (untr) emit untranslatedEntryDisplayed(); else emit translatedEntryDisplayed(); } /** * makes MsgEdit reflect current entry **/ CatalogString TranslationUnitTextEdit::showPos(DocPosition docPosition, const CatalogString& refStr, bool keepCursor) { docPosition.part=m_part; m_currentPos=docPosition; CatalogString catalogString=m_catalog->catalogString(m_currentPos); QString target=catalogString.string; _oldMsgstr=target; //_oldMsgstrAscii=document()->toPlainText(); <-- MOVED THIS TO THE END //BEGIN pos QTextCursor cursor=textCursor(); int pos=cursor.position(); int anchor=cursor.anchor(); //qCWarning(LOKALIZE_LOG)<<"called"<<"pos"<sourceWithTags(docPosition):refStr); connect (document(), SIGNAL(contentsChange(int,int,int)), this, SLOT(contentsChanged(int,int,int))); _oldMsgstrAscii=document()->toPlainText(); //BEGIN pos QTextCursor t=textCursor(); t.movePosition(QTextCursor::Start); if (pos || anchor) { //qCWarning(LOKALIZE_LOG)<<"setting"<blockSignals(true); clear(); QTextCursor c=textCursor(); insertContent(c,catStr,refStr); document()->blockSignals(false); if (m_part==DocPosition::Target) m_highlighter->setSourceString(refStr.string); else //reflectApprovementState() does this for Target m_highlighter->rehighlight(); //explicitly because the signals were disabled } #if 0 struct SearchFunctor { virtual int operator()(const QString& str, int startingPos); }; int SearchFunctor::operator()(const QString& str, int startingPos) { return str.indexOf(TAGRANGE_IMAGE_SYMBOL, startingPos); } struct AlternativeSearchFunctor: public SearchFunctor { int operator()(const QString& str, int startingPos); }; int AlternativeSearchFunctor::operator()(const QString& str, int startingPos) { int tagPos=str.indexOf(TAGRANGE_IMAGE_SYMBOL, startingPos); int diffStartPos=str.indexOf("{KBABEL", startingPos); int diffEndPos=str.indexOf("{/KBABEL", startingPos); int diffPos=qMin(diffStartPos,diffEndPos); if (diffPos==-1) diffPos=qMax(diffStartPos,diffEndPos); int result=qMin(tagPos,diffPos); if (result==-1) result=qMax(tagPos,diffPos); return result; } #endif void insertContent(QTextCursor& cursor, const CatalogString& catStr, const CatalogString& refStr, bool insertText) { //settings for TMView QTextCharFormat chF=cursor.charFormat(); QFont font=cursor.document()->defaultFont(); //font.setWeight(chF.fontWeight()); QMap posToTag; int i=catStr.tags.size(); while(--i>=0) { //qCDebug(LOKALIZE_LOG)<<"\t"< sourceTagIdToIndex=refStr.tagIdToIndex(); int refTagIndexOffset=sourceTagIdToIndex.size(); i=0; int prev=0; while ((i = catStr.string.indexOf(TAGRANGE_IMAGE_SYMBOL, i)) != -1) { #if 0 SearchFunctor nextStopSymbol=AlternativeSearchFunctor(); char state='0'; while ((i = nextStopSymbol(catStr.string, i)) != -1) { //handle diff display for TMView if (catStr.string.at(i)!=TAGRANGE_IMAGE_SYMBOL) { if (catStr.string.at(i+1)=='/') state='0'; else if (catStr.string.at(i+8)=='D') state='-'; else state='+'; continue; } #endif if (insertText) cursor.insertText(catStr.string.mid(prev,i-prev)); else { cursor.movePosition(QTextCursor::NextCharacter,QTextCursor::MoveAnchor,i-prev); cursor.deleteChar();//delete TAGRANGE_IMAGE_SYMBOL to insert it properly } if (!posToTag.contains(i)) { prev=++i; continue; } int tagIndex=posToTag.value(i); InlineTag tag=catStr.tags.at(tagIndex); QString name=tag.id; QString text; if (tag.type==InlineTag::mrk) text=QStringLiteral("*"); else if (!tag.equivText.isEmpty()) text=tag.equivText; //TODO add number? when? -- right now this is done for gettext qt's 156 mark else text=QString::number(sourceTagIdToIndex.contains(tag.id)?sourceTagIdToIndex.value(tag.id):(tagIndex+refTagIndexOffset)); if (tag.start!=tag.end) { //qCWarning(LOKALIZE_LOG)<<"b"<resource(QTextDocument::ImageResource, QUrl(name)).isNull()) cursor.document()->addResource(QTextDocument::ImageResource, QUrl(name), generateImage(text,font)); cursor.insertImage(name);//NOTE what if twice the same name? cursor.setCharFormat(chF); prev=++i; } cursor.insertText(catStr.string.mid(prev)); } void TranslationUnitTextEdit::contentsChanged(int offset, int charsRemoved, int charsAdded) { Q_ASSERT(m_catalog->targetLangCode().length()); Q_ASSERT(Project::instance()->targetLangCode().length()); //qCWarning(LOKALIZE_LOG)<<"contentsChanged. offset"<toPlainText(); if (editTextAscii==_oldMsgstrAscii) { //qCWarning(LOKALIZE_LOG)<<"stopping"<targetWithTags(pos).string; const QStringRef addedText=editText.midRef(offset,charsAdded); //BEGIN XLIFF markup handling //protect from tag removal //TODO use midRef when Qt 4.8 is in distros bool markupRemoved=charsRemoved && target.midRef(offset,charsRemoved).contains(TAGRANGE_IMAGE_SYMBOL); bool markupAdded=charsAdded && addedText.contains(TAGRANGE_IMAGE_SYMBOL); if (markupRemoved || markupAdded) { bool modified=false; CatalogString targetWithTags=m_catalog->targetWithTags(m_currentPos); //special case when the user presses Del w/o selection if (!charsAdded && charsRemoved==1) { int i=targetWithTags.tags.size(); while(--i>=0) { if (targetWithTags.tags.at(i).start==offset || targetWithTags.tags.at(i).end==offset) { modified=true; pos.offset=targetWithTags.tags.at(i).start; m_catalog->push(new DelTagCmd(m_catalog,pos)); } } } else if (!markupAdded) //check if all { plus } tags were selected { modified=removeTargetSubstring(offset, charsRemoved, /*refresh*/false); if (modified&&charsAdded) m_catalog->push(new InsTextCmd(m_catalog,pos,addedText.toString())); } //qCWarning(LOKALIZE_LOG)<<"calling showPos"; showPos(m_currentPos,CatalogString(),/*keepCursor*/true); if (!modified) { //qCWarning(LOKALIZE_LOG)<<"stop"; return; } } //END XLIFF markup handling else { if (charsRemoved) m_catalog->push(new DelTextCmd(m_catalog,pos,_oldMsgstr.mid(offset,charsRemoved))); _oldMsgstr=editText;//newStr becomes OldStr _oldMsgstrAscii=editTextAscii; //qCWarning(LOKALIZE_LOG)<<"char"<push(new InsTextCmd(m_catalog,pos,addedText.toString())); } /* TODO if (_leds) { if (m_catalog->msgstr(pos).isEmpty()) _leds->ledUntr->on(); else _leds->ledUntr->off(); } */ requestToggleApprovement(); reflectUntranslatedState(); // for mergecatalog (remove entry from index) // and for statusbar emit contentsModified(m_currentPos); #ifndef NOKDE if (charsAdded==1) { int sp=target.lastIndexOf(CompletionStorage::instance()->rxSplit,offset-1); int len=(offset-sp); int wordCompletionLength=Settings::self()->wordCompletionLength(); if (wordCompletionLength>=3 && len>=wordCompletionLength) doCompletion(offset+1); else if (m_completionBox) m_completionBox->hide(); } else if (m_completionBox) m_completionBox->hide(); #endif //qCWarning(LOKALIZE_LOG)<<"finish"; } bool TranslationUnitTextEdit::removeTargetSubstring(int delStart, int delLen, bool refresh) { if (Q_UNLIKELY( m_currentPos.entry==-1 )) return false; if (!::removeTargetSubstring(m_catalog, m_currentPos, delStart, delLen)) return false; requestToggleApprovement(); if (refresh) { //qCWarning(LOKALIZE_LOG)<<"calling showPos"; showPos(m_currentPos,CatalogString(),/*keepCursor*/true/*false*/); } emit contentsModified(m_currentPos.entry); return true; } void TranslationUnitTextEdit::insertCatalogString(CatalogString catStr, int start, bool refresh) { QString REMOVEME=QStringLiteral("REMOVEME"); CatalogString sourceForReferencing=m_catalog->sourceWithTags(m_currentPos); const CatalogString target=m_catalog->targetWithTags(m_currentPos); QHash id2tagIndex; int i=sourceForReferencing.tags.size(); while(--i>=0) id2tagIndex.insert(sourceForReferencing.tags.at(i).id,i); //remove markup that is already in target, to avoid duplicates if the string being inserted contains it as well foreach(const InlineTag& tag, target.tags) { if (id2tagIndex.contains(tag.id)) sourceForReferencing.tags[id2tagIndex.value(tag.id)].id=REMOVEME; } //iterating from the end is essential i=sourceForReferencing.tags.size(); while(--i>=0) if (sourceForReferencing.tags.at(i).id==REMOVEME) sourceForReferencing.tags.removeAt(i); adaptCatalogString(catStr,sourceForReferencing); ::insertCatalogString(m_catalog,m_currentPos,catStr,start); if (refresh) { //qCWarning(LOKALIZE_LOG)<<"calling showPos"; showPos(m_currentPos,CatalogString(),/*keepCursor*/true/*false*/); QTextCursor cursor=textCursor(); cursor.movePosition(QTextCursor::NextCharacter,QTextCursor::MoveAnchor,catStr.string.size()); setTextCursor(cursor); } } const QString LOKALIZE_XLIFF_MIMETYPE=QStringLiteral("application/x-lokalize-xliff+xml"); QMimeData* TranslationUnitTextEdit::createMimeDataFromSelection() const { QMimeData *mimeData = new QMimeData; CatalogString catalogString=m_catalog->catalogString(m_currentPos); QTextCursor cursor=textCursor(); int start=qMin(cursor.anchor(),cursor.position()); int end=qMax(cursor.anchor(),cursor.position()); QMap tagPlaces; if (fillTagPlaces(tagPlaces,catalogString,start,end-start)) { //transform CatalogString //TODO substring method catalogString.string=catalogString.string.mid(start,end-start); QList::iterator it=catalogString.tags.begin(); while (it != catalogString.tags.end()) { if (!tagPlaces.contains(it->start)) it=catalogString.tags.erase(it); else { it->start-=start; it->end-=start; ++it; } } QByteArray a; QDataStream out(&a,QIODevice::WriteOnly); QVariant v; qVariantSetValue(v,catalogString); out<setData(LOKALIZE_XLIFF_MIMETYPE,a); } QString text=catalogString.string; text.remove(TAGRANGE_IMAGE_SYMBOL); mimeData->setText(text); return mimeData; } void TranslationUnitTextEdit::insertFromMimeData(const QMimeData* source) { if (m_part==DocPosition::Source) return; if (source->hasFormat(LOKALIZE_XLIFF_MIMETYPE)) { //qCWarning(LOKALIZE_LOG)<<"has"; QVariant v; QByteArray data=source->data(LOKALIZE_XLIFF_MIMETYPE); QDataStream in(&data,QIODevice::ReadOnly); in>>v; //qCWarning(LOKALIZE_LOG)<<"ins"<(v).string<(v).ranges.size(); int start=0; m_catalog->beginMacro(i18nc("@item Undo action item","Insert text with markup")); QTextCursor cursor=textCursor(); if (cursor.hasSelection()) { start=qMin(cursor.anchor(),cursor.position()); int end=qMax(cursor.anchor(),cursor.position()); removeTargetSubstring(start,end-start); cursor.setPosition(start); setTextCursor(cursor); } else //sets right cursor position implicitly -- needed for mouse paste { QMimeData mimeData; mimeData.setText(QString()); KTextEdit::insertFromMimeData(&mimeData); start=textCursor().position(); } insertCatalogString(v.value(), start); m_catalog->endMacro(); } else { QString text=source->text(); text.remove(TAGRANGE_IMAGE_SYMBOL); insertPlainText(text); } } static bool isMasked(const QString& str, uint col) { if(col == 0 || str.isEmpty()) return false; uint counter=0; int pos=col; while(pos >= 0 && str.at(pos) == '\\') { counter++; pos--; } return !(bool)(counter%2); } void TranslationUnitTextEdit::keyPressEvent(QKeyEvent *keyEvent) { QString spclChars=QStringLiteral("abfnrtv'?\\"); if(keyEvent->matches(QKeySequence::MoveToPreviousPage)) emit gotoPrevRequested(); else if(keyEvent->matches(QKeySequence::MoveToNextPage)) emit gotoNextRequested(); else if(keyEvent->matches(QKeySequence::Undo)) emit undoRequested(); else if(keyEvent->matches(QKeySequence::Redo)) emit redoRequested(); else if(keyEvent->matches(QKeySequence::Find)) emit findRequested(); else if(keyEvent->matches(QKeySequence::FindNext)) emit findNextRequested(); else if(keyEvent->matches(QKeySequence::Replace)) emit replaceRequested(); else if (keyEvent->modifiers() == (Qt::AltModifier|Qt::ControlModifier)) { if(keyEvent->key()==Qt::Key_Home) emit gotoFirstRequested(); else if(keyEvent->key()==Qt::Key_End) emit gotoLastRequested(); } else if (keyEvent->matches(QKeySequence::MoveToNextLine) || keyEvent->matches(QKeySequence::MoveToPreviousLine)) { //static QTime lastUpDownPress; //if (lastUpDownPress.msecsTo(QTime::currentTime())<500) { keyEvent->setAccepted(true); bool up=keyEvent->key()==Qt::Key_Up; QTextCursor c=textCursor(); if (!c.movePosition(up?QTextCursor::Up:QTextCursor::Down)) { QTextCursor::MoveOperation op; if (up && !c.atStart()) op = QTextCursor::Start; else if (!up && !c.atEnd()) op = QTextCursor::End; else if (up) { emit gotoPrevRequested(); op = QTextCursor::End;} else { emit gotoNextRequested(); op = QTextCursor::Start;} c.movePosition(op); } setTextCursor(c); } //lastUpDownPress=QTime::currentTime(); } else if (m_part==DocPosition::Source) return KTextEdit::keyPressEvent(keyEvent); //BEGIN GENERAL // ALT+123 feature TODO this is general so should be on another level else if( (keyEvent->modifiers()&Qt::AltModifier) &&!keyEvent->text().isEmpty() &&keyEvent->text().at(0).isDigit() ) { QString text=keyEvent->text(); while (!text.isEmpty()&& text.at(0).isDigit() ) { m_currentUnicodeNumber = 10*m_currentUnicodeNumber+(text.at(0).digitValue()); text.remove(0,1); } KTextEdit::keyPressEvent(keyEvent); } //END GENERAL else if (!keyEvent->modifiers()&&(keyEvent->key()==Qt::Key_Backspace||keyEvent->key()==Qt::Key_Delete)) { //only for cases when: //-BkSpace was hit and cursor was atStart //-Del was hit and cursor was atEnd if (Q_UNLIKELY( !m_catalog->isApproved(m_currentPos.entry) && !textCursor().hasSelection() ) && ((textCursor().atStart()&&keyEvent->key()==Qt::Key_Backspace) ||(textCursor().atEnd()&&keyEvent->key()==Qt::Key_Delete) )) requestToggleApprovement(); else KTextEdit::keyPressEvent(keyEvent); } else if( keyEvent->key() == Qt::Key_Space && (keyEvent->modifiers()&Qt::AltModifier) ) insertPlainText(QChar(0x00a0U)); else if( keyEvent->key() == Qt::Key_Minus && (keyEvent->modifiers()&Qt::AltModifier) ) insertPlainText(QChar(0x0000AD)); //BEGIN clever editing else if(keyEvent->key()==Qt::Key_Return||keyEvent->key()==Qt::Key_Enter) { #ifndef NOKDE if (m_completionBox&&m_completionBox->isVisible()) { if (m_completionBox->currentItem()) completionActivated(m_completionBox->currentItem()->text()); else qCWarning(LOKALIZE_LOG)<<"avoided a crash. a case for bug 238835!"; m_completionBox->hide(); return; } #endif if (m_catalog->type()!=Gettext) return KTextEdit::keyPressEvent(keyEvent); QString str=toPlainText(); QTextCursor t=textCursor(); int pos=t.position(); QString ins; if( keyEvent->modifiers()&Qt::ShiftModifier ) { if(pos>0 &&!str.isEmpty() &&str.at(pos-1)==QLatin1Char('\\') &&!isMasked(str,pos-1)) { ins='n'; } else { ins=QStringLiteral("\\n"); } } else if(!(keyEvent->modifiers()&Qt::ControlModifier)) { if(m_langUsesSpaces &&pos>0 &&!str.isEmpty() &&!str.at(pos-1).isSpace()) { if(str.at(pos-1)==QLatin1Char('\\') &&!isMasked(str,pos-1)) ins=QLatin1Char('\\'); // if there is no new line at the end if(pos<2||str.midRef(pos-2,2)!=QLatin1String("\\n")) ins+=QLatin1Char(' '); } else if(str.isEmpty()) { ins=QStringLiteral("\\n"); } } if (!str.isEmpty()) { ins+='\n'; insertPlainText(ins); } else KTextEdit::keyPressEvent(keyEvent); } else if (m_catalog->type()!=Gettext) KTextEdit::keyPressEvent(keyEvent); else if( (keyEvent->modifiers()&Qt::ControlModifier)? (keyEvent->key()==Qt::Key_D) : (keyEvent->key()==Qt::Key_Delete) && textCursor().atEnd()) { qCWarning(LOKALIZE_LOG)<<"workaround for Qt/X11 bug"; QTextCursor t=textCursor(); if(!t.hasSelection()) { int pos=t.position(); QString str=toPlainText(); //workaround for Qt/X11 bug: if Del on NumPad is pressed, then pos is beyond end if (pos==str.size()) --pos; if(!str.isEmpty() &&str.at(pos) == '\\' &&!isMasked(str,pos) &&posmodifiers()&&keyEvent->key()==Qt::Key_Backspace) || ( ( keyEvent->modifiers() & Qt::ControlModifier ) && keyEvent->key() == Qt::Key_H ) ) { QTextCursor t=textCursor(); if(!t.hasSelection()) { int pos=t.position(); QString str=toPlainText(); if(!str.isEmpty() && pos>0 && spclChars.contains(str.at(pos-1))) { if(pos>1 && str.at(pos-2)==QLatin1Char('\\') && !isMasked(str,pos-2)) { t.deletePreviousChar(); t.deletePreviousChar(); setTextCursor(t); //qCWarning(LOKALIZE_LOG)<<"set-->"<key() == Qt::Key_Tab) insertPlainText(QStringLiteral("\\t")); else KTextEdit::keyPressEvent(keyEvent); //END clever editing } void TranslationUnitTextEdit::keyReleaseEvent(QKeyEvent* e) { if ( (e->key()==Qt::Key_Alt) && m_currentUnicodeNumber >= 32 ) { insertPlainText(QChar( m_currentUnicodeNumber )); m_currentUnicodeNumber=0; } else KTextEdit::keyReleaseEvent(e); } QString TranslationUnitTextEdit::toPlainText() { QTextCursor cursor = textCursor(); cursor.select(QTextCursor::Document); QString text=cursor.selectedText(); text.replace(QChar(8233),'\n'); /* int ii=text.size(); while(--ii>=0) qCWarning(LOKALIZE_LOG)<push(new InsTagCmd(m_catalog,currentPos(),tag)); showPos(currentPos(),CatalogString(),/*keepCursor*/true); cursor.movePosition(QTextCursor::NextCharacter,QTextCursor::MoveAnchor,tag.end+1+tag.isPaired()); setFocus(); } int TranslationUnitTextEdit::strForMicePosIfUnderTag(QPoint mice, CatalogString& str, bool tryHarder) { if (m_currentPos.entry==-1) return -1; QTextCursor cursor=cursorForPosition(mice); int pos=cursor.position(); str=m_catalog->catalogString(m_currentPos); if (pos==-1 || pos>=str.string.size()) return -1; //qCWarning(LOKALIZE_LOG)<<"here1"<0) // { // cursor.movePosition(QTextCursor::Left); // mice.setX(mice.x()+cursorRect(cursor).width()/2); // pos=cursorForPosition(mice).position(); // } if (str.string.at(pos)!=TAGRANGE_IMAGE_SYMBOL) { bool cont=tryHarder && --pos>=0 && str.string.at(pos)==TAGRANGE_IMAGE_SYMBOL; if (!cont) return -1; } int result=str.tags.size(); while(--result>=0 && str.tags.at(result).start!=pos && str.tags.at(result).end!=pos) ; return result; } void TranslationUnitTextEdit::mouseReleaseEvent(QMouseEvent* event) { if (event->button()==Qt::LeftButton) { CatalogString str; int pos=strForMicePosIfUnderTag(event->pos(),str); if (pos!=-1 && m_part==DocPosition::Source) { emit tagInsertRequested(str.tags.at(pos)); event->accept(); return; } } KTextEdit::mouseReleaseEvent(event); } void TranslationUnitTextEdit::contextMenuEvent(QContextMenuEvent *event) { CatalogString str; int pos=strForMicePosIfUnderTag(event->pos(),str); if (pos!=-1) { QString xid=str.tags.at(pos).xid; if (!xid.isEmpty()) { QMenu menu; int entry=m_catalog->unitById(xid); /* QAction* findUnit=menu.addAction(entry>=m_catalog->numberOfEntries()? i18nc("@action:inmenu","Show the binary unit"): i18nc("@action:inmenu","Go to the referenced entry")); */ QAction* result=menu.exec(event->globalPos()); if (result) { if (entry>=m_catalog->numberOfEntries()) emit binaryUnitSelectRequested(xid); else emit gotoEntryRequested(DocPosition(entry)); event->accept(); } return; } } if (textCursor().hasSelection()) { QMenu menu; QAction* lookup=menu.addAction(i18nc("@action:inmenu","Lookup selected text in translation memory")); if (menu.exec(event->globalPos())) emit tmLookupRequested(m_part,textCursor().selectedText()); return; } if (m_part!=DocPosition::Target) return; KTextEdit::contextMenuEvent(event); #if 0 QTextCursor wordSelectCursor=cursorForPosition(event->pos()); wordSelectCursor.select(QTextCursor::WordUnderCursor); if (m_highlighter->isWordMisspelled(wordSelectCursor.selectedText())) { QMenu menu; QMenu suggestions; foreach(const QString& s, m_highlighter->suggestionsForWord(wordSelectCursor.selectedText())) suggestions.addAction(s); if (!suggestions.isEmpty()) { QAction* answer=suggestions.exec(event->globalPos()); if (answer) { m_catalog->beginMacro(i18nc("@item Undo action item","Replace text")); wordSelectCursor.insertText(answer->text()); m_catalog->endMacro(); } } } #endif // QMenu menu; // QAction* spellchecking=menu.addAction(); // event->accept(); } void TranslationUnitTextEdit::wheelEvent(QWheelEvent *event) { if (m_part==DocPosition::Source || !Settings::mouseWheelGo()) return KTextEdit::wheelEvent(event); switch (event->modifiers()) { case Qt::ControlModifier: if (event->delta() > 0) emit gotoPrevFuzzyRequested(); else emit gotoNextFuzzyRequested(); break; case Qt::AltModifier: if (event->delta() > 0) emit gotoPrevUntranslatedRequested(); else emit gotoNextUntranslatedRequested(); break; case Qt::ControlModifier + Qt::ShiftModifier: if (event->delta() > 0) emit gotoPrevFuzzyUntrRequested(); else emit gotoNextFuzzyUntrRequested(); break; case Qt::ShiftModifier: return KTextEdit::wheelEvent(event); default: if (event->delta() > 0) emit gotoPrevRequested(); else emit gotoNextRequested(); } } void TranslationUnitTextEdit::spellReplace() { #ifndef NOKDE QTextCursor wordSelectCursor=textCursor(); wordSelectCursor.select(QTextCursor::WordUnderCursor); if (!m_highlighter->isWordMisspelled(wordSelectCursor.selectedText())) return; const QStringList& suggestions=m_highlighter->suggestionsForWord(wordSelectCursor.selectedText()); if (suggestions.isEmpty()) return; m_catalog->beginMacro(i18nc("@item Undo action item","Replace text")); wordSelectCursor.insertText(suggestions.first()); m_catalog->endMacro(); #endif } bool TranslationUnitTextEdit::event(QEvent *event) { #ifdef Q_OS_MAC if (event->type()==QEvent::InputMethod) { QInputMethodEvent* e=static_cast(event); insertPlainText(e->commitString()); e->accept(); return true; } #endif if (event->type()==QEvent::ToolTip) { QHelpEvent *helpEvent = static_cast(event); CatalogString str; int pos=strForMicePosIfUnderTag(helpEvent->pos(),str,true); if (pos!=-1) { QString tooltip=str.tags.at(pos).displayName(); QToolTip::showText(helpEvent->globalPos(),tooltip); return true; } #if !defined(NOKDE) || defined(SONNET_STATIC) QString tip; QString langCode=m_highlighter->currentLanguage(); bool nospell=langCode.isEmpty(); if (nospell) langCode=m_part==DocPosition::Source?m_catalog->sourceLangCode():m_catalog->targetLangCode(); QLocale l(langCode); if (l.language()!=QLocale::C) tip = l.nativeLanguageName() + QLatin1String(" ("); tip+=langCode; if (l.language()!=QLocale::C) tip += ')'; if (nospell) tip+=QLatin1String(" - ")%i18n("no spellcheck available"); QToolTip::showText(helpEvent->globalPos(), tip); #endif } return KTextEdit::event(event); } void TranslationUnitTextEdit::tagMenu() {doTag(false);} void TranslationUnitTextEdit::tagImmediate(){doTag(true);} void TranslationUnitTextEdit::doTag(bool immediate) { QMenu menu; QAction* txt=0; CatalogString sourceWithTags=m_catalog->sourceWithTags(m_currentPos); int count=sourceWithTags.tags.size(); if (count) { QMap tagIdToIndex=m_catalog->targetWithTags(m_currentPos).tagIdToIndex(); bool hasActive=false; for (int i=0;isetData(QVariant(i)); if (!hasActive && !tagIdToIndex.contains(sourceWithTags.tags.at(i).id)) { if (immediate) { insertTag(sourceWithTags.tags.at(txt->data().toInt())); return; } hasActive=true; menu.setActiveAction(txt); } } if (immediate) return; txt=menu.exec(mapToGlobal(cursorRect().bottomRight())); if (!txt) return; insertTag(sourceWithTags.tags.at(txt->data().toInt())); } else { if (Q_UNLIKELY( Project::instance()->markup().isEmpty() )) return; //QRegExp tag("(<[^>]*>)+|\\&\\w+\\;"); QRegExp tag(Project::instance()->markup()); tag.setMinimal(true); QString en=m_catalog->sourceWithTags(m_currentPos).string; QString target(toPlainText()); en.remove('\n'); target.remove('\n'); int pos=0; //tag.indexIn(en); int posInMsgStr=0; while ((pos=tag.indexIn(en,pos))!=-1) { /* QString str(tag.cap(0)); str.replace("&","&&");*/ txt=menu.addAction(tag.cap(0)); pos+=tag.matchedLength(); if (posInMsgStr!=-1 && (posInMsgStr=target.indexOf(tag.cap(0),posInMsgStr))==-1) { if (immediate) { insertPlainText(txt->text()); return; } menu.setActiveAction(txt); } else if (posInMsgStr!=-1) posInMsgStr+=tag.matchedLength(); } if (!txt || immediate) return; //txt=menu.exec(_msgidEdit->mapToGlobal(QPoint(0,0))); txt=menu.exec(mapToGlobal(cursorRect().bottomRight())); if (txt) insertPlainText(txt->text()); } } void TranslationUnitTextEdit::source2target() { CatalogString sourceWithTags=m_catalog->sourceWithTags(m_currentPos); QString text=sourceWithTags.string; QString out; QString ctxt=m_catalog->context(m_currentPos.entry).first(); QRegExp delimiter(QStringLiteral("\\s*,\\s*")); //TODO ask for the fillment if the first time. //BEGIN KDE specific part if( ctxt.startsWith( QLatin1String("NAME OF TRANSLATORS") ) || text.startsWith( QLatin1String("_: NAME OF TRANSLATORS\\n") )) { if (!document()->toPlainText().split(delimiter).contains(Settings::authorLocalizedName())) { if (!document()->isEmpty()) out=QLatin1String(", "); out+=Settings::authorLocalizedName(); } } else if( ctxt.startsWith( QLatin1String("EMAIL OF TRANSLATORS") ) || text.startsWith( QLatin1String("_: EMAIL OF TRANSLATORS\\n") )) { if (!document()->toPlainText().split(delimiter).contains(Settings::authorEmail())) { if (!document()->isEmpty()) out=QLatin1String(", "); out+=Settings::authorEmail(); } } else if( /*_catalog->isGeneratedFromDocbook() &&*/ text.startsWith( QLatin1String("ROLES_OF_TRANSLATORS") ) ) { if (!document()->isEmpty()) out='\n'; out+=QLatin1String("\n" "\n" "
")%Settings::authorEmail()%QLatin1String("
\n" "
"); } else if( text.startsWith( QLatin1String("CREDIT_FOR_TRANSLATORS") ) ) { if (!document()->isEmpty()) out='\n'; out+=QLatin1String("")%Settings::authorLocalizedName()%'\n'% QLatin1String("")%Settings::authorEmail()%QLatin1String(""); } //END KDE specific part else { m_catalog->beginMacro(i18nc("@item Undo action item","Copy source to target")); removeTargetSubstring(0,-1,/*refresh*/false); insertCatalogString(sourceWithTags,0,/*refresh*/false); m_catalog->endMacro(); showPos(m_currentPos,sourceWithTags,/*keepCursor*/false); requestToggleApprovement(); } if (!out.isEmpty()) { QTextCursor t=textCursor(); t.movePosition(QTextCursor::End); t.insertText(out); setTextCursor(t); } } void TranslationUnitTextEdit::requestToggleApprovement() { if (m_catalog->isApproved(m_currentPos.entry)||!Settings::autoApprove()) return; bool skip=m_catalog->isPlural(m_currentPos); if (skip) { skip=false; DocPos pos(m_currentPos); for (pos.form=0;pos.formnumberOfPluralForms();++(pos.form)) skip=skip||!m_catalog->isModified(pos); } if (!skip) emit toggleApprovementRequested(); } void TranslationUnitTextEdit::cursorToStart() { QTextCursor t=textCursor(); t.movePosition(QTextCursor::Start); setTextCursor(t); } void TranslationUnitTextEdit::doCompletion(int pos) { #ifndef NOKDE QTime a;a.start(); QString target=m_catalog->targetWithTags(m_currentPos).string; int sp=target.lastIndexOf(CompletionStorage::instance()->rxSplit,pos-1); int len=(pos-sp)-1; QStringList s=CompletionStorage::instance()->makeCompletion(QString::fromRawData(target.unicode()+sp+1,len)); if (!m_completionBox) { //BEGIN creation m_completionBox=new MyCompletionBox(this); connect(m_completionBox,SIGNAL(activated(QString)),this,SLOT(completionActivated(QString))); m_completionBox->setSizePolicy(QSizePolicy::Maximum,QSizePolicy::Preferred); //END creation } m_completionBox->setItems(s); if (s.size() && !s.first().isEmpty()) { m_completionBox->setCurrentRow(0); //qApp->removeEventFilter( m_completionBox ); if (!m_completionBox->isVisible()) //NOTE remove the check if kdelibs gets adapted m_completionBox->show(); m_completionBox->resize(m_completionBox->sizeHint()); QPoint p = cursorRect().bottomRight(); if (p.x()<10) //workaround Qt bug p.rx() += textCursor().verticalMovementX() + QFontMetrics(currentFont()).width('W'); m_completionBox->move(viewport()->mapToGlobal(p)); } else m_completionBox->hide(); #endif } void TranslationUnitTextEdit::doExplicitCompletion() { doCompletion(textCursor().anchor()); } void TranslationUnitTextEdit::completionActivated(const QString& semiWord) { QTextCursor cursor=textCursor(); cursor.insertText(semiWord); setTextCursor(cursor); }