Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@yu-tang
Last active October 23, 2019 09:01
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save yu-tang/a6833d687e436ddd5135 to your computer and use it in GitHub Desktop.
Save yu-tang/a6833d687e436ddd5135 to your computer and use it in GitHub Desktop.

activate_source_text.groovy スクリプト

ver.1.1.1 (2016/10/24)

目的

OmegaT 編集ウィンドウの原文領域をアクティブにします。

もともとは OmegaT の下記 RFE に対する、スクリプトによる対処方法です。

#821 Showing cursor on the original segment

上記 RFE は実装済みですが、RFE の実装とスクリプトには機能面に相違があるため、 スクリプトを併用し続けるユーザーのために提供し続けています。

対象バージョン

OmegaT 4.0 以降です。

OmegaT 3.x をお使いの場合は、スクリプトの下記旧バージョンを試してみてください。

https://gist.github.com/yu-tang/a6833d687e436ddd5135/fc981b659fcadf7da44a35ce3955abb66836c3dd

インストール

本スクリプトを一時的に試用する場合は、スクリプトフォルダー (スクリプトウィンドウの File メニューからアクセスできます)にスクリプトを コピーしてください。

スクリプトを一回実行すると、そのセッション内(OmegaT を終了するまで) で本機能が有効になります。

本スクリプトを常用する場合は、スクリプトフォルダー配下の application_startup サブフォルダー(存在しない場合は作成してください) にスクリプトをコピーしてください。

OmegaT 起動時に自動実行されるため、手動実行は不要になります。

アンインストール

本スクリプトをスクリプトフォルダーから削除してください。

なお本機能が有効になっている場合は、スクリプトの削除と関係なく、 該当セッション中は有効のままです。

無効にしたい場合は、スクリプトの削除後に OmegaT を再起動してください。

使い方

本スクリプトを実行した後は、以下の手順で使用できます。

編集ウィンドウ上で [F4] キー(設定で変更可能)を押すと、 アクティブな分節の原文欄にカーソルが移動し、 原文作業モードに移行します。

原文作業モードの有効範囲は、アクティブな分節の原文に限定されます。 原文作業モード中に他の(非アクティブな)分節の原文に移動することはできません。

原文作業モードからは、次のいずれかの操作で復帰できます:

  • [Esc] キー、[F4] キー、[Enter] キーのいずれかを押下する
  • 原文テキスト以外の場所(黄色くハイライトされていない場所)をクリックする

このうち、[F4] キーと [Enter] キーで復帰した場合は、 訳文のカーソル位置に原文の選択テキストが自動挿入されます。 したがって、カーソル位置に原文(の一部)を貼り付けたい場合は、 原文作業モードでわざわざコピーする必要はありません。 単にテキストを選択して、[F4] キーか [Enter] キーで抜けるだけです。 クリップボードは使用しませんので、操作の前後でクリップボードの内容に変更はありません。

[Esc] キーかマウスクリックによって復帰した場合、テキストは自動挿入されません (キャンセル操作として扱われます)。

原文作業モードでは以下のキーボードショートカットを使用できます。

  • Ctrl + F による検索ウィンドウの表示(Ver.0.2~)
  • Ctrl + Shift + G による用語登録ダイアログの表示(Ver.0.2~)
  • Ctrl + G によるダイレクト用語登録(Ver.1.1~)

ユーザー設定

スクリプト本体の冒頭に、ユーザー設定項目がまとめてあります。

FONT_BOLD

アクティブ化した原文テキストを太字で表示するかどうかを true/false で指定します。 既定値は true(太字で表示する)です。

READ_ONLY

アクティブ化した原文テキストを読み取り専用にするかどうかを true/false で指定します。 既定値は true(読み取り専用にする)です。 false を指定した場合は、一時的に原文を自由に編集することができます。 原文を加工してからコピー等の作業が多い場合に便利です。 この原文の変更は一時的なものです。原文作業モードから復帰すると、 原文の変更はすべて元に戻ります。 原文ファイル自体を編集して保存できるわけではありませんので、ご注意ください。

SELECT_ALL

用語登録ダイアログを表示した際、訳語欄のテキストを全選択するかどうかを true/false で指定します。 既定値は false(全選択しない)です。

CARET_AT_THE_END

用語登録ダイアログを表示した際、訳語欄のキャレットを末尾に設定するかどうかを true/false で指定します。 既定値は true(キャレットを末尾に設定する)です。

TRIGGER_KEY

原文作業モードに移行するためのキーを指定します。 既定では、OmegaT 標準のカーソルフリーモード F2 キーに干渉しないように F4 キーに設定されています。

仕様制限

原文作業モードでは、OmegaT 特有の機能が一部制限されます。

たとえばタグの保護機能などです。タグも含めて、すべて通常テキストとして扱われます。

またアクティブな分節の原文が画面上に表示されていない場合(部分的な場合を含む)は、 [F4] キーを押しても原文作業モードに移行しない場合があります (スクリプトウィンドウにエラーが出力されます)。

/* :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."
@kosivantsov
Copy link

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.

@kosivantsov
Copy link

I found the fix. Line 147 should read:

dialog.rootPane.setWindowDecorationStyle JRootPane.NONE
It fixes it for Metal LAF and works as expected for the other LAF's, at least on Linux. See here: http://nadeausoftware.com/articles/2009/02/windows_java_tip_how_control_window_decorations#Usinglookandfeelspecificwindowdecorations

@kosivantsov
Copy link

this is what i did to make it read-only, but still show the caret:
add after line 159:

        setEditable false
        getCaret().setVisible true

@dsmiraglio
Copy link

Hi,
I just switched to Linux and saw the same problem described by Kos on Lubuntu. I corrected line 147 on my OmegaT and now the script is fine. Can you please correct the script on this page, so people that download your script can have immediately the best layout? I have just published a post about this script and added a link to this page (see http://www.language-lane.com/blog/?e=34). I could publish the corrected script on my blog, but I think it is better to have one source for it, so we don't create innumerable and confusing versions (and then it is certainly fairer if people wanting the script download it from the creator's page).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment