/**************************************************************************
OmegaT - Computer Assisted Translation (CAT) tool
with fuzzy matching, translation memory, keyword search,
glossaries, and translation leveraging into updated projects.
Copyright (C) 2016 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.issues;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.ActionListener;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.swing.Icon;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;
import org.omegat.core.Core;
import org.omegat.core.data.TMXEntry;
import org.omegat.core.tagvalidation.ErrorReport;
import org.omegat.core.tagvalidation.ErrorReport.TagError;
import org.omegat.core.tagvalidation.TagValidationTool;
import org.omegat.util.OStrings;
import org.omegat.util.StringUtil;
import org.omegat.util.TagUtil.Tag;
import org.omegat.util.gui.CharacterWrapEditorKit;
import org.omegat.util.gui.Styles.EditorColor;
/**
* A class representing problems with tags in a translation. One instance holds
* all problems for the given source/target text pair.
*
* @author Aaron Madlon-Kay
*
*/
public class TagIssue implements IIssue {
private static final Icon ICON = new SimpleColorIcon(EditorColor.COLOR_PLACEHOLDER.getColor());
private final ErrorReport report;
public TagIssue(ErrorReport report) {
this.report = report;
}
@Override
public Icon getIcon() {
return ICON;
}
@Override
public String getTypeName() {
return OStrings.getString("ISSUES_TAGS_TYPE");
}
@Override
public int getSegmentNumber() {
return report.entryNum;
}
@Override
public String getDescription() {
Map<TagError, Long> freq = Stream.of(report.srcErrors, report.transErrors).flatMap(m -> m.values().stream())
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
return "<html>" + freq.entrySet().stream().sorted(Comparator.comparing(Map.Entry::getKey)).map(e -> {
TagError error = e.getKey();
String message = ErrorReport.localizedTagError(e.getKey());
String html = colorizeByHTML(message, error);
return StringUtil.format(OStrings.getString("ISSUES_TAGS_ERROR_TEMPLATE"), html, e.getValue());
}).collect(Collectors.joining(OStrings.getString("ISSUES_TAGS_ERROR_DELIMITER"))) + "</html>";
}
@Override
public Component getDetailComponent() {
TripleSplitButtonPanel panel = new TripleSplitButtonPanel();
panel.firstTextPane.setEditorKit(new CharacterWrapEditorKit());
panel.middleTextPane.setEditorKit(new CharacterWrapEditorKit());
panel.lastTextPane.setEditorKit(new CharacterWrapEditorKit());
panel.firstButton.setBorderPainted(false);
panel.middleButton.setBorderPainted(false);
try {
insertText(panel.firstTextPane.getStyledDocument(), report.source, report.srcErrors);
insertText(panel.middleTextPane.getStyledDocument(), report.translation, report.transErrors);
} catch (BadLocationException ex) {
// Ignore
}
int minHeight = panel.firstTextPane.getFont().getSize() * 6;
String fixText = TagValidationTool.fixErrors(report);
if (fixText == null) {
panel.lastPanel.setVisible(false);
panel.firstButton.setVisible(false);
panel.middleButton.setVisible(false);
} else {
panel.lastTextPane.setText(fixText);
org.openide.awt.Mnemonics.setLocalizedText(panel.lastButton, OStrings.getString("ISSUES_TAGS_BUTTON_APPLY_FIX"));
panel.lastButton.addActionListener(getFixActionListener(fixText));
minHeight += panel.lastButton.getPreferredSize().height;
}
panel.setMinimumSize(new Dimension(0, minHeight));
return panel;
}
private void insertText(StyledDocument doc, String text, Map<Tag, TagError> errors)
throws BadLocationException {
doc.insertString(0, text, null);
for (Map.Entry<Tag, TagError> e : errors.entrySet()) {
Tag tag = e.getKey();
doc.setCharacterAttributes(tag.pos, tag.tag.length(), styleForError(e.getValue()), false);
}
}
@Override
public boolean hasMenuComponents() {
return true;
}
@Override
public List<? extends JMenuItem> getMenuComponents() {
String fixText = TagValidationTool.fixErrors(report);
if (fixText == null) {
return Collections.emptyList();
} else {
JMenuItem doFix = new JMenuItem();
org.openide.awt.Mnemonics.setLocalizedText(doFix, OStrings.getString("ISSUES_TAGS_BUTTON_APPLY_FIX"));
doFix.addActionListener(getFixActionListener(fixText));
return Arrays.asList(doFix);
}
}
private ActionListener getFixActionListener(String fixText) {
return e -> {
if (!doFix(report, fixText)) {
JOptionPane.showMessageDialog(Core.getMainWindow().getApplicationFrame(),
OStrings.getString("TAG_FIX_ERROR_MESSAGE"), OStrings.getString("TAG_FIX_ERROR_TITLE"),
JOptionPane.ERROR_MESSAGE);
}
};
}
@SuppressWarnings("fallthrough")
public static AttributeSet styleForError(TagError error) {
SimpleAttributeSet attr = new SimpleAttributeSet();
StyleConstants.setBold(attr, true);
switch (error) {
case EXTRANEOUS:
StyleConstants.setStrikeThrough(attr, true);
case MISSING:
case MALFORMED:
case WHITESPACE:
StyleConstants.setForeground(attr, Color.RED);
break;
case DUPLICATE:
StyleConstants.setForeground(attr, Color.decode("#800080")); // purple
break;
case ORPHANED:
StyleConstants.setUnderline(attr, true);
case ORDER:
StyleConstants.setForeground(attr, Color.decode("#FF8C00")); // orange
break;
case UNSPECIFIED:
StyleConstants.setForeground(attr, Color.BLUE);
break;
}
return attr;
}
/**
* Fix all errors in a given report, and commit the changed translation to
* the project. Checks to make sure the translation has not been changed in
* the meantime.
*
* @param report
* The report to fix
* @return Whether or not the fix succeeded
*/
private boolean doFix(ErrorReport report, String fixed) {
// Make sure the translation hasn't changed in the editor.
TMXEntry prevTrans = Core.getProject().getTranslationInfo(report.ste);
if (!report.translation.equals(prevTrans.translation)) {
return false;
}
// Put modified translation back into project.
Core.getEditor().gotoEntry(report.entryNum);
Core.getEditor().replaceEditTextAndMark(fixed);
return true;
}
@SuppressWarnings("fallthrough")
public static String colorizeByHTML(String text, TagError error) {
String color = "black";
switch (error) {
case EXTRANEOUS:
text = "<strike>" + text + "</strike>";
case MISSING:
case MALFORMED:
case WHITESPACE:
color = "red";
break;
case DUPLICATE:
color = "purple";
break;
case ORPHANED:
text = "<u>" + text + "</u>";
case ORDER:
color = "#FF8C00"; // Orange. Pre-1.7 Java doesn't recognize the
// name "orange".
break;
case UNSPECIFIED:
color = "blue";
break;
}
return "<font color=\"" + color + "\"><b>" + text + "</b></font>";
}
}