Skip to content

Instantly share code, notes, and snippets.

@yu-tang
Last active August 29, 2015 14:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yu-tang/7a5007c4d05ffbeada33 to your computer and use it in GitHub Desktop.
Save yu-tang/7a5007c4d05ffbeada33 to your computer and use it in GitHub Desktop.
PoC for segment shortcut
/*
* 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