Last active
August 29, 2015 14:11
-
-
Save yu-tang/7a5007c4d05ffbeada33 to your computer and use it in GitHub Desktop.
PoC for segment shortcut
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* PoC for segment shortcut | |
* | |
* Related RFE : http://sourceforge.net/p/omegat/feature-requests/1038/ | |
* Hint : Put this script to <ScriptsDir>/application_startup folder | |
* to automatically execution at every time OmT launching. | |
* | |
* @author Yu Tang | |
* @date 2014-12-21 | |
* @version 0.1 | |
*/ | |
import groovy.transform.ToString | |
import org.omegat.core.Core | |
import org.omegat.gui.editor.EditorController | |
import org.omegat.gui.editor.EditorTextArea3 | |
import org.omegat.gui.editor.SegmentBuilder | |
import org.omegat.gui.main.MainWindow | |
import org.omegat.util.OConsts | |
import org.omegat.util.OStrings | |
import org.omegat.util.Preferences | |
import org.omegat.util.StaticUtils | |
import org.omegat.util.StringUtil | |
import org.omegat.util.gui.UIThreadsUtil | |
import javax.swing.* | |
import javax.swing.text.BadLocationException | |
import java.awt.* | |
import java.awt.event.ActionListener | |
import java.awt.event.ComponentAdapter | |
import java.awt.event.ComponentEvent | |
import java.awt.event.KeyAdapter | |
import java.awt.event.KeyEvent | |
import java.awt.font.TextLayout | |
import java.beans.PropertyChangeEvent | |
import java.beans.PropertyChangeListener | |
import java.util.List | |
// PoC closure for ActionListener | |
// taken from org.omegat.gui.main.MainWindowMenuHandler | |
def action = { | |
// Create a dialog for input | |
final JOptionPane input = new JOptionPane(OStrings.getString("MW_PROMPT_SEG_NR_MSG"), | |
JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION); // create | |
input.setWantsInput(true); // make it require input | |
final JDialog dialog = new JDialog(mainWindow as MainWindow, OStrings.getString("MW_PROMPT_SEG_NR_TITLE"), true); // create | |
// dialog | |
dialog.setContentPane(input); // add option pane to dialog | |
// create hinting panel | |
final HintingPanel hintingPanel = new HintingPanel(); | |
// Make the dialog verify the input | |
input.addPropertyChangeListener(new PropertyChangeListener() { | |
@Override | |
public void propertyChange(PropertyChangeEvent event) { | |
// Handle the event | |
if (dialog.isVisible() && (event.source == input)) { | |
// If user pressed Enter or OK, check the input | |
String property = event.getPropertyName(); | |
Object value = input.getValue(); | |
// Don't do the checks if no option has been selected | |
if (value == JOptionPane.UNINITIALIZED_VALUE) | |
return; | |
if (property.equals(JOptionPane.INPUT_VALUE_PROPERTY) | |
|| (property.equals(JOptionPane.VALUE_PROPERTY) && ((Integer) value) == JOptionPane.OK_OPTION)) { | |
// Prevent the checks from being done twice | |
input.setValue(JOptionPane.UNINITIALIZED_VALUE); | |
// Get the value entered by the user | |
String inputValue = (String) input.getInputValue(); | |
int maxNr = Core.getProject().getAllEntries().size(); | |
// Check if the user entered a value at all | |
if ((inputValue == null) || (inputValue.trim().length() == 0)) { | |
// Show error message | |
displayErrorMessage(maxNr); | |
return; | |
} | |
// translate hinting-marker to segment number | |
inputValue = hintingPanel.translateSegmentNumber(inputValue); | |
// Check if the user really entered a number | |
int segmentNr; | |
try { | |
// Just parse it. If parsed, it's a number. | |
segmentNr = Integer.parseInt(inputValue); | |
} catch (NumberFormatException e) { | |
// If the exception is thrown, the user didn't | |
// enter a number | |
// Show error message | |
displayErrorMessage(maxNr); | |
return; | |
} | |
// Check if the segment number is within bounds | |
if (segmentNr < 1 || segmentNr > maxNr) { | |
// Tell the user he has to enter a number within | |
// certain bounds | |
displayErrorMessage(maxNr); | |
return; | |
} | |
} | |
// If we're here, the user has either pressed | |
// Cancel/Esc, | |
// or has entered a valid number. In all cases, close | |
// the dialog. | |
dialog.setVisible(false); | |
} | |
} | |
private void displayErrorMessage(int maxNr) { | |
JOptionPane.showMessageDialog(dialog, | |
StaticUtils.format(OStrings.getString("MW_SEGMENT_NUMBER_ERROR"), maxNr), | |
OStrings.getString("TF_ERROR"), JOptionPane.ERROR_MESSAGE); | |
} | |
}); | |
dialog.addComponentListener(new ComponentAdapter() { | |
@Override | |
public void componentShown(ComponentEvent ce) { | |
try { | |
final KeyboardFocusManager keyboardFocusManager = KeyboardFocusManager.currentKeyboardFocusManager | |
final JTextField f = (JTextField) keyboardFocusManager.focusOwner | |
f.addKeyListener(new KeyAdapter() { | |
@Override | |
public void keyTyped(KeyEvent e) { | |
char keyChar = e.keyChar | |
if (keyChar >= ('a' as char) && keyChar <= ('z' as char) && hintingPanel.containsHintChar(String.valueOf(keyChar))) { | |
if (StringUtil.isEmpty(f.text)) { | |
keyboardFocusManager.focusNextComponent() | |
SwingUtilities.invokeLater({ | |
keyboardFocusManager.focusOwner.doClick() | |
} as Runnable) | |
} | |
} | |
} | |
}); | |
} catch (Exception e) { | |
// | |
} | |
} | |
}); | |
hintingPanel.showHint(); | |
// Show the input dialog | |
dialog.pack(); // make it look good | |
dialog.setLocationRelativeTo(mainWindow.applicationFrame); // center it on the main window | |
dialog.setVisible(true); // show it | |
// Get the input value, if any | |
Object inputValue = input.getInputValue(); | |
if ((inputValue != null) && !inputValue.equals(JOptionPane.UNINITIALIZED_VALUE)) { | |
// Go to the segment the user requested | |
try { | |
// translate hinting-marker to segment number | |
inputValue = hintingPanel.translateSegmentNumber(String.valueOf(inputValue)); | |
editor.gotoEntry(Integer.parseInt((String) inputValue)); | |
} catch (ClassCastException e) { | |
// Shouldn't happen, but still... Just eat silently. | |
} catch (NumberFormatException e) { | |
} | |
} | |
hintingPanel.hideHint(); | |
} | |
// HintingPanel | |
class HintingPanel extends JPanel { | |
static final String DEFAULT_HINT_FONT_NAME = "Century"; | |
static final char FIRST_HINT_CHARACTER = 'a'; | |
static final Color HINT_TEXT_COLOR = Color.RED; | |
static final Color HINT_BACKGROUND_COLOR = Color.YELLOW; | |
static final Color HINT_BORDER_COLOR = Color.ORANGE; | |
List<Hint> hintList = null; | |
@Override | |
public void paint(Graphics g) { | |
try { | |
// translate location | |
JScrollPane pane = ((EditorController) Core.editor).scrollPane; | |
Point srcLocation = new Point(0, 0); | |
Point dstLocation = SwingUtilities.convertPoint(pane, srcLocation, this); | |
Graphics2D g2 = (Graphics2D) g.create(); | |
g2.translate(dstLocation.x, dstLocation.y); | |
// enable AntiAliasing | |
if (!g2.getFontRenderContext().isAntiAliased()) { | |
g2.setRenderingHint((RenderingHints.Key)RenderingHints.KEY_ANTIALIASING, | |
RenderingHints.VALUE_ANTIALIAS_ON); | |
} | |
// draw hint | |
hintList.each { hint -> | |
drawHint g2, hint.location, hint.hintChar | |
} | |
g2.dispose(); | |
} catch (Exception e) { | |
System.out.println(e.getMessage()); | |
} | |
} | |
private static void drawHint(Graphics2D g2, Point location, char hintChar) { | |
// create box | |
// location map to the left-bottom corner of the box. | |
Font font = getHintFont(); | |
int boxSize = (int) (font.getSize2D() * 1.4f); | |
Point boxLocation = new Point(location); | |
boxLocation.translate(0, -boxSize); | |
Rectangle box = new Rectangle((int)boxLocation.x, (int)boxLocation.y, boxSize, boxSize); | |
// create text | |
TextLayout layout = new TextLayout(String.valueOf(hintChar), font, g2.getFontRenderContext()); | |
Rectangle pixelBounds = layout.getPixelBounds(null, (int)location.x, (int)location.y); | |
Dimension diffForCentered = getCenteredDimension(pixelBounds, box); | |
// create call-out | |
Polygon poly = createCallout(boxSize); | |
poly.translate((int)boxLocation.x, (int)boxLocation.y); | |
// fill rectangle | |
g2.setColor(HINT_BACKGROUND_COLOR); | |
g2.fill(poly); | |
// draw rectangle | |
g2.setColor(HINT_BORDER_COLOR); | |
g2.draw(poly); | |
// draw hint text | |
g2.setColor(HINT_TEXT_COLOR); | |
layout.draw(g2, (int)location.x + diffForCentered.width, (int)location.y + diffForCentered.height); | |
} | |
private static Polygon createCallout(int boxSize) { | |
Polygon poly = new Polygon(); | |
poly.addPoint(0, boxSize); // +------+ | |
poly.addPoint((int)boxSize / 3, boxSize); // | | | |
poly.addPoint((int)boxSize / 2, (int)boxSize + boxSize / 3); // | | | |
poly.addPoint((int)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 getHintFont() { | |
// font | |
String fontName = null; | |
for (String s: StaticUtils.getFontNames()) { | |
if (s.equals(DEFAULT_HINT_FONT_NAME)) { | |
fontName = DEFAULT_HINT_FONT_NAME | |
} | |
} | |
if (fontName == null) { | |
fontName =Font.SERIF | |
} | |
int fontSize = Preferences.getPreferenceDefault(OConsts.TF_SRC_FONT_SIZE, | |
OConsts.TF_FONT_SIZE_DEFAULT) | |
return new Font(fontName, Font.BOLD, fontSize) | |
} | |
public void showHint() { | |
UIThreadsUtil.mustBeSwingThread() | |
JLayeredPane parent = layeredPane | |
setSize parent.width - 1, parent.height - 1 | |
parent.add this, JLayeredPane.POPUP_LAYER, 0 | |
this.hintList = createHintList() | |
parent.validate() | |
parent.repaint() | |
} | |
public void hideHint() { | |
UIThreadsUtil.mustBeSwingThread(); | |
JLayeredPane parent = layeredPane | |
parent.remove this | |
parent.validate() | |
parent.repaint() | |
} | |
private static JLayeredPane getLayeredPane() { | |
return Core.mainWindow.applicationFrame.layeredPane; | |
} | |
private static List<Hint> createHintList() { | |
List<Hint> list = new ArrayList<Hint>(); | |
EditorController editorController = (EditorController) Core.editor | |
EditorTextArea3 editorTextArea3 = editorController.editor | |
JScrollPane scrollPane = editorController.scrollPane | |
JViewport viewport = scrollPane.getViewport() | |
Rectangle viewRect = viewport.getViewRect() | |
Point viewPosition = viewport.getViewPosition() | |
SegmentBuilder[] docSegList = editorController.m_docSegList | |
char c = FIRST_HINT_CHARACTER; | |
for (SegmentBuilder sb : docSegList) { | |
try { | |
Point location = editorTextArea3.modelToView(sb.getStartPosition()).getLocation() | |
if (viewRect.contains(location)) { // location is viewable | |
Hint hint = new Hint() | |
hint.segmentNumber = sb.segmentNumberInProject | |
location.translate((int)-viewPosition.x, (int)-viewPosition.y) // adjust to view position | |
hint.location = location | |
hint.hintChar = c | |
c += 1 | |
list.add(hint) | |
} | |
} catch (BadLocationException ex) { | |
// ignore | |
} | |
} | |
list | |
} | |
/** | |
* Translate string to segment number. If hinting shortcut character found, | |
* it will be converted to actually segment number string. | |
* @param inputValue | |
* @return traslated string | |
*/ | |
public String translateSegmentNumber(String inputValue) { | |
Hint hint = findHintByChar(inputValue); | |
if (hint != null) { | |
return String.valueOf(hint.segmentNumber); | |
} | |
return inputValue; | |
} | |
public boolean containsHintChar(String hintChar) { | |
Hint hint = findHintByChar(hintChar); | |
return hint != null; | |
} | |
private Hint findHintByChar(String hintChar) { | |
String trimmed = hintChar.trim(); | |
if (trimmed.length() == 1) { | |
char ch = trimmed.charAt(0); | |
// search hinting character | |
for (Hint hint : hintList) { | |
if (ch == hint.hintChar) { | |
return hint; | |
} | |
} | |
} | |
return null; | |
} | |
@ToString | |
private static class Hint { | |
int segmentNumber = 0 | |
Point location = null | |
char hintChar = 0 | |
} | |
} | |
// install PoC action to "Go To Segment Number" menu item (only while current session) | |
def menu = mainWindow.mainMenu.gotoSegmentMenuItem | |
if (menu.actionListeners) { | |
menu.removeActionListener menu.actionListeners[0] // remove current ActionListener | |
} | |
menu.addActionListener action as ActionListener | |
// return message for scripting window | |
"Segment shortcut hinting is installed (enabled while current session). Try new \"Go To Segment Number\" menu out." |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment