38 #include <threadweaver/ThreadWeaver.h>
39 #include <ktextbrowser.h>
40 #include <kglobalsettings.h>
41 #include <kpassivepopup.h>
43 #include <kmessagebox.h>
46 #include <QDragEnterEvent>
49 #include <QSignalMapper>
63 DiffInfo(
int reserveSize);
70 QVector<int> old2DiffClean;
73 DiffInfo::DiffInfo(
int reserveSize)
75 diffClean.reserve(reserveSize);
76 old.reserve(reserveSize);
77 diffIndex.reserve(reserveSize);
78 old2DiffClean.reserve(reserveSize);
94 DiffInfo d(diff.size());
101 while (++pos<diff.size())
103 if (diff.at(pos)==sep)
105 if (diff.indexOf(
"{KBABELDEL}",pos)==pos)
110 else if (diff.indexOf(
"{KBABELADD}",pos)==pos)
115 else if (diff.indexOf(
"{/KBABEL",pos)==pos)
125 d.old.append(diff.at(pos));
126 d.old2DiffClean.append(d.diffIndex.count());
128 d.diffIndex.append(state);
129 d.diffClean.append(diff.at(pos));
138 KTextBrowser::mouseDoubleClickEvent(event);
140 QString sel=textCursor().selectedText();
141 if (!(sel.isEmpty()||sel.contains(
' ')))
147 :
QDockWidget ( i18nc(
"@title:window",
"Translation Memory"), parent)
150 , m_currentSelectJob(0)
152 , m_normTitle(i18nc(
"@title:window",
"Translation Memory"))
153 , m_hasInfoTitle(m_normTitle+
" [*]")
155 , m_isBatching(false)
156 , m_markAsFuzzy(false)
158 setObjectName(
"TMView");
159 setWidget(m_browser);
161 m_browser->document()->setDefaultStyleSheet(
"p.close_match { font-weight:bold; }");
162 m_browser->viewport()->setBackgroundRole(QPalette::Background);
164 QTimer::singleShot(0,
this,SLOT(initLater()));
165 connect(m_catalog,SIGNAL(signalFileLoaded(KUrl)),
173 ThreadWeaver::Weaver::instance()->dequeue(m_jobs.takeLast());
176 void TMView::initLater()
178 setAcceptDrops(
true);
180 QSignalMapper* signalMapper=
new QSignalMapper(
this);
181 int i=m_actions.size();
184 connect(m_actions.at(i),SIGNAL(triggered()),signalMapper,SLOT(map()));
185 signalMapper->setMapping(m_actions.at(i), i);
189 setToolTip(i18nc(
"@info:tooltip",
"Double-click any word to insert it into translation"));
194 connect(m_browser,SIGNAL(customContextMenuRequested(QPoint)),
this,SLOT(contextMenu(QPoint)));
203 event->acceptProposedAction();
209 event->acceptProposedAction();
219 connect(job,SIGNAL(done(ThreadWeaver::Job*)),job,SLOT(deleteLater()));
220 ThreadWeaver::Weaver::instance()->enqueue(job);
230 ThreadWeaver::Weaver::instance()->dequeue(m_jobs.takeLast());
239 connect(j,SIGNAL(done(ThreadWeaver::Job*)),
this,SLOT(slotCacheSuggestions(ThreadWeaver::Job*)));
245 connect(m_seq,SIGNAL(done(ThreadWeaver::Job*)),m_seq,SLOT(deleteLater()));
246 connect(m_seq,SIGNAL(done(ThreadWeaver::Job*)),
this,SLOT(slotBatchSelectDone(ThreadWeaver::Job*)));
247 ThreadWeaver::Weaver::instance()->enqueue(m_seq);
248 m_jobs.append(m_seq);
251 void TMView::slotCacheSuggestions(ThreadWeaver::Job* j)
262 void TMView::slotBatchSelectDone(ThreadWeaver::Job* )
268 bool insHappened=
false;
276 const QVector<TMEntry>& suggList=m_cache.value(
DocPos(pos));
277 if (suggList.isEmpty())
279 const TMEntry& entry=suggList.first();
280 if (entry.
score<9900)
283 bool forceFuzzy=(suggList.size()>1&&suggList.at(1).score>=10000)
285 bool ctxtMatches=entry.
score==1001;
290 if ( ctxtMatches || !(m_markAsFuzzy||forceFuzzy) )
293 else if ((m_markAsFuzzy&&!ctxtMatches)||forceFuzzy)
307 m_catalog->beginMacro(i18nc(
"@item Undo action",
"Batch translation memory filling"));
310 QString msg=i18nc(
"@info",
"Batch translation has been completed.");
312 m_catalog->endMacro();
317 msg+=i18nc(
"@info",
"No suggestions with exact matches were found.");
320 KPassivePopup::message(KPassivePopup::Balloon,
321 i18nc(
"@title",
"Batch translation complete"),
332 else if (m_jobs.isEmpty())
333 return slotBatchSelectDone(0);
334 KPassivePopup::message(KPassivePopup::Balloon,
335 i18nc(
"@title",
"Batch translation"),
336 i18nc(
"@info",
"Batch translation has been scheduled."),
347 else if (m_jobs.isEmpty())
348 slotBatchSelectDone(0);
349 KPassivePopup::message(KPassivePopup::Balloon,
350 i18nc(
"@title",
"Batch translation"),
351 i18nc(
"@info",
"Batch translation has been scheduled."),
361 ThreadWeaver::Weaver::instance()->dequeue(m_currentSelectJob);
371 &&m_cache.contains(
DocPos(m_pos)))
376 connect(m_currentSelectJob,SIGNAL(done(ThreadWeaver::Job*)),
this,SLOT(
slotSuggestionsCame(ThreadWeaver::Job*)));
388 m_prevCachePos=m_pos;
393 QTime time;time.start();
406 const QString& projectID=project->
projectID();
421 QModelIndex root=dbFilesModel.
rootIndex();
422 int i=dbFilesModel.rowCount(root);
426 const QString& dbName=dbFilesModel.
data(dbFilesModel.index(i,0,root)).toString();
430 connect(j,SIGNAL(done(ThreadWeaver::Job*)),
this,SLOT(
slotSuggestionsCame(ThreadWeaver::Job*)));
446 setWindowTitle(m_normTitle);
453 setWindowTitle(m_hasInfoTitle);
456 setUpdatesEnabled(
false);
458 m_entryPositions.clear();
464 QTextBlockFormat blockFormatBase;
465 QTextBlockFormat blockFormatAlternate; blockFormatAlternate.setBackground(QPalette().alternateBase());
466 QTextCharFormat noncloseMatchCharFormat;
467 QTextCharFormat closeMatchCharFormat; closeMatchCharFormat.setFontWeight(QFont::Bold);
470 QTextCursor cur=m_browser->textCursor();
475 html+=(entry.
score>9500)?
"<p class='close_match'>":
"<p>";
478 html+=QString(
"/%1%/ ").arg(
float(entry.
score)/100);
485 result.replace(
"{KBABELADD}",
"<font style=\"background-color:"%
Settings::addColor().name()%
";color:black\">");
486 result.replace(
"{/KBABELADD}",
"</font>");
487 result.replace(
"{KBABELDEL}",
"<font style=\"background-color:"%
Settings::delColor().name()%
";color:black\">");
488 result.replace(
"{/KBABELDEL}",
"</font>");
489 result.replace(
"\\n",
"\\n<br>");
490 result.replace(
"\\n",
"\\n<br>");
493 cur.insertHtml(result);
495 cur.movePosition(QTextCursor::PreviousCharacter,QTextCursor::MoveAnchor,cur.position()-sourceStartPos);
497 catStr.
string.remove(
"{KBABELDEL}"); catStr.
string.remove(
"{/KBABELDEL}");
498 catStr.
string.remove(
"{KBABELADD}"); catStr.
string.remove(
"{/KBABELADD}");
501 int j=catStr.
tags.size();
504 catStr.
tags[j].start=d.old2DiffClean.at(catStr.
tags.at(j).start);
505 catStr.
tags[j].end =d.old2DiffClean.at(catStr.
tags.at(j).end);
512 if (KDE_ISLIKELY( i<m_actions.size() ))
515 html+=QString(
"[%1] ").arg(m_actions.at(i)->shortcut().toString());
525 cur.insertHtml(html); html.clear();
526 cur.setCharFormat((entry.
score>9500)?closeMatchCharFormat:noncloseMatchCharFormat);
528 m_entryPositions.insert(cur.anchor(),i);
530 html+=i?
"<br></p>":
"</p>";
531 cur.insertHtml(html);
533 if (KDE_ISUNLIKELY( ++i>=limit ))
536 cur.insertBlock(i%2?blockFormatAlternate:blockFormatBase);
539 m_browser->insertHtml(
"</html>");
540 setUpdatesEnabled(
true);
550 bool TMView::event(QEvent *event)
552 if (event->type()==QEvent::ToolTip)
554 QHelpEvent *helpEvent =
static_cast<QHelpEvent *
>(event);
556 QMap<int,int>::iterator block =m_entryPositions.lowerBound(m_browser->cursorForPosition(m_browser->viewport()->mapFromGlobal(helpEvent->globalPos())).anchor());
557 if (block!=m_entryPositions.end() && *block<m_entries.size())
559 const TMEntry& tmEntry=m_entries.at(*block);
560 QString file=tmEntry.
file;
561 if (file==m_catalog->
url().toLocalFile())
562 file=i18nc(
"File argument in tooltip, when file is current file",
"this");
563 QString tooltip=i18nc(
"@info:tooltip",
"File: %1<br />Addition date: %2",file, tmEntry.
date.toString(Qt::ISODate));
565 tooltip+=i18nc(
"@info:tooltip on TM entry continues",
"<br />Last change date: %1", tmEntry.
changeDate.toString(Qt::ISODate));
567 tooltip+=i18nc(
"@info:tooltip on TM entry continues",
"<br />Last change author: %1", tmEntry.
changeAuthor);
568 tooltip+=i18nc(
"@info:tooltip on TM entry continues",
"<br />TM: %1", tmEntry.
dbName);
570 tooltip+=i18nc(
"@info:tooltip on TM entry continues",
"<br />Is not present in the file anymore");
571 QToolTip::showText(helpEvent->globalPos(),tooltip);
575 return QWidget::event(event);
578 void TMView::contextMenu(
const QPoint& pos)
580 int block=*m_entryPositions.lowerBound(m_browser->cursorForPosition(pos).anchor());
582 if (block>=m_entries.size())
585 const TMEntry& e=m_entries.at(block);
588 popup.addAction(i18nc(
"@action:inmenu",
"Remove this entry"))->setData(Remove);
589 if (e.
file!= m_catalog->
url().toLocalFile() && QFile::exists(e.
file))
590 popup.addAction(i18nc(
"@action:inmenu",
"Open file containing this entry"))->setData(Open);
591 QAction* r=popup.exec(m_browser->mapToGlobal(pos));
594 if ((r->data().toInt()==Remove) &&
595 KMessageBox::Yes==KMessageBox::questionYesNo(
this, i18n(
"<html>Do you really want to remove this entry:<br/><i>%1</i><br/>from translation memory %2?</html>",
Qt::escape(e.
target.
string), e.
dbName),
596 i18nc(
"@title:window",
"Translation Memory Entry Removal")))
599 connect(job,SIGNAL(done(ThreadWeaver::Job*)),job,SLOT(deleteLater()));
601 ThreadWeaver::Weaver::instance()->enqueue(job);
603 else if (r->data().toInt()==Open)
614 static QRegExp rxNum(
"[\\d\\.\\%]+");
615 static QRegExp rxAbbr(
"\\w+");
617 int numPos=rxNum.indexIn(old,start);
621 while (((abbrPos=rxAbbr.indexIn(old,abbrPos))!=-1))
623 QString word=rxAbbr.cap(0);
625 const QChar* c=word.unicode()+1;
629 if ((c++)->isUpper())
632 abbrPos+=rxAbbr.matchedLength();
635 int pos=qMin(numPos,abbrPos);
637 pos=qMax(numPos,abbrPos);
644 cap=(pos==numPos?rxNum:rxAbbr).cap(0);
664 QString diff=entry.
diff;
669 QRegExp rxAdd(
"<font style=\"background-color:[^>]*" %
Settings::addColor().name() %
"[^>]*\">([^>]*)</font>");
670 QRegExp rxDel(
"<font style=\"background-color:[^>]*" %
Settings::delColor().name() %
"[^>]*\">([^>]*)</font>");
676 while ((pos=rxDel.indexIn(diff,pos))!=-1)
677 diff.replace(pos,rxDel.matchedLength(),
"\tKBABELDEL\t" % rxDel.cap(1) %
"\t/KBABELDEL\t");
679 while ((pos=rxAdd.indexIn(diff,pos))!=-1)
680 diff.replace(pos,rxAdd.matchedLength(),
"\tKBABELADD\t" % rxAdd.cap(1) %
"\t/KBABELADD\t");
682 diff.replace(
"<",
"<");
683 diff.replace(
">",
">");
701 bool tryMarkup=entry.
target.
tags.size() && sameMarkup;
706 rxMarkup.setMinimal(
true);
709 while ((pos=rxMarkup.indexIn(d.old,pos))!=-1)
712 QByteArray diffIndexPart(d.diffIndex.mid(d.old2DiffClean.at(pos),
713 d.old2DiffClean.at(pos+rxMarkup.matchedLength()-1)+1-d.old2DiffClean.at(pos)));
715 if (diffIndexPart.contains(
'-')
716 ||diffIndexPart.contains(
'+'))
720 newMarkup.reserve(diffIndexPart.size());
722 while(++j<diffIndexPart.size())
724 if (diffIndexPart.at(j)!=
'-')
725 newMarkup.append(d.diffClean.at(d.old2DiffClean.at(pos)+j));
729 int tmp=target.
string.indexOf(rxMarkup.cap(0),replacingPos);
733 rxMarkup.cap(0).size(),
739 tmp=d.old2DiffClean.at(pos+rxMarkup.matchedLength()-1);
740 while(--tmp>=d.old2DiffClean.at(pos))
741 d.diffIndex[tmp]=
'M';
746 pos+=rxMarkup.matchedLength();
753 QRegExp rxNonTranslatable;
755 rxNonTranslatable.setPattern(
"^((" % entry.
markupExpr %
")|(\\W|\\d)+)+");
757 rxNonTranslatable.setPattern(
"^(\\W|\\d)+");
763 int len=d.diffIndex.indexOf(
'0');
766 QByteArray diffMPart(d.diffIndex.left(len));
767 int m=diffMPart.indexOf(
'M');
769 diffMPart.truncate(m);
776 while(++j<diffMPart.size())
778 if (diffMPart.at(j)==
'+')
780 else if (seenAdd && diffMPart.at(j)==
'-')
782 diffMPart.truncate(j);
789 oldMarkup.reserve(diffMPart.size());
791 while(++j<diffMPart.size())
793 if (diffMPart.at(j)!=
'+')
794 oldMarkup.append(d.diffClean.at(j));
798 rxNonTranslatable.indexIn(oldMarkup);
799 oldMarkup=rxNonTranslatable.cap(0);
800 if (target.
string.startsWith(oldMarkup))
805 newMarkup.reserve(diffMPart.size());
807 while(++j<diffMPart.size())
809 if (diffMPart.at(j)!=
'-')
810 newMarkup.append(d.diffClean.at(j));
813 rxNonTranslatable.indexIn(newMarkup);
814 newMarkup=rxNonTranslatable.cap(0);
817 kWarning()<<
"BEGIN HANDLING. replacing"<<target.
string.left(oldMarkup.size())<<
"with"<<newMarkup;
818 target.
remove(0,oldMarkup.size());
819 target.
insert(0,newMarkup);
832 rxNonTranslatable.setPattern(
"(("% entry.
markupExpr %
")|(\\W|\\d)+)+$");
834 rxNonTranslatable.setPattern(
"(\\W|\\d)+$");
837 if (!d.diffIndex.endsWith(
'0'))
839 len=d.diffIndex.lastIndexOf(
'0')+1;
840 QByteArray diffMPart(d.diffIndex.mid(len));
841 int m=diffMPart.lastIndexOf(
'M');
845 diffMPart=diffMPart.mid(len);
850 oldMarkup.reserve(diffMPart.size());
852 while(++j<diffMPart.size())
854 if (diffMPart.at(j)!=
'+')
855 oldMarkup.append(d.diffClean.at(len+j));
858 rxNonTranslatable.indexIn(oldMarkup);
859 oldMarkup=rxNonTranslatable.cap(0);
860 if (target.
string.endsWith(oldMarkup))
865 newMarkup.reserve(diffMPart.size());
867 while(++j<diffMPart.size())
869 if (diffMPart.at(j)!=
'-')
870 newMarkup.append(d.diffClean.at(len+j));
873 rxNonTranslatable.indexIn(newMarkup);
874 newMarkup=rxNonTranslatable.cap(0);
877 target.
string.chop(oldMarkup.size());
878 target.
string.append(newMarkup);
883 d.diffIndex[len+j]=
'M';
896 kWarning()<<
"string:"<<target.
string<<
"searching for placeables in"<<d.old;
899 kWarning()<<
"considering placable"<<cap;
901 int endPos1=pos+cap.size()-1;
902 int endPos=d.old2DiffClean.at(endPos1);
903 int startPos=d.old2DiffClean.at(pos);
904 QByteArray diffMPart=d.diffIndex.mid(startPos,
907 kWarning()<<
"starting diffMPart"<<diffMPart;
910 while ((++endPos<d.diffIndex.size())
911 &&(d.diffIndex.at(endPos)==
'+')
914 diffMPart.append(
'+');
916 kWarning()<<
"diffMPart extended 1"<<diffMPart;
925 while ((--startPos>=0)
926 &&(d.diffIndex.at(startPos)==
'+')
929 diffMPart.prepend(
'+');
932 kWarning()<<
"diffMPart extended 2"<<diffMPart;
934 if ((diffMPart.contains(
'-')
935 ||diffMPart.contains(
'+'))
936 &&(!diffMPart.contains(
'M')))
940 newMarkup.reserve(diffMPart.size());
942 while(++j<diffMPart.size())
944 if (diffMPart.at(j)!=
'-')
945 newMarkup.append(d.diffClean.at(startPos+j));
947 if (newMarkup.endsWith(
' ')) newMarkup.chop(1);
952 int tmp=target.
string.indexOf(cap,replacingPos);
955 kWarning()<<
"replacing"<<cap<<
"with"<<newMarkup;
956 target.
replace(tmp, cap.size(), newMarkup);
960 tmp=d.old2DiffClean.at(endPos1)+1;
961 while(--tmp>=d.old2DiffClean.at(pos))
962 d.diffIndex[tmp]=
'M';
966 kWarning()<<
"newMarkup"<<newMarkup<<
"wasn't used";
976 if (KDE_ISUNLIKELY( i>=m_entries.size() ))
982 QString tmp=target.
string;
984 kWarning()<<
"targetAdapted"<<tmp;
987 kWarning()<<
"tag"<<tag.
start<<tag.
end;
989 if (KDE_ISUNLIKELY( target.
isEmpty() ))
992 m_catalog->beginMacro(i18nc(
"@item Undo action",
"Use translation memory suggestion"));
1002 kWarning()<<
"1"<<target.
string;
1007 if (m_entries.at(i).score>9900 && !m_catalog->
isApproved(m_pos.
entry))
1010 m_catalog->endMacro();
1016 #include "tmview.moc"
int scanRecursive(const QList< QUrl > &urls, const QString &dbName)
wrapper. returns gross number of jobs started
CatalogString targetWithTags(const DocPosition &pos) const
bool dragIsAcceptable(const QList< QUrl > &urls)
void dropEvent(QDropEvent *)
QString targetLangCode() const
static int nextPlacableIn(const QString &old, int start, QString &cap)
helper function: searches to th nearest rxNum or ABBR clears rxNum if ABBR is found before rxNum ...
static void push(Catalog *catalog, const DocPosition &pos, bool approved)
#define TAGRANGE_IMAGE_SYMBOL
bool switchNext(Catalog *&catalog, DocPosition &pos, int parts)
QMap< QString, TMConfig > m_configurations
static Project * instance()
void mouseDoubleClickEvent(QMouseEvent *event)
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const
QList< TMEntry > m_entries
void slotNewEntryDisplayed(const DocPosition &pos=DocPosition())
QString markup() const
Get Markup.
void insertContent(QTextCursor &cursor, const CatalogString &catStr, const CatalogString &refStr, bool insertText)
static QColor delColor()
Get DelColor.
void insert(int position, const QString &str)
void slotFileLoaded(const KUrl &)
bool isEmpty(uint index) const
CatalogString targetAdapted(const TMEntry &entry, const CatalogString &ref)
this tries some black magic naturally, there are many assumptions that might not always be true ...
static DiffInfo getDiffInfo(const QString &diff)
0 - common
This struct represents a position in a catalog.
void textInsertRequested(const QString &)
void slotSuggestionsCame(ThreadWeaver::Job *)
Singleton object that represents project.
bool removeTargetSubstring(Catalog *catalog, DocPosition pos, int delStart, int delLen)
static QColor addColor()
Get AddColor.
TMView(QWidget *, Catalog *, const QVector< KAction * > &)
static QString escape(QString str)
void slotUseSuggestion(int)
SelectJob * initSelectJob(Catalog *, DocPosition pos, QString db=QString(), int opt=Enqueue)
void adaptCatalogString(CatalogString &target, const CatalogString &ref)
prepares
static int suggCount()
Get SuggCount.
static bool scanToTMOnOpen()
Get ScanToTMOnOpen.
data structure used to pass info about inline elements a XLIFF tag is represented by a TAGRANGE_IMAGE...
void insertCatalogString(Catalog *catalog, DocPosition pos, const CatalogString &catStr, int start)
void fileOpenRequested(const KUrl &path, const QString &str, const QString &ctxt)
QString projectID() const
Get ProjectID.
QModelIndex rootIndex() const
CatalogString sourceWithTags(const DocPosition &pos) const
bool isApproved(uint index) const
int numberOfEntries() const
simpler version of DocPosition for use in QMap
void slotBatchTranslateFuzzy()
This class represents a catalog It uses CatalogStorage interface to work with catalogs in different f...
void replace(int position, int len, const QString &str)
void remove(int position, int len)
void textInsertRequested(const QString &)
data structure used to pass info about inline elements a XLIFF tag is represented by a TAGRANGE_IMAGE...
void slotBatchTranslate()
static bool prefetchTM()
Get PrefetchTM.
static DBFilesModel * instance()
void dragEnterEvent(QDragEnterEvent *event)