/**************************************************************************
OmegaT - Computer Assisted Translation (CAT) tool
with fuzzy matching, translation memory, keyword search,
glossaries, and translation leveraging into updated projects.
Copyright (C) 2010-2013 Alex Buloichik
2014 Aaron Madlon-Kay
Home page: http://www.omegat.org/
Support center: http://groups.yahoo.com/group/OmegaT/
This file is part of OmegaT.
OmegaT 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 3 of the License, or
(at your option) any later version.
OmegaT 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 <http://www.gnu.org/licenses/>.
**************************************************************************/
package org.omegat.gui.editor;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import javax.swing.SwingUtilities;
import javax.swing.text.BadLocationException;
import javax.swing.text.Highlighter;
import javax.swing.text.Position;
import org.omegat.core.Core;
import org.omegat.filters2.master.PluginUtils;
import org.omegat.gui.editor.mark.CalcMarkersThread;
import org.omegat.gui.editor.mark.EntryMarks;
import org.omegat.gui.editor.mark.IMarker;
import org.omegat.gui.editor.mark.Mark;
import org.omegat.util.Log;
import org.omegat.util.gui.UIThreadsUtil;
/**
* Class for manage marks and controll all markers.
*
* All markers for inactive segment usually executed in background threads(one
* thread for one marker class), but markers for active segment executed in UI
* thread immediately.
*
* @author Alex Buloichik (alex73mail@gmail.com)
*/
public class MarkerController {
private final EditorController ec;
/** List of marker's class names. */
private final String[] markerNames;
/** Threads for each marker. */
protected final CalcMarkersThread[] markerThreads;
private final Highlighter highlighter;
MarkerController(EditorController ec) {
this.ec = ec;
this.highlighter = ec.editor.getHighlighter();
List<IMarker> ms = new ArrayList<IMarker>();
// start all markers threads
for (Class<?> mc : PluginUtils.getMarkerClasses()) {
try {
ms.add((IMarker) mc.newInstance());
} catch (Exception ex) {
Log.logErrorRB(ex, "PLUGIN_MARKER_INITIALIZE", mc.getName());
}
}
for (IMarker marker : Core.getMarkers()) {
ms.add(marker);
}
markerThreads = new CalcMarkersThread[ms.size()];
markerNames = new String[ms.size()];
for (int i = 0; i < ms.size(); i++) {
IMarker m = ms.get(i);
markerNames[i] = m.getClass().getName();
markerThreads[i] = new CalcMarkersThread(this, m, i);
markerThreads[i].start();
}
}
/**
* Get marker's index by class name.
*
* @param markerClassName
* marker's class name
* @return marker's index
*/
int getMarkerIndex(final String markerClassName) {
for (int i = 0; i < markerNames.length; i++) {
if (markerNames[i].equals(markerClassName)) {
return i;
}
}
return -1;
}
/**
* Remove all marks for all entries.
*
* @param newEntriesCount
* count of newly displayed entries
*/
void removeAll() {
UIThreadsUtil.mustBeSwingThread();
for (CalcMarkersThread th : markerThreads) {
th.reset();
}
synchronized (outputQueue) {
outputQueue.clear();
}
highlighter.removeAllHighlights();
}
/**
* Remove marks for one segment for one marker.
*/
void remove(SegmentBuilder sb, int makerIndex) {
UIThreadsUtil.mustBeSwingThread();
if (sb.marks == null) {
return;
}
MarkInfo[] me = sb.marks[makerIndex];
if (me != null) {
for (int j = 0; j < me.length; j++) {
if (me[j] != null && me[j].highlight != null) {
highlighter.removeHighlight(me[j].highlight);
}
}
sb.marks[makerIndex] = null;
}
}
/**
* Reprocess all entries for one marker only. Usually used for spell
* checking or one marker state changes.
*/
public void reprocess(SegmentBuilder[] entryBuilders, int markerIndex) {
UIThreadsUtil.mustBeSwingThread();
if (entryBuilders == null) {
return;
}
for (SegmentBuilder sb : entryBuilders) {
if (!sb.hasBeenCreated()) {
continue;
}
remove(sb, markerIndex);
}
markerThreads[markerIndex].add(entryBuilders);
}
/**
* Reprocess one entry immediately, in current thread. Usually used for
* active entry.
*/
public void reprocessImmediately(SegmentBuilder entryBuilder) {
UIThreadsUtil.mustBeSwingThread();
entryBuilder.resetTextAttributes();
List<EntryMarks> evs = new ArrayList<EntryMarks>();
for (int i = 0; i < markerNames.length; i++) {
remove(entryBuilder, i);
try {
EntryMarks ev = new EntryMarks(entryBuilder, entryBuilder.getDisplayVersion(), i);
ev.result = markerThreads[i].marker.getMarksForEntry(ev.ste, ev.sourceText, ev.translationText,
ev.isActive);
if (ev.result != null) {
evs.add(ev);
}
} catch (Throwable ex) {
Log.log(ex);
}
}
marksOutput(evs);
}
/**
* Process all segment for all markers.
*/
public void process(SegmentBuilder[] entryBuilders) {
UIThreadsUtil.mustBeSwingThread();
for (CalcMarkersThread th : markerThreads) {
th.add(entryBuilders);
}
}
/**
* Return tooltips texts for specified editor position.
*
* @param entryIndex
* @param pos
* @return
*/
public String getToolTips(int entryIndex, int pos) {
UIThreadsUtil.mustBeSwingThread();
if (entryIndex >= ec.m_docSegList.length || entryIndex < 0) {
return null;
}
MarkInfo[][] m = ec.m_docSegList[entryIndex].marks;
if (m == null) {
return null;
}
StringBuilder res = new StringBuilder();
for (int i = 0; i < m.length; i++) {
if (m[i] == null) {
continue;
}
for (MarkInfo t : m[i]) {
if (t != null && t.tooltip != null) {
if (t.tooltip.p0.getOffset() <= pos && t.tooltip.p1.getOffset() >= pos) {
if (res.length() > 0) {
res.append("<br>");
}
res.append(t.tooltip.text);
}
}
}
}
if (res.length() == 0) {
return null;
}
String r = res.toString();
r = r.replace("<suggestion>", "<b>");
r = r.replace("</suggestion>", "</b>");
return "<html>" + r + "</html>";
}
private final Queue<EntryMarks> outputQueue = new LinkedList<EntryMarks>();
public void queueMarksOutput(EntryMarks ev) {
synchronized (outputQueue) {
// output marks
outputQueue.add(ev);
outputQueue.notifyAll();
}
SwingUtilities.invokeLater(new Runnable() {
public void run() {
List<EntryMarks> evs = new ArrayList<EntryMarks>();
synchronized (outputQueue) {
while (true) {
EntryMarks ev = outputQueue.poll();
if (ev == null) {
break;
}
evs.add(ev);
}
}
marksOutput(evs);
}
});
}
/**
* Output marks.
*/
private void marksOutput(List<EntryMarks> evs) {
UIThreadsUtil.mustBeSwingThread();
if (evs.isEmpty()) {
return;
}
Document3 doc = ec.editor.getOmDocument();
doc.trustedChangesInProgress = true;
try {
for (int i = 0; i < evs.size(); i++) {
EntryMarks ev = evs.get(i);
if (!ev.isSegmentChanged()) {
remove(ev.builder, ev.markerIndex);
try {
if (ev.builder.marks == null) {
ev.builder.marks = new MarkInfo[markerNames.length][];
}
ev.builder.marks[ev.markerIndex] = new MarkInfo[ev.result.size()];
for (int j = 0; j < ev.result.size(); j++) {
MarkInfo nm = new MarkInfo(ev.result.get(j), ev.builder, doc, highlighter);
ev.builder.marks[ev.markerIndex][j] = nm;
}
} catch (BadLocationException ex) {
}
}
}
} finally {
doc.trustedChangesInProgress = false;
}
}
/**
* Class for store info about displayed mark.
*/
protected static class MarkInfo {
Highlighter.Highlight highlight;
Tooltip tooltip;
public MarkInfo(Mark m, SegmentBuilder sb, Document3 doc, Highlighter highlighter) throws BadLocationException {
if (m.entryPart == Mark.ENTRY_PART.SOURCE && sb.getSourceText() == null) {
return;
}
int sourceStartOffset = sb.getStartSourcePosition();
int translationStartOffset;
if (sb.isActive()) {
translationStartOffset = doc.getTranslationStart();
} else {
translationStartOffset = sb.getStartTranslationPosition();
}
int startOffset;
if (m.entryPart == Mark.ENTRY_PART.SOURCE) {
startOffset = sourceStartOffset;
} else {
startOffset = translationStartOffset;
}
if (m.painter != null) {
highlight = (Highlighter.Highlight) highlighter.addHighlight(startOffset + m.startOffset, startOffset
+ m.endOffset, m.painter);
}
if (m.toolTipText != null) {
tooltip = new Tooltip(doc, startOffset + m.startOffset, startOffset + m.endOffset, m.toolTipText);
}
if (m.attributes != null) {
doc.setCharacterAttributes(startOffset + m.startOffset, m.endOffset - m.startOffset, m.attributes,
false);
}
}
}
protected static class Tooltip {
Position p0, p1;
String text;
public Tooltip(Document3 doc, int start, int end, String text) throws BadLocationException {
p0 = doc.createPosition(start);
p1 = doc.createPosition(end);
this.text = text;
}
}
}