|
/* :name=Activate source text :description=Activate source text on the Editor with keyboard shortcut |
|
* |
|
* The workaround by script for RFE #821: |
|
* Showing cursor on the original segment |
|
* http://sourceforge.net/p/omegat/feature-requests/821/ |
|
* |
|
* @author Yu Tang |
|
* @author Kos Ivantsov |
|
* @date 2016-10-24 |
|
* @version 1.1.1 |
|
*/ |
|
|
|
// ******************* |
|
// BEGIN USER SETTINGS |
|
//******************** |
|
|
|
// for the source text field in the editor |
|
FONT_BOLD = true // or false |
|
READ_ONLY = true // or false |
|
|
|
// for the target text field in the create glossary dialog |
|
SELECT_ALL = false // or true |
|
CARET_AT_THE_END = true // or false (at the beginning) |
|
|
|
@Field final int TRIGGER_KEY = KeyEvent.VK_F4 // see https://docs.oracle.com/javase/8/docs/api/java/awt/event/KeyEvent.html for full list |
|
|
|
// ***************************************************** |
|
// END USER SETTINGS, NOTHING FURTHER NEEDS TO BE EDITED |
|
// ***************************************************** |
|
|
|
import groovy.transform.Field |
|
import org.omegat.core.data.ProjectProperties |
|
import org.omegat.gui.glossary.GlossaryEntry |
|
import org.omegat.gui.glossary.GlossaryReaderTSV |
|
import org.omegat.util.Log |
|
import org.omegat.util.StringUtil |
|
|
|
import javax.swing.JTextPane |
|
import java.awt.Component |
|
import java.awt.ComponentOrientation |
|
import java.awt.event.ActionEvent |
|
import java.awt.event.KeyAdapter |
|
import java.awt.event.KeyEvent |
|
import java.awt.event.KeyListener |
|
import java.awt.event.WindowFocusListener |
|
import java.awt.Font |
|
import java.awt.Point |
|
import java.awt.Rectangle |
|
import java.awt.Toolkit |
|
import java.awt.event.InputEvent |
|
|
|
import javax.swing.border.EmptyBorder |
|
import javax.swing.JDialog |
|
import javax.swing.JEditorPane |
|
import javax.swing.JRootPane |
|
import javax.swing.JViewport |
|
import javax.swing.SwingUtilities |
|
|
|
import org.omegat.core.Core |
|
import org.omegat.core.CoreEvents |
|
import org.omegat.core.events.IProjectEventListener |
|
import org.omegat.core.events.IProjectEventListener.PROJECT_CHANGE_TYPE |
|
import org.omegat.core.search.SearchMode |
|
import org.omegat.gui.dialogs.CreateGlossaryEntry |
|
import org.omegat.gui.editor.SegmentBuilder |
|
import org.omegat.gui.search.SearchWindowController |
|
import org.omegat.util.gui.StaticUIUtils |
|
import org.omegat.util.gui.Styles |
|
import org.omegat.util.gui.UIThreadsUtil |
|
import org.omegat.util.OStrings |
|
import org.omegat.util.StaticUtils |
|
|
|
@Field final String SCRIPT_NAME = 'activate_source_text' |
|
|
|
KeyListener createEditorKeyListener() { |
|
[ |
|
keyPressed : { KeyEvent e -> |
|
if (StaticUtils.isKey(e, TRIGGER_KEY, 0)) { |
|
showDialog() |
|
} |
|
} |
|
] as KeyAdapter |
|
} |
|
|
|
KeyListener createDialogEditorKeyListener() { |
|
[ |
|
keyPressed : { KeyEvent e -> |
|
String selection = e.source.selectedText |
|
switch(true) { |
|
// press an Enter key or any trigger key by user settings |
|
case StaticUtils.isKey(e, KeyEvent.VK_ENTER, 0): |
|
case StaticUtils.isKey(e, TRIGGER_KEY, 0): |
|
// insert selected text to the editor |
|
if (selection) { |
|
editor.insertText selection |
|
} |
|
|
|
// close this dialog |
|
closeDialog e.source |
|
break |
|
|
|
case StaticUtils.isKey(e, KeyEvent.VK_F, Toolkit.defaultToolkit.menuShortcutKeyMask): |
|
// open search window |
|
SearchWindowController search = new SearchWindowController(SearchMode.SEARCH) |
|
mainWindow.addSearchWindow search |
|
search.makeVisible selection |
|
break |
|
|
|
case StaticUtils.isKey(e, KeyEvent.VK_G, Toolkit.defaultToolkit.menuShortcutKeyMask | InputEvent.SHIFT_MASK): |
|
// DevNote: the signature of below method is changed. |
|
// OmegaT 3.x: void Core.glossary.showCreateGlossaryEntryDialog() |
|
// OmegaT 4.0: void Core.glossary.showCreateGlossaryEntryDialog(final Frame parent) |
|
|
|
// open add glossary term dialog |
|
Closure<CreateGlossaryEntry> getCreateGlossaryEntryDialog = { -> |
|
Core.glossary.showCreateGlossaryEntryDialog(mainWindow.applicationFrame) |
|
((org.omegat.gui.glossary.GlossaryTextArea)Core.glossary).createGlossaryEntryDialog |
|
} |
|
if (selection) { |
|
def dlg = getCreateGlossaryEntryDialog() |
|
if (dlg.targetText.text) { |
|
// do nothing |
|
} else { |
|
dlg.targetText.text = editor.selectedText |
|
} |
|
SwingUtilities.invokeLater { |
|
dlg.targetText.requestFocus() |
|
if (SELECT_ALL) { |
|
if (CARET_AT_THE_END) { |
|
dlg.targetText.selectAll() |
|
} else { |
|
dlg.targetText.caret.setDot dlg.targetText.text.size() |
|
dlg.targetText.caret.moveDot 0 |
|
} |
|
} else if (!CARET_AT_THE_END) { |
|
dlg.targetText.setCaretPosition(0) |
|
} |
|
} as Runnable |
|
} else { |
|
closeDialog e.source |
|
getCreateGlossaryEntryDialog() |
|
} |
|
break |
|
|
|
case StaticUtils.isKey(e, KeyEvent.VK_G, Toolkit.defaultToolkit.menuShortcutKeyMask): |
|
// Direct adding a glossary term without dialog (Ctrl+G) |
|
String src = StringUtil.normalizeUnicode(selection) |
|
String loc = StringUtil.normalizeUnicode(editor.selectedText) |
|
String com = "" |
|
if (!StringUtil.isEmpty(src) && !StringUtil.isEmpty(loc)) { |
|
try { |
|
ProjectProperties props = Core.project.projectProperties |
|
File out = new File(props.writeableGlossary) |
|
GlossaryReaderTSV.append out, new GlossaryEntry(src, loc, com, true) |
|
} catch (Exception ex) { |
|
Log.log ex |
|
} |
|
} |
|
closeDialog e.source |
|
break |
|
} |
|
} |
|
] as KeyAdapter |
|
} |
|
|
|
JDialog getDialogAncestor(Component comp) { |
|
def win = SwingUtilities.getWindowAncestor(comp) |
|
while (win == null || !win instanceof JDialog) { |
|
win = SwingUtilities.getWindowAncestor(win) |
|
} |
|
win |
|
} |
|
|
|
void showDialog() { |
|
try { |
|
def ste = editor.currentEntry |
|
if (ste == null) { |
|
return |
|
} |
|
|
|
JDialog dialog = createDialog(ste.srcText) |
|
StaticUIUtils.setEscapeClosable dialog |
|
setLostFocusClosable dialog |
|
UIThreadsUtil.executeInSwingThread { dialog.setVisible true } as Runnable |
|
} catch(ex) { |
|
console.println "$SCRIPT_NAME >> $ex" |
|
} |
|
} |
|
|
|
void closeDialog(Component comp) { |
|
JDialog dialog = getDialogAncestor(comp) |
|
def closeAction = dialog.rootPane.actionMap.get("ESCAPE") |
|
def action = new ActionEvent(comp, ActionEvent.ACTION_PERFORMED, "ESCAPE") |
|
closeAction.actionPerformed action |
|
} |
|
|
|
void setLostFocusClosable(JDialog dialog) { |
|
def closeAction = dialog.rootPane.actionMap.get("ESCAPE") |
|
def focusListener = [ |
|
windowGainedFocus : {}, |
|
windowLostFocus : { |
|
def action = new ActionEvent(it.source, ActionEvent.ACTION_PERFORMED, "ESCAPE") |
|
closeAction.actionPerformed action |
|
} |
|
] as WindowFocusListener |
|
dialog.addWindowFocusListener focusListener |
|
} |
|
|
|
JDialog createDialog(String text) { |
|
JDialog dialog = new JDialog(mainWindow) |
|
dialog.setUndecorated true |
|
// dialog.rootPane.setWindowDecorationStyle JRootPane.PLAIN_DIALOG |
|
dialog.rootPane.setWindowDecorationStyle JRootPane.NONE // Fix for Metal LAF by Kos Ivantsov |
|
JEditorPane pane = createEditorPane(text) |
|
pane.addKeyListener createDialogEditorKeyListener() |
|
dialog.add pane |
|
dialog.pack() |
|
dialog.setBounds sourceSegmentRect |
|
dialog |
|
} |
|
|
|
JEditorPane createEditorPane(String text) { |
|
//JEditorPane pane = new JEditorPane('text/plain', text) |
|
JTextPane pane = new JTextPane() //Fix word wrapping issue |
|
pane.text = text |
|
pane.with { |
|
// For read-only settings by Kos Ivantsov |
|
if (READ_ONLY) { |
|
setEditable false |
|
caret.visible = true |
|
} |
|
|
|
setDragEnabled true |
|
setComponentOrientation sourceOrientation |
|
setFont FONT_BOLD ? editor.font.deriveFont(Font.BOLD) : editor.font |
|
setForeground Styles.EditorColor.COLOR_ACTIVE_SOURCE_FG.color |
|
setCaretColor Styles.EditorColor.COLOR_ACTIVE_SOURCE_FG.color |
|
setBackground Styles.EditorColor.COLOR_ACTIVE_SOURCE.color |
|
setCaretPosition 0 |
|
def b = editor.editor.border |
|
def border = new EmptyBorder(0, b.left, 0, b.right) // top and bottom = 0 |
|
setBorder border |
|
} |
|
pane |
|
} |
|
|
|
ComponentOrientation getSourceOrientation() { |
|
editor.sourceLangIsRTL \ |
|
? ComponentOrientation.RIGHT_TO_LEFT |
|
: ComponentOrientation.LEFT_TO_RIGHT |
|
} |
|
|
|
Rectangle getSourceSegmentRect() { |
|
int activeSegment = editor.displayedEntryIndex |
|
JViewport viewport = editor.scrollPane.viewport |
|
Rectangle viewRect = viewport.viewRect |
|
|
|
SegmentBuilder sb = editor.m_docSegList[activeSegment] |
|
int startSourcePosition = sb.startSourcePosition |
|
int startTranslationPosition = sb.startTranslationPosition |
|
if (startTranslationPosition == -1) { |
|
startTranslationPosition = startSourcePosition + sb.sourceText.size() + 1 // + 1 for line break |
|
} |
|
Point sourceLocation = editor.editor.modelToView(startSourcePosition).location |
|
Point transLocation = editor.editor.modelToView(startTranslationPosition).location |
|
|
|
if (!viewRect.contains(sourceLocation)) { // location is NOT viewable |
|
throw new RuntimeException("Source segment must be viewable"); |
|
} |
|
|
|
// create new Rectangle for source segment |
|
int x = viewRect.x |
|
int y = sourceLocation.y |
|
int width = viewRect.width |
|
int height = transLocation.y - y |
|
Point point = new Point(x, y) |
|
SwingUtilities.convertPointToScreen point, viewport.view |
|
Rectangle rect = new Rectangle(point.@x, point.@y, width, height) |
|
rect |
|
} |
|
|
|
boolean isAvailable() { |
|
// OmegaT 4.x or later |
|
(OStrings.VERSION =~/^\d+/)[0].toInteger() >= 4 |
|
} |
|
|
|
// controller class |
|
class ActivateSourceTextController implements IProjectEventListener { |
|
|
|
KeyListener _listener |
|
|
|
ActivateSourceTextController(KeyListener listener) { |
|
_listener = listener |
|
if (Core.project.isProjectLoaded()) { |
|
installKeyListener() |
|
} |
|
} |
|
|
|
void onProjectChanged(PROJECT_CHANGE_TYPE eventType) { |
|
switch(eventType) { |
|
case PROJECT_CHANGE_TYPE.CREATE: |
|
case PROJECT_CHANGE_TYPE.LOAD: |
|
// Lazy adding listener for waiting the opening documents process will be complete. |
|
Runnable doRun = { installKeyListener() } as Runnable |
|
SwingUtilities.invokeLater doRun |
|
break |
|
case PROJECT_CHANGE_TYPE.CLOSE: |
|
uninstallKeyListener() |
|
break |
|
} |
|
} |
|
|
|
void installKeyListener() { |
|
Core.editor.editor.addKeyListener _listener |
|
} |
|
|
|
void uninstallKeyListener() { |
|
Core.editor.editor.removeKeyListener _listener |
|
} |
|
} |
|
|
|
//====================== |
|
// Main routine |
|
//====================== |
|
|
|
// verify OmegaT version |
|
if (! isAvailable()) { |
|
return "$SCRIPT_NAME >> This script is not available before OmegaT 4." |
|
} |
|
|
|
CoreEvents.registerProjectChangeListener new ActivateSourceTextController(createEditorKeyListener()) |
|
"${SCRIPT_NAME}.groovy is available in the current session." |
There's a tiny problem with the Metal LAF, at least on Linux. It has to do with spawned source pane's placement. When it spawns, the last line isn't visible, so if the segment is only one line long, I see only the border. The pane is resizable, but I would much rather just have it guess the segment length properly. If the segment is longer than just a few lines, then the pane covers some of the target. My rude fix for that was editing line 203 to move the pane 40 pixels higher and make it 40 pixels taller ( Rectangle rect = new Rectangle(point.@x, point.@Y-40, width, height+40)), but it's rather an ugly workaround, as it would fail for others who use different fonts.
After comparing with other LAF's and trying it on Windows, I see that the pane is decorated when Metal LAF is used, and the decoration steals from the overall geometry.