/************************************************************************** OmegaT - Computer Assisted Translation (CAT) tool with fuzzy matching, translation memory, keyword search, glossaries, and translation leveraging into updated projects. Copyright (C) 2015 Yu Tang 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.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Point; import java.awt.Polygon; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.font.TextLayout; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Map.Entry; import javax.swing.JLayeredPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.SwingUtilities; import org.omegat.core.Core; import org.omegat.util.Preferences; import org.omegat.util.StaticUtils; import org.omegat.util.gui.UIThreadsUtil; /** * Popup panel for displaying alphabetical markers. * * @author Yu Tang */ @SuppressWarnings("serial") public abstract class AlphabeticalMarkers extends JPanel { private static final String DEFAULT_MARKER_FONT_NAME = "Century"; private static final int FIRST_TITLE_LETTER = 'a'; private final ColorScheme colorScheme; private final Font TITLE_FONT = getTitleFont(); private final int BOX_SIZE = getBoxSize(TITLE_FONT); private final Polygon MARKER_SHAPE = createMarkerShape(BOX_SIZE); private final Rectangle GUIDING_SQUARE = new Rectangle(BOX_SIZE, BOX_SIZE); private List<Marker> markers = null; private final JLayeredPane parent; private final JScrollPane scrollPane; private final boolean sourceLangIsRTL; AlphabeticalMarkers(JScrollPane scrollPane) { this.parent = Core.getMainWindow().getApplicationFrame().getLayeredPane(); this.scrollPane = scrollPane; String sourceLang = Core.getProject().getProjectProperties() .getSourceLanguage().getLanguageCode(); this.sourceLangIsRTL = EditorUtils.isRTL(sourceLang); this.colorScheme = createColorScheme(scrollPane.getViewport().getView().getBackground()); } private ColorScheme createColorScheme(final Color editorBackground) { int MINIMUM_VISIBILITY = 0x8000; int distanceToLightScheme = calculateSSD(editorBackground, Color.YELLOW); if (distanceToLightScheme >= MINIMUM_VISIBILITY) { // Use the light scheme: background, foreground, border return new ColorScheme(Color.YELLOW, Color.RED, Color.ORANGE); } else { // Use the dark scheme return new ColorScheme(Color.DARK_GRAY, Color.GREEN, Color.MAGENTA); } } // calcurate SSD (Sum of Squared Difference) for colors private int calculateSSD(final Color a, final Color b) { int db = a.getBlue() - b.getBlue(); int dg = a.getGreen() - b.getGreen(); int dr = a.getRed() - b.getRed(); return db * db + dg * dg + dr * dr; } @Override public void paint(Graphics g) { try { // translate location Point srcLocation = new Point(0, 0); Point dstLocation = SwingUtilities.convertPoint(scrollPane, srcLocation, this); Graphics2D g2 = (Graphics2D) g.create(); g2.translate(dstLocation.x, dstLocation.y); // enable AntiAliasing if (!g2.getFontRenderContext().isAntiAliased()) { g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); } // draw marker for (Marker marker : markers) { drawMarker(g2, marker.location, String.valueOf(Character.toChars(marker.title))); } g2.dispose(); } catch (Exception e) { System.out.println(e.getMessage()); } } private void drawMarker(Graphics2D g2, Point location, String title) { // map location to the appropriate corner of the guiding square. Point boxLocation = new Point(location); if (sourceLangIsRTL) { boxLocation.translate(-BOX_SIZE, -BOX_SIZE); // right-bottom corner } else { boxLocation.translate(0, -BOX_SIZE); // left-bottom corner } GUIDING_SQUARE.setLocation(boxLocation); // create TextLayout TextLayout layout = new TextLayout(title, TITLE_FONT, g2.getFontRenderContext()); Rectangle pixelBounds = layout.getPixelBounds(null, location.x, location.y); Dimension diffForCentered = getCenteredDimension(pixelBounds, GUIDING_SQUARE); // set marker shape's position MARKER_SHAPE.translate(boxLocation.x, boxLocation.y); // fill rectangle g2.setColor(colorScheme.background); g2.fill(MARKER_SHAPE); // draw rectangle g2.setColor(colorScheme.border); g2.draw(MARKER_SHAPE); // hideMarkers position for next time MARKER_SHAPE.translate(-boxLocation.x, -boxLocation.y); // draw title letter g2.setColor(colorScheme.foreground); layout.draw(g2, location.x + diffForCentered.width, location.y + diffForCentered.height); } // +------+ // | | // | | // +-- --+ // \/ private static Polygon createMarkerShape(int boxSize) { Polygon poly = new Polygon(); poly.addPoint(0, boxSize); poly.addPoint(boxSize / 3, boxSize); poly.addPoint(boxSize / 2, boxSize + boxSize / 3); poly.addPoint(boxSize - boxSize / 3, boxSize); poly.addPoint(boxSize, boxSize); poly.addPoint(boxSize, 0); poly.addPoint(0, 0); return poly; } private static Dimension getCenteredDimension(Rectangle target, Rectangle base) { double baseCenterX = base.getCenterX(); double baseCenterY = base.getCenterY(); double targetCenterX = target.getCenterX(); double targetCenterY = target.getCenterY(); double diffX = 0, diffY = 0; if (baseCenterX != targetCenterX) { diffX = baseCenterX - targetCenterX; } if (baseCenterY != targetCenterY) { diffY = baseCenterY - targetCenterY; } return new Dimension((int) diffX, (int) diffY); } private static Font getTitleFont() { boolean fontAvailable = Arrays.asList(StaticUtils.getFontNames()) .contains(DEFAULT_MARKER_FONT_NAME); String fontName = fontAvailable ? DEFAULT_MARKER_FONT_NAME : Font.SERIF; int fontSize = Preferences.getPreferenceDefault( Preferences.TF_SRC_FONT_SIZE, Preferences.TF_FONT_SIZE_DEFAULT); return new Font(fontName, Font.BOLD, fontSize); } private static int getBoxSize(Font baseFont) { return (int) (baseFont.getSize2D() * 1.4f); } /** * Makes the alphabetical markers visible. */ public void showMarkers() { UIThreadsUtil.mustBeSwingThread(); markers = createMarkers(getViewableSegmentLocations()); if (markers.isEmpty()) { return; } setSize(parent.getWidth() - 1, parent.getHeight() - 1); parent.add(this, JLayeredPane.POPUP_LAYER, 0); // top most parent.validate(); parent.repaint(); } /** * Makes the alphabetical markers invisible. */ public void hideMarkers() { UIThreadsUtil.mustBeSwingThread(); if (markers != null && !markers.isEmpty()) { parent.remove(this); parent.validate(); parent.repaint(); markers.clear(); } markers = null; } private static List<Marker> createMarkers(Map<Integer, Point> map) { List<Marker> list = new ArrayList<Marker>(); int title = FIRST_TITLE_LETTER; for (Entry<Integer, Point> entry : map.entrySet()) { Marker marker = new Marker(); marker.segmentNumber = entry.getKey(); marker.location = entry.getValue(); marker.title = title++; list.add(marker); } return list; } protected abstract Map<Integer, Point> getViewableSegmentLocations(); /** * Translate a marker title letter to a segment number. If the letter * found, it will be converted to actual segment number string. * @param inputValue * @return if the letter found translated string, otherwise inputValue. */ public String translateSegmentNumber(String inputValue) { try { Marker marker = findMarkerByTitle(inputValue); return String.valueOf(marker.segmentNumber); } catch(Exception ex) { return inputValue; } } /** * Returns <tt>true</tt> if this list contains the specified title. * @param title as segment shortcut letter * @return <tt>true</tt> if this list contains the specified title */ public boolean containsTitle(int title) { try { findMarkerByTitle(title); return true; } catch(Exception ex) { return false; } } private Marker findMarkerByTitle(String title) { String trimmed = title.trim(); if (trimmed.codePointCount(0, trimmed.length()) == 1) { int cp = trimmed.codePointAt(0); return findMarkerByTitle(cp); } throw new RuntimeException("Marker with the title '" + title + "' is not found"); } private Marker findMarkerByTitle(int title) { for (Marker marker : markers) { if (title == marker.title) { return marker; } } throw new RuntimeException("Marker with the title '" + String.valueOf(Character.toChars(title)) + "' is not found"); } private static class Marker { int segmentNumber = 0; Point location = null; int title = 0; @Override public String toString() { return this.getClass().getSimpleName() + " {" + this.segmentNumber + ", '" + this.title + "', " + this.location.toString() + "}"; } } private static class ColorScheme { Color background; Color foreground; Color border; public ColorScheme(Color background, Color foreground, Color border) { this.background = background; this.foreground = foreground; this.border = border; } } }